summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/CODEOWNERS69
-rw-r--r--.github/workflows/runner.yml2
-rw-r--r--.github/workflows/windows_builds.yml27
-rw-r--r--SConstruct75
-rw-r--r--core/config/project_settings.cpp21
-rw-r--r--core/core_constants.cpp1
-rw-r--r--core/debugger/remote_debugger.cpp36
-rw-r--r--core/extension/gdextension_interface.cpp9
-rw-r--r--core/extension/gdextension_interface.h15
-rw-r--r--core/extension/gdextension_library_loader.cpp4
-rw-r--r--core/extension/gdextension_library_loader.h1
-rw-r--r--core/extension/gdextension_loader.h1
-rw-r--r--core/extension/gdextension_manager.cpp3
-rw-r--r--core/input/input_map.compat.inc41
-rw-r--r--core/input/input_map.cpp5
-rw-r--r--core/input/input_map.h7
-rw-r--r--core/io/image.cpp2
-rw-r--r--core/io/resource.cpp33
-rw-r--r--core/io/resource.h1
-rw-r--r--core/io/resource_format_binary.cpp40
-rw-r--r--core/math/geometry_2d.cpp12
-rw-r--r--core/object/object.h1
-rw-r--r--core/object/ref_counted.h102
-rw-r--r--core/string/translation_domain.cpp292
-rw-r--r--core/string/translation_domain.h42
-rw-r--r--core/string/translation_server.cpp234
-rw-r--r--core/string/translation_server.h21
-rw-r--r--core/string/ustring.cpp25
-rw-r--r--core/string/ustring.h2
-rw-r--r--core/templates/hash_map.cpp43
-rw-r--r--core/templates/hash_map.h43
-rw-r--r--core/templates/hashfuncs.h95
-rw-r--r--core/templates/rid_owner.h139
-rw-r--r--core/variant/callable.cpp19
-rw-r--r--core/variant/dictionary.cpp5
-rw-r--r--core/variant/dictionary.h1
-rw-r--r--core/variant/variant.cpp132
-rw-r--r--core/variant/variant.h18
-rw-r--r--core/variant/variant_call.cpp1
-rw-r--r--core/variant/variant_construct.cpp30
-rw-r--r--core/variant/variant_construct.h9
-rw-r--r--core/variant/variant_internal.h29
-rw-r--r--doc/classes/@GlobalScope.xml12
-rw-r--r--doc/classes/Animation.xml71
-rw-r--r--doc/classes/AnimationPlayer.xml89
-rw-r--r--doc/classes/CPUParticles3D.xml6
-rw-r--r--doc/classes/CameraAttributesPhysical.xml2
-rw-r--r--doc/classes/CameraFeed.xml28
-rw-r--r--doc/classes/Dictionary.xml6
-rw-r--r--doc/classes/DisplayServer.xml7
-rw-r--r--doc/classes/EditorContextMenuPlugin.xml18
-rw-r--r--doc/classes/EditorExportPlugin.xml9
-rw-r--r--doc/classes/EditorInspector.xml1
-rw-r--r--doc/classes/EditorInterface.xml8
-rw-r--r--doc/classes/EditorSettings.xml11
-rw-r--r--doc/classes/Environment.xml3
-rw-r--r--doc/classes/FileDialog.xml21
-rw-r--r--doc/classes/GraphEdit.xml5
-rw-r--r--doc/classes/InputMap.xml2
-rw-r--r--doc/classes/ItemList.xml19
-rw-r--r--doc/classes/LineEdit.xml15
-rw-r--r--doc/classes/PackedVector2Array.xml2
-rw-r--r--doc/classes/Performance.xml17
-rw-r--r--doc/classes/PopupMenu.xml2
-rw-r--r--doc/classes/PopupPanel.xml2
-rw-r--r--doc/classes/ProjectSettings.xml11
-rw-r--r--doc/classes/RenderingServer.xml35
-rw-r--r--doc/classes/Signal.xml2
-rw-r--r--doc/classes/Sprite2D.xml2
-rw-r--r--doc/classes/TextServer.xml5
-rw-r--r--doc/classes/TranslationDomain.xml45
-rw-r--r--doc/classes/TranslationServer.xml5
-rw-r--r--doc/classes/TreeItem.xml16
-rw-r--r--doc/classes/Vector4i.xml2
-rw-r--r--drivers/SCsub8
-rw-r--r--drivers/d3d12/SCsub6
-rw-r--r--drivers/d3d12/dxil_hash.cpp2
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp338
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.h76
-rw-r--r--drivers/egl/egl_manager.cpp65
-rw-r--r--drivers/egl/egl_manager.h7
-rw-r--r--drivers/gles3/effects/cubemap_filter.cpp18
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.h2
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp21
-rw-r--r--drivers/gles3/rasterizer_gles3.h11
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp4
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h5
-rw-r--r--drivers/gles3/shaders/canvas.glsl3
-rw-r--r--drivers/gles3/shaders/scene.glsl291
-rw-r--r--drivers/gles3/storage/config.cpp2
-rw-r--r--drivers/gles3/storage/material_storage.cpp5
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp6
-rw-r--r--drivers/gles3/storage/mesh_storage.h2
-rw-r--r--drivers/gles3/storage/texture_storage.cpp5
-rw-r--r--drivers/gles3/storage/texture_storage.h2
-rw-r--r--drivers/metal/rendering_device_driver_metal.mm9
-rw-r--r--drivers/unix/dir_access_unix.cpp12
-rw-r--r--drivers/vulkan/godot_vulkan.h (renamed from modules/squish/image_decompress_squish.h)18
-rw-r--r--drivers/vulkan/rendering_context_driver_vulkan.h6
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp4
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.h8
-rw-r--r--drivers/vulkan/vulkan_hooks.h6
-rw-r--r--editor/animation_track_editor.cpp1445
-rw-r--r--editor/animation_track_editor.h216
-rw-r--r--editor/code_editor.cpp12
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_parser.cpp46
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.cpp202
-rw-r--r--editor/debugger/debug_adapter/debug_adapter_protocol.h24
-rw-r--r--editor/debugger/editor_debugger_inspector.cpp15
-rw-r--r--editor/debugger/editor_debugger_inspector.h2
-rw-r--r--editor/debugger/editor_debugger_server.cpp4
-rw-r--r--editor/debugger/editor_debugger_tree.cpp5
-rw-r--r--editor/debugger/editor_expression_evaluator.cpp145
-rw-r--r--editor/debugger/editor_expression_evaluator.h (renamed from editor/editor_quick_open.h)76
-rw-r--r--editor/debugger/script_editor_debugger.cpp22
-rw-r--r--editor/debugger/script_editor_debugger.h4
-rw-r--r--editor/directory_create_dialog.cpp94
-rw-r--r--editor/directory_create_dialog.h16
-rw-r--r--editor/editor_about.cpp30
-rw-r--r--editor/editor_about.h5
-rw-r--r--editor/editor_file_system.cpp7
-rw-r--r--editor/editor_inspector.cpp24
-rw-r--r--editor/editor_inspector.h7
-rw-r--r--editor/editor_interface.cpp26
-rw-r--r--editor/editor_interface.h2
-rw-r--r--editor/editor_node.cpp88
-rw-r--r--editor/editor_node.h10
-rw-r--r--editor/editor_paths.cpp16
-rw-r--r--editor/editor_properties.cpp16
-rw-r--r--editor/editor_properties.h2
-rw-r--r--editor/editor_quick_open.cpp308
-rw-r--r--editor/editor_resource_picker.cpp52
-rw-r--r--editor/editor_resource_picker.h5
-rw-r--r--editor/editor_resource_preview.cpp20
-rw-r--r--editor/editor_resource_preview.h7
-rw-r--r--editor/editor_settings.cpp39
-rw-r--r--editor/editor_settings.h7
-rw-r--r--editor/export/editor_export_platform_pc.cpp6
-rw-r--r--editor/export/editor_export_plugin.cpp7
-rw-r--r--editor/export/editor_export_plugin.h2
-rw-r--r--editor/export/editor_export_preset.cpp25
-rw-r--r--editor/export/export_template_manager.cpp3
-rw-r--r--editor/filesystem_dock.cpp90
-rw-r--r--editor/filesystem_dock.h5
-rw-r--r--editor/gui/editor_bottom_panel.cpp29
-rw-r--r--editor/gui/editor_bottom_panel.h3
-rw-r--r--editor/gui/editor_dir_dialog.cpp8
-rw-r--r--editor/gui/editor_dir_dialog.h2
-rw-r--r--editor/gui/editor_file_dialog.cpp44
-rw-r--r--editor/gui/editor_object_selector.cpp14
-rw-r--r--editor/gui/editor_quick_open_dialog.cpp962
-rw-r--r--editor/gui/editor_quick_open_dialog.h232
-rw-r--r--editor/gui/editor_run_bar.cpp13
-rw-r--r--editor/gui/editor_run_bar.h5
-rw-r--r--editor/gui/editor_version_button.cpp (renamed from editor/plugins/cpu_particles_3d_editor_plugin.h)112
-rw-r--r--editor/gui/editor_version_button.h61
-rw-r--r--editor/icons/GuiToggleOffMirrored.svg2
-rw-r--r--editor/icons/Marker.svg1
-rw-r--r--editor/icons/MarkerSelected.svg1
-rw-r--r--editor/import/dynamic_font_import_settings.cpp13
-rw-r--r--editor/import/resource_importer_layered_texture.cpp9
-rw-r--r--editor/import/resource_importer_texture.cpp7
-rw-r--r--editor/inspector_dock.cpp2
-rw-r--r--editor/multi_node_edit.cpp18
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp166
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.h47
-rw-r--r--editor/plugins/animation_library_editor.cpp4
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp58
-rw-r--r--editor/plugins/animation_player_editor_plugin.h26
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp3
-rw-r--r--editor/plugins/control_editor_plugin.cpp4
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.cpp309
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.h94
-rw-r--r--editor/plugins/cpu_particles_3d_editor_plugin.cpp217
-rw-r--r--editor/plugins/editor_context_menu_plugin.cpp22
-rw-r--r--editor/plugins/editor_context_menu_plugin.h2
-rw-r--r--editor/plugins/editor_plugin.cpp1
-rw-r--r--editor/plugins/editor_preview_plugins.cpp9
-rw-r--r--editor/plugins/font_config_plugin.cpp8
-rw-r--r--editor/plugins/gdextension_export_plugin.h5
-rw-r--r--editor/plugins/gpu_particles_2d_editor_plugin.cpp427
-rw-r--r--editor/plugins/gpu_particles_2d_editor_plugin.h101
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.cpp461
-rw-r--r--editor/plugins/gpu_particles_3d_editor_plugin.h118
-rw-r--r--editor/plugins/lightmap_gi_editor_plugin.cpp17
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp147
-rw-r--r--editor/plugins/node_3d_editor_plugin.h6
-rw-r--r--editor/plugins/particles_editor_plugin.cpp968
-rw-r--r--editor/plugins/particles_editor_plugin.h216
-rw-r--r--editor/plugins/script_editor_plugin.cpp18
-rw-r--r--editor/plugins/theme_editor_plugin.cpp78
-rw-r--r--editor/plugins/theme_editor_plugin.h1
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp5
-rw-r--r--editor/plugins/tool_button_editor_plugin.cpp82
-rw-r--r--editor/plugins/tool_button_editor_plugin.h57
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp1
-rw-r--r--editor/project_manager.cpp24
-rw-r--r--editor/project_manager.h7
-rw-r--r--editor/project_manager/project_dialog.cpp17
-rw-r--r--editor/project_settings_editor.cpp8
-rw-r--r--editor/register_editor_types.cpp7
-rw-r--r--editor/renames_map_3_to_4.cpp2
-rw-r--r--editor/scene_tree_dock.cpp103
-rw-r--r--editor/scene_tree_dock.h4
-rw-r--r--editor/themes/editor_fonts.cpp18
-rw-r--r--main/main.cpp33
-rw-r--r--main/performance.cpp21
-rw-r--r--main/performance.h5
-rw-r--r--methods.py36
-rw-r--r--misc/dist/html/full-size.html7
-rw-r--r--misc/dist/linux/org.godotengine.Godot.desktop2
-rw-r--r--misc/extension_api_validation/4.3-stable.expected8
-rwxr-xr-xmisc/scripts/install_vulkan_sdk_macos.sh16
-rw-r--r--modules/basis_universal/SCsub24
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp206
-rw-r--r--modules/basis_universal/image_compress_basisu.h9
-rw-r--r--modules/bcdec/SCsub10
-rw-r--r--modules/bcdec/config.py (renamed from modules/squish/config.py)0
-rw-r--r--modules/bcdec/image_decompress_bcdec.cpp190
-rw-r--r--modules/bcdec/image_decompress_bcdec.h49
-rw-r--r--modules/bcdec/register_types.cpp (renamed from modules/squish/register_types.cpp)9
-rw-r--r--modules/bcdec/register_types.h (renamed from modules/squish/register_types.h)10
-rw-r--r--modules/betsy/SCsub1
-rw-r--r--modules/betsy/bc4.glsl151
-rw-r--r--modules/betsy/image_compress_betsy.cpp80
-rw-r--r--modules/betsy/image_compress_betsy.h7
-rw-r--r--modules/camera/buffer_decoder.cpp10
-rw-r--r--modules/camera/camera_feed_linux.cpp10
-rw-r--r--modules/camera/camera_macos.mm2
-rw-r--r--modules/cvtt/register_types.cpp1
-rw-r--r--modules/enet/doc_classes/ENetPacketPeer.xml6
-rw-r--r--modules/enet/enet_packet_peer.cpp6
-rw-r--r--modules/enet/enet_packet_peer.h1
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml35
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp15
-rw-r--r--modules/gdscript/gdscript_cache.cpp12
-rw-r--r--modules/gdscript/gdscript_editor.cpp32
-rw-r--r--modules/gdscript/gdscript_parser.cpp79
-rw-r--r--modules/gdscript/gdscript_parser.h1
-rw-r--r--modules/gdscript/gdscript_vm.cpp2
-rw-r--r--modules/gdscript/tests/scripts/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out4
-rw-r--r--modules/gdscript/tests/scripts/project.godot2
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd3
-rw-r--r--modules/gltf/gltf_document.cpp6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs13
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs2
-rw-r--r--modules/mono/editor/bindings_generator.cpp69
-rw-r--r--modules/mono/editor/bindings_generator.h3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs68
-rw-r--r--modules/multiplayer/SCsub7
-rw-r--r--modules/multiplayer/doc_classes/SceneMultiplayer.xml2
-rw-r--r--modules/multiplayer/tests/test_scene_multiplayer.h284
-rw-r--r--modules/noise/doc_classes/FastNoiseLite.xml4
-rw-r--r--modules/squish/SCsub45
-rw-r--r--modules/text_server_adv/SCsub1
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct1
-rw-r--r--modules/text_server_adv/text_server_adv.cpp38
-rw-r--r--modules/text_server_adv/text_server_adv.h3
-rw-r--r--modules/text_server_fb/text_server_fb.cpp34
-rw-r--r--modules/text_server_fb/text_server_fb.h3
-rw-r--r--modules/vorbis/resource_importer_ogg_vorbis.cpp1
-rw-r--r--modules/websocket/editor/editor_debugger_server_websocket.cpp4
-rw-r--r--modules/websocket/emws_peer.cpp3
-rw-r--r--modules/websocket/wsl_peer.cpp3
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml2
-rw-r--r--platform/android/api/api.cpp10
-rw-r--r--platform/android/api/java_class_wrapper.h13
-rw-r--r--platform/android/api/jni_singleton.h197
-rw-r--r--platform/android/display_server_android.cpp7
-rw-r--r--platform/android/display_server_android.h1
-rw-r--r--platform/android/export/export_plugin.cpp3
-rw-r--r--platform/android/java/app/build.gradle6
-rw-r--r--platform/android/java/app/config.gradle5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt6
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt63
-rw-r--r--platform/android/java_class_wrapper.cpp10
-rw-r--r--platform/android/java_godot_io_wrapper.cpp11
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/android/java_godot_lib_jni.cpp4
-rw-r--r--platform/android/plugin/godot_plugin_jni.cpp25
-rw-r--r--platform/android/rendering_context_driver_vulkan_android.cpp6
-rw-r--r--platform/ios/detect.py4
-rw-r--r--platform/ios/display_server_ios.h7
-rw-r--r--platform/ios/display_server_ios.mm10
-rw-r--r--platform/ios/doc_classes/EditorExportPlatformIOS.xml125
-rw-r--r--platform/ios/export/export_plugin.cpp233
-rw-r--r--platform/ios/os_ios.mm6
-rw-r--r--platform/linuxbsd/detect.py4
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp2
-rw-r--r--platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp6
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp2
-rw-r--r--platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp6
-rw-r--r--platform/macos/detect.py12
-rw-r--r--platform/macos/display_server_macos.mm10
-rw-r--r--platform/macos/doc_classes/EditorExportPlatformMacOS.xml7
-rw-r--r--platform/macos/export/export_plugin.cpp16
-rw-r--r--platform/macos/godot_menu_delegate.mm6
-rw-r--r--platform/web/export/export_plugin.cpp8
-rw-r--r--platform/web/js/libs/library_godot_audio.js38
-rw-r--r--platform/windows/detect.py7
-rw-r--r--platform/windows/display_server_windows.cpp35
-rw-r--r--platform/windows/display_server_windows.h4
-rw-r--r--platform/windows/rendering_context_driver_vulkan_windows.cpp6
-rw-r--r--scene/2d/cpu_particles_2d.cpp2
-rw-r--r--scene/2d/gpu_particles_2d.cpp12
-rw-r--r--scene/2d/mesh_instance_2d.cpp13
-rw-r--r--scene/2d/skeleton_2d.cpp44
-rw-r--r--scene/2d/skeleton_2d.h2
-rw-r--r--scene/3d/cpu_particles_3d.cpp1
-rw-r--r--scene/3d/lightmap_gi.cpp13
-rw-r--r--scene/3d/node_3d.cpp22
-rw-r--r--scene/3d/skeleton_3d.cpp223
-rw-r--r--scene/3d/skeleton_3d.h13
-rw-r--r--scene/animation/animation_blend_tree.cpp2
-rw-r--r--scene/animation/animation_mixer.cpp93
-rw-r--r--scene/animation/animation_mixer.h2
-rw-r--r--scene/animation/animation_player.cpp187
-rw-r--r--scene/animation/animation_player.h16
-rw-r--r--scene/debugger/scene_debugger.cpp16
-rw-r--r--scene/gui/code_edit.cpp6
-rw-r--r--scene/gui/color_picker.cpp16
-rw-r--r--scene/gui/file_dialog.cpp148
-rw-r--r--scene/gui/file_dialog.h16
-rw-r--r--scene/gui/flow_container.cpp5
-rw-r--r--scene/gui/flow_container.h3
-rw-r--r--scene/gui/graph_edit.cpp50
-rw-r--r--scene/gui/graph_node.cpp2
-rw-r--r--scene/gui/item_list.cpp121
-rw-r--r--scene/gui/item_list.h12
-rw-r--r--scene/gui/line_edit.cpp56
-rw-r--r--scene/gui/line_edit.h5
-rw-r--r--scene/gui/rich_text_label.cpp11
-rw-r--r--scene/gui/scroll_container.cpp2
-rw-r--r--scene/gui/tab_bar.cpp15
-rw-r--r--scene/gui/tab_bar.h4
-rw-r--r--scene/gui/tab_container.cpp4
-rw-r--r--scene/gui/tree.cpp57
-rw-r--r--scene/gui/tree.h7
-rw-r--r--scene/main/http_request.cpp3
-rw-r--r--scene/main/node.h9
-rw-r--r--scene/main/scene_tree.cpp16
-rw-r--r--scene/main/viewport.cpp10
-rw-r--r--scene/register_scene_types.cpp1
-rw-r--r--scene/resources/3d/fog_material.cpp2
-rw-r--r--scene/resources/3d/sky_material.cpp3
-rw-r--r--scene/resources/animation.cpp146
-rw-r--r--scene/resources/animation.h27
-rw-r--r--scene/resources/canvas_item_material.cpp2
-rw-r--r--scene/resources/immediate_mesh.cpp2
-rw-r--r--scene/resources/material.cpp357
-rw-r--r--scene/resources/material.h23
-rw-r--r--scene/resources/particle_process_material.cpp2
-rw-r--r--scene/resources/resource_format_text.cpp78
-rw-r--r--scene/resources/shader.cpp45
-rw-r--r--scene/resources/shader.h6
-rw-r--r--scene/resources/style_box_flat.cpp147
-rw-r--r--scene/theme/default_theme.cpp1
-rw-r--r--scene/theme/icons/toggle_filename_filter.svg1
-rw-r--r--servers/audio_server.cpp2
-rw-r--r--servers/camera/camera_feed.cpp17
-rw-r--r--servers/camera/camera_feed.h6
-rw-r--r--servers/display_server.cpp19
-rw-r--r--servers/display_server.h12
-rw-r--r--servers/rendering/dummy/rasterizer_canvas_dummy.h1
-rw-r--r--servers/rendering/dummy/rasterizer_dummy.h1
-rw-r--r--servers/rendering/dummy/rasterizer_scene_dummy.h5
-rw-r--r--servers/rendering/dummy/storage/texture_storage.h2
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp139
-rw-r--r--servers/rendering/renderer_canvas_cull.h18
-rw-r--r--servers/rendering/renderer_canvas_render.h1
-rw-r--r--servers/rendering/renderer_compositor.h1
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp886
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h92
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp528
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h146
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp646
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h95
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp414
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h130
-rw-r--r--servers/rendering/renderer_rd/pipeline_cache_rd.cpp3
-rw-r--r--servers/rendering/renderer_rd/pipeline_cache_rd.h8
-rw-r--r--servers/rendering/renderer_rd/pipeline_hash_map_rd.h218
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp687
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h169
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.h1
-rw-r--r--servers/rendering/renderer_rd/shader_rd.cpp114
-rw-r--r--servers/rendering/renderer_rd/shader_rd.h19
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl186
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl48
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl245
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl87
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl409
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl119
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl54
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_vertex_lights_inc.glsl82
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp67
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h18
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp89
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.h14
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp202
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h9
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp140
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h25
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp141
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.h32
-rw-r--r--servers/rendering/renderer_scene_cull.cpp8
-rw-r--r--servers/rendering/renderer_scene_cull.h3
-rw-r--r--servers/rendering/renderer_scene_render.h5
-rw-r--r--servers/rendering/rendering_device.cpp1263
-rw-r--r--servers/rendering/rendering_device.h124
-rw-r--r--servers/rendering/rendering_device_commons.cpp2
-rw-r--r--servers/rendering/rendering_device_commons.h2
-rw-r--r--servers/rendering/rendering_device_driver.h4
-rw-r--r--servers/rendering/rendering_method.h5
-rw-r--r--servers/rendering/rendering_server_default.cpp10
-rw-r--r--servers/rendering/rendering_server_default.h93
-rw-r--r--servers/rendering/shader_compiler.cpp10
-rw-r--r--servers/rendering/shader_language.cpp368
-rw-r--r--servers/rendering/shader_language.h4
-rw-r--r--servers/rendering/storage/texture_storage.h1
-rw-r--r--servers/rendering_server.cpp15
-rw-r--r--servers/rendering_server.h19
-rw-r--r--tests/core/io/test_packet_peer.h204
-rw-r--r--tests/core/io/test_stream_peer.h289
-rw-r--r--tests/core/io/test_stream_peer_buffer.h185
-rw-r--r--tests/core/string/test_string.h55
-rw-r--r--tests/scene/test_physics_material.h (renamed from modules/squish/image_decompress_squish.cpp)109
-rw-r--r--tests/scene/test_sky.h141
-rw-r--r--tests/scene/test_tab_bar.h864
-rw-r--r--tests/scene/test_tab_container.h671
-rw-r--r--tests/test_macros.h3
-rw-r--r--tests/test_main.cpp7
-rw-r--r--thirdparty/README.md30
-rw-r--r--thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.cpp2052
-rw-r--r--thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.h45
-rw-r--r--thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.cpp3310
-rw-r--r--thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.h224
-rw-r--r--thirdparty/basis_universal/encoder/basisu_backend.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_backend.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_basis_file.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_basis_file.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_bc7enc.cpp5
-rw-r--r--thirdparty/basis_universal/encoder/basisu_bc7enc.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_comp.cpp2028
-rw-r--r--thirdparty/basis_universal/encoder/basisu_comp.h104
-rw-r--r--thirdparty/basis_universal/encoder/basisu_enc.cpp1634
-rw-r--r--thirdparty/basis_universal/encoder/basisu_enc.h653
-rw-r--r--thirdparty/basis_universal/encoder/basisu_etc.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_etc.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_frontend.cpp3
-rw-r--r--thirdparty/basis_universal/encoder/basisu_frontend.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp561
-rw-r--r--thirdparty/basis_universal/encoder/basisu_gpu_texture.h50
-rw-r--r--thirdparty/basis_universal/encoder/basisu_kernels_declares.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_kernels_imp.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_kernels_sse.cpp18
-rw-r--r--thirdparty/basis_universal/encoder/basisu_miniz.h10
-rw-r--r--thirdparty/basis_universal/encoder/basisu_opencl.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_opencl.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_pvrtc1_4.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_pvrtc1_4.h13
-rw-r--r--thirdparty/basis_universal/encoder/basisu_resample_filters.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_resampler.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_resampler.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_resampler_filters.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_ssim.cpp2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_ssim.h2
-rw-r--r--thirdparty/basis_universal/encoder/basisu_uastc_enc.cpp21
-rw-r--r--thirdparty/basis_universal/encoder/basisu_uastc_enc.h2
-rw-r--r--thirdparty/basis_universal/encoder/cppspmd_flow.h2
-rw-r--r--thirdparty/basis_universal/encoder/cppspmd_math.h4
-rw-r--r--thirdparty/basis_universal/encoder/cppspmd_math_declares.h2
-rw-r--r--thirdparty/basis_universal/encoder/cppspmd_sse.h28
-rw-r--r--thirdparty/basis_universal/encoder/cppspmd_type_aliases.h2
-rw-r--r--thirdparty/basis_universal/encoder/pvpngreader.cpp18
-rw-r--r--thirdparty/basis_universal/patches/external-jpgd.patch (renamed from modules/basis_universal/patches/external-jpgd.patch)0
-rw-r--r--thirdparty/basis_universal/patches/external-tinyexr.patch23
-rw-r--r--thirdparty/basis_universal/patches/remove-tinydds-qoi.patch446
-rw-r--r--thirdparty/basis_universal/transcoder/basisu.h105
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_astc_hdr_core.h102
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_astc_helpers.h3587
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_containers.h62
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_containers_impl.h47
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_file_headers.h5
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_transcoder.cpp2057
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_transcoder.h80
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_transcoder_internal.h216
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_5.inc2
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_6.inc2
-rw-r--r--thirdparty/basis_universal/transcoder/basisu_transcoder_uastc.h1
-rw-r--r--thirdparty/harfbuzz/src/OT/Color/COLR/COLR.hh16
-rw-r--r--thirdparty/harfbuzz/src/OT/Layout/GDEF/GDEF.hh18
-rw-r--r--thirdparty/harfbuzz/src/OT/Layout/GPOS/PairPosFormat2.hh8
-rw-r--r--thirdparty/harfbuzz/src/OT/Var/VARC/VARC.cc346
-rw-r--r--thirdparty/harfbuzz/src/OT/Var/VARC/VARC.hh193
-rw-r--r--thirdparty/harfbuzz/src/OT/Var/VARC/coord-setter.hh37
-rw-r--r--thirdparty/harfbuzz/src/OT/glyf/Glyph.hh135
-rw-r--r--thirdparty/harfbuzz/src/OT/glyf/SubsetGlyph.hh13
-rw-r--r--thirdparty/harfbuzz/src/OT/glyf/glyf.hh13
-rw-r--r--thirdparty/harfbuzz/src/graph/graph.hh4
-rw-r--r--thirdparty/harfbuzz/src/hb-aat-layout-common.hh60
-rw-r--r--thirdparty/harfbuzz/src/hb-aat-layout-just-table.hh12
-rw-r--r--thirdparty/harfbuzz/src/hb-aat-layout-kerx-table.hh44
-rw-r--r--thirdparty/harfbuzz/src/hb-aat-layout-morx-table.hh37
-rw-r--r--thirdparty/harfbuzz/src/hb-aat-layout-opbd-table.hh8
-rw-r--r--thirdparty/harfbuzz/src/hb-buffer-verify.cc2
-rw-r--r--thirdparty/harfbuzz/src/hb-buffer.cc43
-rw-r--r--thirdparty/harfbuzz/src/hb-buffer.h7
-rw-r--r--thirdparty/harfbuzz/src/hb-buffer.hh2
-rw-r--r--thirdparty/harfbuzz/src/hb-cff-interp-common.hh1
-rw-r--r--thirdparty/harfbuzz/src/hb-cff-interp-dict-common.hh2
-rw-r--r--thirdparty/harfbuzz/src/hb-common.h18
-rw-r--r--thirdparty/harfbuzz/src/hb-config.hh4
-rw-r--r--thirdparty/harfbuzz/src/hb-coretext.cc52
-rw-r--r--thirdparty/harfbuzz/src/hb-draw.hh2
-rw-r--r--thirdparty/harfbuzz/src/hb-face-builder.cc61
-rw-r--r--thirdparty/harfbuzz/src/hb-face.cc72
-rw-r--r--thirdparty/harfbuzz/src/hb-face.h28
-rw-r--r--thirdparty/harfbuzz/src/hb-face.hh10
-rw-r--r--thirdparty/harfbuzz/src/hb-features.h119
-rw-r--r--thirdparty/harfbuzz/src/hb-ft-colr.hh2
-rw-r--r--thirdparty/harfbuzz/src/hb-ft.cc40
-rw-r--r--thirdparty/harfbuzz/src/hb-geometry.hh284
-rw-r--r--thirdparty/harfbuzz/src/hb-iter.hh10
-rw-r--r--thirdparty/harfbuzz/src/hb-limits.hh17
-rw-r--r--thirdparty/harfbuzz/src/hb-open-file.hh6
-rw-r--r--thirdparty/harfbuzz/src/hb-open-type.hh741
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-cff-common.hh257
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-cff1-table.hh51
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-cff2-table.cc7
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-cff2-table.hh22
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-cmap-table.hh226
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-face-table-list.hh3
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-font.cc8
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-hmtx-table.hh1
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-kern-table.hh18
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-layout-base-table.hh20
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-layout-common.hh872
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-layout-gsubgpos.hh87
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-layout.cc6
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-layout.hh15
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-post-table-v2subset.hh21
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-post-table.hh2
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc36
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shape-normalize.hh34
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shape.cc26
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-arabic-joining-list.hh8
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-arabic-table.hh23
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-arabic.cc3
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-hebrew.cc2
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-indic-table.cc28
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-use-table.hh543
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper-vowel-constraints.cc4
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-shaper.hh36
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-stat-table.hh46
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-tag-table.hh66
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-var-common.hh482
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-var-cvar-table.hh4
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-var-gvar-table.hh20
-rw-r--r--thirdparty/harfbuzz/src/hb-ot-var-varc-table.hh32
-rw-r--r--thirdparty/harfbuzz/src/hb-paint-extents.hh168
-rw-r--r--thirdparty/harfbuzz/src/hb-style.cc4
-rw-r--r--thirdparty/harfbuzz/src/hb-subset-cff2.cc3
-rw-r--r--thirdparty/harfbuzz/src/hb-subset-input.cc3
-rw-r--r--thirdparty/harfbuzz/src/hb-subset-instancer-solver.cc2
-rw-r--r--thirdparty/harfbuzz/src/hb-subset-plan.cc136
-rw-r--r--thirdparty/harfbuzz/src/hb-subset.cc10
-rw-r--r--thirdparty/harfbuzz/src/hb-ucd-table.hh5932
-rw-r--r--thirdparty/harfbuzz/src/hb-unicode-emoji-table.hh8
-rw-r--r--thirdparty/harfbuzz/src/hb-unicode.hh3
-rw-r--r--thirdparty/harfbuzz/src/hb-version.h8
-rw-r--r--thirdparty/harfbuzz/src/hb.hh1
-rw-r--r--thirdparty/misc/bcdec.h1345
-rw-r--r--thirdparty/squish/LICENSE.txt20
-rw-r--r--thirdparty/squish/alpha.cpp350
-rw-r--r--thirdparty/squish/alpha.h41
-rw-r--r--thirdparty/squish/clusterfit.cpp392
-rw-r--r--thirdparty/squish/clusterfit.h61
-rw-r--r--thirdparty/squish/colourblock.cpp247
-rw-r--r--thirdparty/squish/colourblock.h45
-rw-r--r--thirdparty/squish/colourfit.cpp54
-rw-r--r--thirdparty/squish/colourfit.h56
-rw-r--r--thirdparty/squish/colourset.cpp121
-rw-r--r--thirdparty/squish/colourset.h58
-rw-r--r--thirdparty/squish/config.h69
-rw-r--r--thirdparty/squish/maths.cpp259
-rw-r--r--thirdparty/squish/maths.h233
-rw-r--r--thirdparty/squish/patches/config_sse.patch31
-rw-r--r--thirdparty/squish/patches/decompress_bc4_bc5.patch85
-rw-r--r--thirdparty/squish/rangefit.cpp201
-rw-r--r--thirdparty/squish/rangefit.h54
-rw-r--r--thirdparty/squish/simd.h40
-rw-r--r--thirdparty/squish/simd_float.h183
-rw-r--r--thirdparty/squish/simd_sse.h180
-rw-r--r--thirdparty/squish/simd_ve.h166
-rw-r--r--thirdparty/squish/singlecolourfit.cpp172
-rw-r--r--thirdparty/squish/singlecolourfit.h58
-rw-r--r--thirdparty/squish/singlecolourlookup.inl1064
-rw-r--r--thirdparty/squish/squish.cpp411
-rw-r--r--thirdparty/squish/squish.h309
-rw-r--r--thirdparty/vulkan/patches/VKEnumStringHelper-use-godot-vulkan.patch (renamed from thirdparty/vulkan/patches/VKEnumStringHelper-use-volk.patch)10
-rw-r--r--thirdparty/vulkan/patches/VMA-use-godot-vulkan.patch (renamed from thirdparty/vulkan/patches/VMA-use-volk.patch)15
-rw-r--r--thirdparty/vulkan/vk_enum_string_helper.h6
-rw-r--r--thirdparty/vulkan/vk_mem_alloc.h6
611 files changed, 44831 insertions, 18038 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 7cca1b6f2a..68bd4bc4ce 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,7 +2,7 @@
# Each line is a file pattern followed by one or more owners.
# Owners can be @users, @org/teams or emails
-# Buildsystem
+# Buildsystem (Before everything to be overwritten)
* @godotengine/buildsystem
@@ -17,10 +17,11 @@
# Doc
/doc/ @godotengine/documentation
-**/doc_classes/ @godotengine/documentation
# Drivers
+/drivers/ @godotengine/_systems
+
## Audio
/drivers/alsa/ @godotengine/audio
/drivers/alsamidi/ @godotengine/audio
@@ -34,7 +35,9 @@
## Rendering
/drivers/d3d12/ @godotengine/rendering
/drivers/dummy/ @godotengine/rendering
+/drivers/egl/ @godotengine/rendering
/drivers/gles3/ @godotengine/rendering
+/drivers/metal/ @godotengine/rendering
/drivers/spirv-reflect/ @godotengine/rendering
/drivers/vulkan/ @godotengine/rendering
@@ -47,18 +50,19 @@
# Editor
-/editor/*debugger* @godotengine/debugger
+/editor/ @godotengine/_editor
+/editor/**/*2d* @godotengine/2d-editor
+/editor/**/*3d* @godotengine/3d-editor
+/editor/**/*code* @godotengine/script-editor
+/editor/**/*debugger* @godotengine/debugger
+/editor/**/*dock* @godotengine/docks
+/editor/**/*script* @godotengine/script-editor
+/editor/**/*shader* @godotengine/shaders
+/editor/debugger/ @godotengine/debugger
/editor/gui/ @godotengine/usability @godotengine/gui-nodes
/editor/icons/ @godotengine/usability
/editor/import/ @godotengine/import
-/editor/plugins/*2d_*.* @godotengine/2d-editor
-/editor/plugins/*3d_*.* @godotengine/3d-editor
-/editor/plugins/script_*.* @godotengine/script-editor
-/editor/plugins/*shader*.* @godotengine/shaders
/editor/themes/ @godotengine/usability @godotengine/gui-nodes
-/editor/code_editor.* @godotengine/script-editor
-/editor/*dock*.* @godotengine/docks
-/editor/*shader*.* @godotengine/shaders
# Main
@@ -71,9 +75,16 @@
# Modules
+/modules/ @godotengine/_engine
+/modules/**/doc_classes/ @godotengine/_engine @godotengine/documentation
+/modules/**/editor/ @godotengine/_engine @godotengine/_editor
+/modules/**/icons/ @godotengine/_engine @godotengine/usability
+/modules/**/tests/ @godotengine/_engine @godotengine/tests
+
## Audio (+ video)
/modules/interactive_music/ @godotengine/audio
/modules/interactive_music/doc_classes/ @godotengine/audio @godotengine/documentation
+/modules/interactive_music/editor/ @godotengine/audio @godotengine/_editor
/modules/minimp3/ @godotengine/audio
/modules/minimp3/doc_classes/ @godotengine/audio @godotengine/documentation
/modules/ogg/ @godotengine/audio
@@ -93,8 +104,10 @@
/modules/etcpak/ @godotengine/import
/modules/fbx/ @godotengine/import
/modules/fbx/doc_classes/ @godotengine/import @godotengine/documentation
+/modules/fbx/editor/ @godotengine/import @godotengine/_editor
/modules/gltf/ @godotengine/import
/modules/gltf/doc_classes/ @godotengine/import @godotengine/documentation
+/modules/gltf/editor/ @godotengine/import @godotengine/_editor
/modules/gltf/tests/ @godotengine/import @godotengine/tests
/modules/hdr/ @godotengine/import
/modules/jpg/ @godotengine/import
@@ -112,12 +125,14 @@
/modules/mbedtls/tests/ @godotengine/network @godotengine/tests
/modules/multiplayer/ @godotengine/network
/modules/multiplayer/doc_classes/ @godotengine/network @godotengine/documentation
+/modules/multiplayer/editor/ @godotengine/network @godotengine/_editor
/modules/upnp/ @godotengine/network
/modules/upnp/doc_classes/ @godotengine/network @godotengine/documentation
/modules/webrtc/ @godotengine/network
/modules/webrtc/doc_classes/ @godotengine/network @godotengine/documentation
/modules/websocket/ @godotengine/network
/modules/websocket/doc_classes/ @godotengine/network @godotengine/documentation
+/modules/websocket/editor/ @godotengine/network @godotengine/_editor
## Physics
/modules/godot_physics_2d/ @godotengine/physics
@@ -134,12 +149,14 @@
## Scripting
/modules/gdscript/ @godotengine/gdscript
/modules/gdscript/doc_classes/ @godotengine/gdscript @godotengine/documentation
+/modules/gdscript/editor/ @godotengine/gdscript @godotengine/_editor
/modules/gdscript/icons/ @godotengine/gdscript @godotengine/usability
/modules/gdscript/tests/ @godotengine/gdscript @godotengine/tests
/modules/jsonrpc/ @godotengine/gdscript @godotengine/network
/modules/jsonrpc/tests @godotengine/gdscript @godotengine/network @godotengine/tests
/modules/mono/ @godotengine/dotnet
/modules/mono/doc_classes/ @godotengine/dotnet @godotengine/documentation
+/modules/mono/editor/ @godotengine/dotnet @godotengine/_editor
/modules/mono/icons/ @godotengine/dotnet @godotengine/usability
## Text
@@ -156,28 +173,36 @@
/modules/mobile_vr/doc_classes/ @godotengine/xr @godotengine/documentation
/modules/openxr/ @godotengine/xr
/modules/openxr/doc_classes/ @godotengine/xr @godotengine/documentation
+/modules/openxr/editor/ @godotengine/xr @godotengine/_editor
/modules/webxr/ @godotengine/xr
/modules/webxr/doc_classes/ @godotengine/xr @godotengine/documentation
## Misc
/modules/csg/ @godotengine/3d-nodes
/modules/csg/doc_classes/ @godotengine/3d-nodes @godotengine/documentation
+/modules/csg/editor/ @godotengine/3d-nodes @godotengine/_editor
/modules/csg/icons/ @godotengine/3d-nodes @godotengine/usability
-/modules/navigation/ @godotengine/navigation
/modules/gridmap/ @godotengine/3d-nodes
/modules/gridmap/doc_classes/ @godotengine/3d-nodes @godotengine/documentation
+/modules/gridmap/editor/ @godotengine/3d-nodes @godotengine/_editor
/modules/gridmap/icons/ @godotengine/3d-nodes @godotengine/usability
+/modules/navigation/ @godotengine/navigation
+/modules/navigation/editor/ @godotengine/navigation @godotengine/_editor
/modules/noise/ @godotengine/core
/modules/noise/doc_classes/ @godotengine/core @godotengine/documentation
+/modules/noise/editor/ @godotengine/core @godotengine/_editor
+/modules/noise/icons/ @godotengine/core @godotengine/usability
/modules/noise/tests/ @godotengine/core @godotengine/tests
/modules/regex/ @godotengine/core
/modules/regex/doc_classes/ @godotengine/core @godotengine/documentation
-/modules/regex/test/ @godotengine/core @godotengine/tests
+/modules/regex/icons/ @godotengine/core @godotengine/usability
+/modules/regex/tests/ @godotengine/core @godotengine/tests
/modules/zip/ @godotengine/core
/modules/zip/doc_classes/ @godotengine/core @godotengine/documentation
# Platform
+/platform/ @godotengine/_platforms
/platform/android/ @godotengine/android
/platform/android/doc_classes/ @godotengine/android @godotengine/documentation
/platform/ios/ @godotengine/ios
@@ -193,6 +218,7 @@
# Scene
+/scene/ @godotengine/_systems @godotengine/core
/scene/2d/ @godotengine/2d-nodes
/scene/2d/physics/ @godotengine/2d-nodes @godotengine/physics
/scene/3d/ @godotengine/3d-nodes
@@ -207,17 +233,20 @@
/scene/resources/text_paragraph.* @godotengine/gui-nodes
/scene/resources/visual_shader*.* @godotengine/shaders
/scene/theme/ @godotengine/gui-nodes
+/scene/theme/icons/ @godotengine/gui-nodes @godotengine/usability
# Servers
-/servers/audio* @godotengine/audio
-/servers/camera* @godotengine/xr
-/servers/display_server.* @godotengine/_platforms
-/servers/navigation_server*.* @godotengine/navigation
-/servers/physics* @godotengine/physics
-/servers/rendering* @godotengine/rendering
-/servers/text_server.* @godotengine/gui-nodes
-/servers/xr* @godotengine/xr
+/servers/ @godotengine/_systems
+/servers/**/audio* @godotengine/audio
+/servers/**/camera* @godotengine/xr
+/servers/**/debugger* @godotengine/debugger
+/servers/**/display* @godotengine/_platforms
+/servers/**/navigation* @godotengine/navigation
+/servers/**/physics* @godotengine/physics
+/servers/**/rendering* @godotengine/rendering
+/servers/**/text* @godotengine/gui-nodes
+/servers/**/xr* @godotengine/xr
# Tests
diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml
index d2d0e3571f..fd5e74b914 100644
--- a/.github/workflows/runner.yml
+++ b/.github/workflows/runner.yml
@@ -1,5 +1,5 @@
name: 🔗 GHA
-on: [push, pull_request]
+on: [push, pull_request, merge_group]
concurrency:
group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-runner
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index 95e3d4a553..384284b604 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -30,7 +30,7 @@ jobs:
# Skip debug symbols, they're way too big with MSVC.
sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console
bin: ./bin/godot.windows.editor.x86_64.exe
- artifact: true
+ compiler: msvc
- name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes)
cache-name: windows-editor-clang
@@ -38,6 +38,7 @@ jobs:
tests: true
sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes
bin: ./bin/godot.windows.editor.x86_64.llvm.exe
+ compiler: clang
- name: Template (target=template_release, tests=yes)
cache-name: windows-template
@@ -45,7 +46,16 @@ jobs:
tests: true
sconsflags: debug_symbols=no
bin: ./bin/godot.windows.template_release.x86_64.console.exe
- artifact: true
+ compiler: msvc
+
+ - name: Template w/ GCC (target=template_release, tests=yes, use_mingw=yes)
+ cache-name: windows-template-gcc
+ # MinGW takes MUCH longer to compile; save time by only targeting Template.
+ target: template_release
+ tests: true
+ sconsflags: debug_symbols=no use_mingw=yes
+ bin: ./bin/godot.windows.template_release.x86_64.console.exe
+ compiler: gcc
steps:
- name: Checkout
@@ -69,16 +79,21 @@ jobs:
uses: dsaltares/fetch-gh-release-asset@1.1.2
with:
repo: godotengine/godot-angle-static
- version: tags/chromium/6029
- file: Windows.6029-1.MSVC_17.x86_64.x86_32.zip
+ version: tags/chromium/6601.2
+ file: godot-angle-static-x86_64-${{ matrix.compiler == 'gcc' && 'gcc' || 'msvc' }}-release.zip
target: angle/angle.zip
- name: Extract pre-built ANGLE static libraries
run: Expand-Archive -Force angle/angle.zip ${{ github.workspace }}/
- name: Setup MSVC problem matcher
+ if: matrix.compiler == 'msvc'
uses: ammaraskar/msvc-problem-matcher@master
+ - name: Setup GCC problem matcher
+ if: matrix.compiler != 'msvc'
+ uses: ammaraskar/gcc-problem-matcher@master
+
- name: Compilation
uses: ./.github/actions/godot-build
with:
@@ -94,12 +109,12 @@ jobs:
continue-on-error: true
- name: Prepare artifact
- if: ${{ matrix.artifact }}
+ if: matrix.compiler == 'msvc'
run: |
Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
- name: Upload artifact
- if: ${{ matrix.artifact }}
+ if: matrix.compiler == 'msvc'
uses: ./.github/actions/upload-artifact
with:
name: ${{ matrix.cache-name }}
diff --git a/SConstruct b/SConstruct
index 5566770148..63cff4fe16 100644
--- a/SConstruct
+++ b/SConstruct
@@ -299,7 +299,6 @@ opts.Add(BoolVariable("builtin_pcre2_with_jit", "Use JIT compiler for the built-
opts.Add(BoolVariable("builtin_recastnavigation", "Use the built-in Recast navigation library", True))
opts.Add(BoolVariable("builtin_rvo2_2d", "Use the built-in RVO2 2D library", True))
opts.Add(BoolVariable("builtin_rvo2_3d", "Use the built-in RVO2 3D library", True))
-opts.Add(BoolVariable("builtin_squish", "Use the built-in squish library", True))
opts.Add(BoolVariable("builtin_xatlas", "Use the built-in xatlas library", True))
opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True))
opts.Add(BoolVariable("builtin_zstd", "Use the built-in Zstd library", True))
@@ -640,25 +639,18 @@ if env.dev_build:
print("NOTE: Developer build, with debug optimization level and debug symbols (unless overridden).")
# Enforce our minimal compiler version requirements
-cc_version = methods.get_compiler_version(env) or {
- "major": None,
- "minor": None,
- "patch": None,
- "metadata1": None,
- "metadata2": None,
- "date": None,
-}
-cc_version_major = int(cc_version["major"] or -1)
-cc_version_minor = int(cc_version["minor"] or -1)
-cc_version_metadata1 = cc_version["metadata1"] or ""
-
-if methods.using_gcc(env):
- if cc_version_major == -1:
- print_warning(
- "Couldn't detect compiler version, skipping version checks. "
- "Build may fail if the compiler doesn't support C++17 fully."
- )
- elif cc_version_major < 9:
+cc_version = methods.get_compiler_version(env)
+cc_version_major = cc_version["major"]
+cc_version_minor = cc_version["minor"]
+cc_version_metadata1 = cc_version["metadata1"]
+
+if cc_version_major == -1:
+ print_warning(
+ "Couldn't detect compiler version, skipping version checks. "
+ "Build may fail if the compiler doesn't support C++17 fully."
+ )
+elif methods.using_gcc(env):
+ if cc_version_major < 9:
print_error(
"Detected GCC version older than 9, which does not fully support "
"C++17, or has bugs when compiling Godot. Supported versions are 9 "
@@ -678,17 +670,12 @@ if methods.using_gcc(env):
print_warning("GCC < 8 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.")
env["debug_paths_relative"] = False
elif methods.using_clang(env):
- if cc_version_major == -1:
- print_warning(
- "Couldn't detect compiler version, skipping version checks. "
- "Build may fail if the compiler doesn't support C++17 fully."
- )
# Apple LLVM versions differ from upstream LLVM version \o/, compare
# in https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
- elif env["platform"] == "macos" or env["platform"] == "ios":
+ if env["platform"] == "macos" or env["platform"] == "ios":
vanilla = methods.is_vanilla_clang(env)
if vanilla and cc_version_major < 6:
- print_warning(
+ print_error(
"Detected Clang version older than 6, which does not fully support "
"C++17. Supported versions are Clang 6 and later."
)
@@ -713,6 +700,28 @@ elif methods.using_clang(env):
if env["debug_paths_relative"] and cc_version_major < 10:
print_warning("Clang < 10 doesn't support -ffile-prefix-map, disabling `debug_paths_relative` option.")
env["debug_paths_relative"] = False
+elif env.msvc:
+ # Ensure latest minor builds of Visual Studio 2017/2019.
+ # https://github.com/godotengine/godot/pull/94995#issuecomment-2336464574
+ if cc_version_major == 16 and cc_version_minor < 11:
+ print_error(
+ "Detected Visual Studio 2019 version older than 16.11, which has bugs "
+ "when compiling Godot. Use a newer VS2019 version, or VS2022."
+ )
+ Exit(255)
+ if cc_version_major == 15 and cc_version_minor < 9:
+ print_error(
+ "Detected Visual Studio 2017 version older than 15.9, which has bugs "
+ "when compiling Godot. Use a newer VS2017 version, or VS2019/VS2022."
+ )
+ Exit(255)
+ if cc_version_major < 15:
+ print_error(
+ "Detected Visual Studio 2015 or earlier, which is unsupported in Godot. "
+ "Supported versions are Visual Studio 2017 and later."
+ )
+ Exit(255)
+
# Set optimize and debug_symbols flags.
# "custom" means do nothing and let users set their own optimization flags.
@@ -790,13 +799,17 @@ if env["lto"] != "none":
# This needs to come after `configure`, otherwise we don't have env.msvc.
if not env.msvc:
# Specifying GNU extensions support explicitly, which are supported by
- # both GCC and Clang. Both currently default to gnu11 and gnu++14.
+ # both GCC and Clang. Both currently default to gnu11 and gnu++17.
env.Prepend(CFLAGS=["-std=gnu11"])
env.Prepend(CXXFLAGS=["-std=gnu++17"])
else:
- # MSVC doesn't have clear C standard support, /std only covers C++.
- # We apply it to CCFLAGS (both C and C++ code) in case it impacts C features.
- env.Prepend(CCFLAGS=["/std:c++17"])
+ # MSVC started offering C standard support with Visual Studio 2019 16.8, which covers all
+ # of our supported VS2019 & VS2022 versions; VS2017 will only pass the C++ standard.
+ env.Prepend(CXXFLAGS=["/std:c++17"])
+ if cc_version_major < 16:
+ print_warning("Visual Studio 2017 cannot specify a C-Standard.")
+ else:
+ env.Prepend(CFLAGS=["/std:c11"])
# MSVC is non-conforming with the C++ standard by default, so we enable more conformance.
# Note that this is still not complete conformance, as certain Windows-related headers
# don't compile under complete conformance.
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 7951ee9edd..01f15f9c8e 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -348,7 +348,6 @@ bool ProjectSettings::_get(const StringName &p_name, Variant &r_ret) const {
_THREAD_SAFE_METHOD_
if (!props.has(p_name)) {
- WARN_PRINT("Property not found: " + String(p_name));
return false;
}
r_ret = props[p_name].variant;
@@ -1168,22 +1167,16 @@ bool ProjectSettings::is_project_loaded() const {
}
bool ProjectSettings::_property_can_revert(const StringName &p_name) const {
- if (!props.has(p_name)) {
- return false;
- }
-
- return props[p_name].initial != props[p_name].variant;
+ return props.has(p_name);
}
bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
- if (!props.has(p_name)) {
- return false;
+ const RBMap<StringName, ProjectSettings::VariantContainer>::Element *value = props.find(p_name);
+ if (value) {
+ r_property = value->value().initial.duplicate();
+ return true;
}
-
- // Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
- r_property = props[p_name].initial.duplicate();
-
- return true;
+ return false;
}
void ProjectSettings::set_setting(const String &p_setting, const Variant &p_value) {
@@ -1398,7 +1391,7 @@ void ProjectSettings::_add_builtin_input_map() {
}
Dictionary action;
- action["deadzone"] = Variant(0.5f);
+ action["deadzone"] = Variant(0.2f);
action["events"] = events;
String action_name = "input/" + E.key;
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 68af5abf66..25da49fa5c 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -677,6 +677,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_HIDE_QUATERNION_EDIT);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PASSWORD);
+ BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_TOOL_BUTTON);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MAX);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NONE);
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index e2ed7245a2..fc1b7b74f9 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -37,6 +37,7 @@
#include "core/debugger/script_debugger.h"
#include "core/input/input.h"
#include "core/io/resource_loader.h"
+#include "core/math/expression.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "servers/display_server.h"
@@ -529,6 +530,41 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
} else if (command == "set_skip_breakpoints") {
ERR_FAIL_COND(data.is_empty());
script_debugger->set_skip_breakpoints(data[0]);
+ } else if (command == "evaluate") {
+ String expression_str = data[0];
+ int frame = data[1];
+
+ ScriptInstance *breaked_instance = script_debugger->get_break_language()->debug_get_stack_level_instance(frame);
+ if (!breaked_instance) {
+ break;
+ }
+
+ List<String> locals;
+ List<Variant> local_vals;
+
+ script_debugger->get_break_language()->debug_get_stack_level_locals(frame, &locals, &local_vals);
+ ERR_FAIL_COND(locals.size() != local_vals.size());
+
+ PackedStringArray locals_vector;
+ for (const String &S : locals) {
+ locals_vector.append(S);
+ }
+
+ Array local_vals_array;
+ for (const Variant &V : local_vals) {
+ local_vals_array.append(V);
+ }
+
+ Expression expression;
+ expression.parse(expression_str, locals_vector);
+ const Variant return_val = expression.execute(local_vals_array, breaked_instance->get_owner());
+
+ DebuggerMarshalls::ScriptStackVariable stvar;
+ stvar.name = expression_str;
+ stvar.value = return_val;
+ stvar.type = 3;
+
+ send_message("evaluation_return", stvar.serialize());
} else {
bool captured = false;
ERR_CONTINUE(_try_capture(command, data, captured) != OK);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index ddf90f6130..66b0161160 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -507,6 +507,14 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_
return ret;
}
+static GDObjectInstanceID gdextension_variant_get_object_instance_id(GDExtensionConstVariantPtr p_self) {
+ const Variant *self = (const Variant *)p_self;
+ if (likely(self->get_type() == Variant::OBJECT)) {
+ return self->operator ObjectID();
+ }
+ return 0;
+}
+
static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) {
String name = Variant::get_type_name((Variant::Type)p_type);
memnew_placement(r_ret, String(name));
@@ -1610,6 +1618,7 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(variant_has_method);
REGISTER_INTERFACE_FUNC(variant_has_member);
REGISTER_INTERFACE_FUNC(variant_has_key);
+ REGISTER_INTERFACE_FUNC(variant_get_object_instance_id);
REGISTER_INTERFACE_FUNC(variant_get_type_name);
REGISTER_INTERFACE_FUNC(variant_can_convert);
REGISTER_INTERFACE_FUNC(variant_can_convert_strict);
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 9e3ce25698..374dbfd071 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -1308,6 +1308,21 @@ typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVaria
typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid);
/**
+ * @name variant_get_object_instance_id
+ * @since 4.4
+ *
+ * Gets the object instance ID from a variant of type GDEXTENSION_VARIANT_TYPE_OBJECT.
+ *
+ * If the variant isn't of type GDEXTENSION_VARIANT_TYPE_OBJECT, then zero will be returned.
+ * The instance ID will be returned even if the object is no longer valid - use `object_get_instance_by_id()` to check if the object is still valid.
+ *
+ * @param p_self A pointer to the Variant.
+ *
+ * @return The instance ID for the contained object.
+ */
+typedef GDObjectInstanceID (*GDExtensionInterfaceVariantGetObjectInstanceId)(GDExtensionConstVariantPtr p_self);
+
+/**
* @name variant_get_type_name
* @since 4.1
*
diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp
index 5ba4933c35..d5f2eb668f 100644
--- a/core/extension/gdextension_library_loader.cpp
+++ b/core/extension/gdextension_library_loader.cpp
@@ -259,6 +259,10 @@ bool GDExtensionLibraryLoader::has_library_changed() const {
return false;
}
+bool GDExtensionLibraryLoader::library_exists() const {
+ return FileAccess::exists(resource_path);
+}
+
Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
resource_path = p_path;
diff --git a/core/extension/gdextension_library_loader.h b/core/extension/gdextension_library_loader.h
index f4372a75d4..f781611b30 100644
--- a/core/extension/gdextension_library_loader.h
+++ b/core/extension/gdextension_library_loader.h
@@ -77,6 +77,7 @@ public:
virtual void close_library() override;
virtual bool is_library_open() const override;
virtual bool has_library_changed() const override;
+ virtual bool library_exists() const override;
Error parse_gdextension_file(const String &p_path);
};
diff --git a/core/extension/gdextension_loader.h b/core/extension/gdextension_loader.h
index 7d779858b7..2289550329 100644
--- a/core/extension/gdextension_loader.h
+++ b/core/extension/gdextension_loader.h
@@ -42,6 +42,7 @@ public:
virtual void close_library() = 0;
virtual bool is_library_open() const = 0;
virtual bool has_library_changed() const = 0;
+ virtual bool library_exists() const = 0;
};
#endif // GDEXTENSION_LOADER_H
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index 01efe0d96e..fff938858f 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -302,7 +302,8 @@ bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_exten
for (const String &loaded_extension : loaded_extensions) {
if (!p_extensions.has(loaded_extension)) {
// The extension may not have a .gdextension file.
- if (!FileAccess::exists(loaded_extension)) {
+ const Ref<GDExtension> extension = GDExtensionManager::get_singleton()->get_extension(loaded_extension);
+ if (!extension->get_loader()->library_exists()) {
extensions_removed.push_back(loaded_extension);
}
}
diff --git a/core/input/input_map.compat.inc b/core/input/input_map.compat.inc
new file mode 100644
index 0000000000..da4bd962b6
--- /dev/null
+++ b/core/input/input_map.compat.inc
@@ -0,0 +1,41 @@
+/**************************************************************************/
+/* input_map.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISABLE_DEPRECATED
+
+void InputMap::_add_action_bind_compat_97281(const StringName &p_action, float p_deadzone) {
+ add_action(p_action, p_deadzone);
+}
+
+void InputMap::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::_add_action_bind_compat_97281, DEFVAL(0.5f));
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 9a772c87c9..27a50c79f6 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "input_map.h"
+#include "input_map.compat.inc"
#include "core/config/project_settings.h"
#include "core/input/input.h"
@@ -43,7 +44,7 @@ int InputMap::ALL_DEVICES = -1;
void InputMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action);
ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions);
- ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.5f));
+ ClassDB::bind_method(D_METHOD("add_action", "action", "deadzone"), &InputMap::add_action, DEFVAL(0.2f));
ClassDB::bind_method(D_METHOD("erase_action", "action"), &InputMap::erase_action);
ClassDB::bind_method(D_METHOD("action_set_deadzone", "action", "deadzone"), &InputMap::action_set_deadzone);
@@ -306,7 +307,7 @@ void InputMap::load_from_project_settings() {
String name = pi.name.substr(pi.name.find("/") + 1, pi.name.length());
Dictionary action = GLOBAL_GET(pi.name);
- float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.5f;
+ float deadzone = action.has("deadzone") ? (float)action["deadzone"] : 0.2f;
Array events = action["events"];
add_action(name, deadzone);
diff --git a/core/input/input_map.h b/core/input/input_map.h
index 3774a131e6..b29687d144 100644
--- a/core/input/input_map.h
+++ b/core/input/input_map.h
@@ -69,12 +69,17 @@ private:
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _add_action_bind_compat_97281(const StringName &p_action, float p_deadzone = 0.5);
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
public:
static _FORCE_INLINE_ InputMap *get_singleton() { return singleton; }
bool has_action(const StringName &p_action) const;
List<StringName> get_actions() const;
- void add_action(const StringName &p_action, float p_deadzone = 0.5);
+ void add_action(const StringName &p_action, float p_deadzone = 0.2);
void erase_action(const StringName &p_action);
float action_get_deadzone(const StringName &p_action);
diff --git a/core/io/image.cpp b/core/io/image.cpp
index aa391b77dd..d782af931f 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -2751,7 +2751,7 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels
case COMPRESS_S3TC: {
// BC3 is unsupported currently.
- if ((p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) {
+ if ((p_channels == USED_CHANNELS_R || p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) {
Error result = _image_compress_bc_rd_func(this, p_channels);
// If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme.
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 5f8a4b85a4..0ff4fbe490 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -99,31 +99,42 @@ void Resource::set_path_cache(const String &p_path) {
GDVIRTUAL_CALL(_set_path_cache, p_path);
}
+static thread_local RandomPCG unique_id_gen(0, RandomPCG::DEFAULT_INC);
+
+void Resource::seed_scene_unique_id(uint32_t p_seed) {
+ unique_id_gen.seed(p_seed);
+}
+
String Resource::generate_scene_unique_id() {
// Generate a unique enough hash, but still user-readable.
// If it's not unique it does not matter because the saver will try again.
- OS::DateTime dt = OS::get_singleton()->get_datetime();
- uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec());
- hash = hash_murmur3_one_32(dt.year, hash);
- hash = hash_murmur3_one_32(dt.month, hash);
- hash = hash_murmur3_one_32(dt.day, hash);
- hash = hash_murmur3_one_32(dt.hour, hash);
- hash = hash_murmur3_one_32(dt.minute, hash);
- hash = hash_murmur3_one_32(dt.second, hash);
- hash = hash_murmur3_one_32(Math::rand(), hash);
+ if (unique_id_gen.get_seed() == 0) {
+ OS::DateTime dt = OS::get_singleton()->get_datetime();
+ uint32_t hash = hash_murmur3_one_32(OS::get_singleton()->get_ticks_usec());
+ hash = hash_murmur3_one_32(dt.year, hash);
+ hash = hash_murmur3_one_32(dt.month, hash);
+ hash = hash_murmur3_one_32(dt.day, hash);
+ hash = hash_murmur3_one_32(dt.hour, hash);
+ hash = hash_murmur3_one_32(dt.minute, hash);
+ hash = hash_murmur3_one_32(dt.second, hash);
+ hash = hash_murmur3_one_32(Math::rand(), hash);
+ unique_id_gen.seed(hash);
+ }
+
+ uint32_t random_num = unique_id_gen.rand();
static constexpr uint32_t characters = 5;
static constexpr uint32_t char_count = ('z' - 'a');
static constexpr uint32_t base = char_count + ('9' - '0');
String id;
for (uint32_t i = 0; i < characters; i++) {
- uint32_t c = hash % base;
+ uint32_t c = random_num % base;
if (c < char_count) {
id += String::chr('a' + c);
} else {
id += String::chr('0' + (c - char_count));
}
- hash /= base;
+ random_num /= base;
}
return id;
diff --git a/core/io/resource.h b/core/io/resource.h
index 8966c0233c..015f7ad197 100644
--- a/core/io/resource.h
+++ b/core/io/resource.h
@@ -114,6 +114,7 @@ public:
virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache.
_FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); }
+ static void seed_scene_unique_id(uint32_t p_seed);
static String generate_scene_unique_id();
void set_scene_unique_id(const String &p_id);
String get_scene_unique_id() const;
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index b4826c356e..109999d612 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -845,29 +845,27 @@ Error ResourceLoaderBinary::load() {
}
}
- if (ClassDB::has_property(res->get_class_name(), name)) {
- if (value.get_type() == Variant::ARRAY) {
- Array set_array = value;
- bool is_get_valid = false;
- Variant get_value = res->get(name, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
- Array get_array = get_value;
- if (!set_array.is_same_typed(get_array)) {
- value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
- }
+ if (value.get_type() == Variant::ARRAY) {
+ Array set_array = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(name, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
+ Array get_array = get_value;
+ if (!set_array.is_same_typed(get_array)) {
+ value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
}
}
+ }
- if (value.get_type() == Variant::DICTIONARY) {
- Dictionary set_dict = value;
- bool is_get_valid = false;
- Variant get_value = res->get(name, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
- Dictionary get_dict = get_value;
- if (!set_dict.is_same_typed(get_dict)) {
- value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
- get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
- }
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(name, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
}
}
}
@@ -2136,6 +2134,8 @@ static String _resource_get_class(Ref<Resource> p_resource) {
}
Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) {
+ Resource::seed_scene_unique_id(p_path.hash());
+
Error err;
Ref<FileAccess> f;
if (p_flags & ResourceSaver::FLAG_COMPRESS) {
diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp
index d60619b27f..a49826958a 100644
--- a/core/math/geometry_2d.cpp
+++ b/core/math/geometry_2d.cpp
@@ -35,7 +35,8 @@
#define STB_RECT_PACK_IMPLEMENTATION
#include "thirdparty/misc/stb_rect_pack.h"
-#define PRECISION 5 // Based on CMP_EPSILON.
+const int clipper_precision = 5; // Based on CMP_EPSILON.
+const double clipper_scale = Math::pow(10.0, clipper_precision);
Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &polygon) {
Vector<Vector<Vector2>> decomp;
@@ -224,7 +225,7 @@ Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation
path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y);
}
- ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision.
+ ClipperD clp(clipper_precision); // Scale points up internally to attain the desired precision.
clp.PreserveCollinear(false); // Remove redundant vertices.
if (is_a_open) {
clp.AddOpenSubject({ path_a });
@@ -298,9 +299,10 @@ Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_poly
}
// Inflate/deflate.
- PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0);
- // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
- // and the PRECISION is used to scale points up internally, to attain the desired precision.
+ PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, clipper_precision, 0.25 * clipper_scale);
+ // Here the points are scaled up internally and
+ // the arc_tolerance is scaled accordingly
+ // to attain the desired precision.
Vector<Vector<Point2>> polypaths;
for (PathsD::size_type i = 0; i < paths.size(); ++i) {
diff --git a/core/object/object.h b/core/object/object.h
index efe22ecc1a..110d2790c5 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -87,6 +87,7 @@ enum PropertyHint {
PROPERTY_HINT_PASSWORD,
PROPERTY_HINT_LAYERS_AVOIDANCE,
PROPERTY_HINT_DICTIONARY_TYPE,
+ PROPERTY_HINT_TOOL_BUTTON,
PROPERTY_HINT_MAX,
};
diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h
index f0706b4d08..22eb5a7a3f 100644
--- a/core/object/ref_counted.h
+++ b/core/object/ref_counted.h
@@ -57,24 +57,30 @@ template <typename T>
class Ref {
T *reference = nullptr;
- void ref(const Ref &p_from) {
- if (p_from.reference == reference) {
+ _FORCE_INLINE_ void ref(const Ref &p_from) {
+ ref_pointer<false>(p_from.reference);
+ }
+
+ template <bool Init>
+ _FORCE_INLINE_ void ref_pointer(T *p_refcounted) {
+ if (p_refcounted == reference) {
return;
}
- unref();
-
- reference = p_from.reference;
+ // This will go out of scope and get unref'd.
+ Ref cleanup_ref;
+ cleanup_ref.reference = reference;
+ reference = p_refcounted;
if (reference) {
- reference->reference();
- }
- }
-
- void ref_pointer(T *p_ref) {
- ERR_FAIL_NULL(p_ref);
-
- if (p_ref->init_ref()) {
- reference = p_ref;
+ if constexpr (Init) {
+ if (!reference->init_ref()) {
+ reference = nullptr;
+ }
+ } else {
+ if (!reference->reference()) {
+ reference = nullptr;
+ }
+ }
}
}
@@ -124,15 +130,11 @@ public:
template <typename T_Other>
void operator=(const Ref<T_Other> &p_from) {
- RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr()));
- if (!refb) {
- unref();
- return;
- }
- Ref r;
- r.reference = Object::cast_to<T>(refb);
- ref(r);
- r.reference = nullptr;
+ ref_pointer<false>(Object::cast_to<T>(p_from.ptr()));
+ }
+
+ void operator=(T *p_from) {
+ ref_pointer<true>(p_from);
}
void operator=(const Variant &p_variant) {
@@ -142,16 +144,7 @@ public:
return;
}
- unref();
-
- if (!object) {
- return;
- }
-
- T *r = Object::cast_to<T>(object);
- if (r && r->reference()) {
- reference = r;
- }
+ ref_pointer<false>(Object::cast_to<T>(object));
}
template <typename T_Other>
@@ -159,48 +152,25 @@ public:
if (reference == p_ptr) {
return;
}
- unref();
- T *r = Object::cast_to<T>(p_ptr);
- if (r) {
- ref_pointer(r);
- }
+ ref_pointer<true>(Object::cast_to<T>(p_ptr));
}
Ref(const Ref &p_from) {
- ref(p_from);
+ this->operator=(p_from);
}
template <typename T_Other>
Ref(const Ref<T_Other> &p_from) {
- RefCounted *refb = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_from.ptr()));
- if (!refb) {
- unref();
- return;
- }
- Ref r;
- r.reference = Object::cast_to<T>(refb);
- ref(r);
- r.reference = nullptr;
+ this->operator=(p_from);
}
- Ref(T *p_reference) {
- if (p_reference) {
- ref_pointer(p_reference);
- }
+ Ref(T *p_from) {
+ this->operator=(p_from);
}
- Ref(const Variant &p_variant) {
- Object *object = p_variant.get_validated_object();
-
- if (!object) {
- return;
- }
-
- T *r = Object::cast_to<T>(object);
- if (r && r->reference()) {
- reference = r;
- }
+ Ref(const Variant &p_from) {
+ this->operator=(p_from);
}
inline bool is_valid() const { return reference != nullptr; }
@@ -222,7 +192,7 @@ public:
ref(memnew(T(p_params...)));
}
- Ref() {}
+ Ref() = default;
~Ref() {
unref();
@@ -299,13 +269,13 @@ struct GetTypeInfo<const Ref<T> &> {
template <typename T>
struct VariantInternalAccessor<Ref<T>> {
static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
- static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); }
+ static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::object_assign(v, p_ref); }
};
template <typename T>
struct VariantInternalAccessor<const Ref<T> &> {
static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
- static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); }
+ static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::object_assign(v, p_ref); }
};
#endif // REF_COUNTED_H
diff --git a/core/string/translation_domain.cpp b/core/string/translation_domain.cpp
index b44eb40366..6a5e1b2af8 100644
--- a/core/string/translation_domain.cpp
+++ b/core/string/translation_domain.cpp
@@ -33,6 +33,170 @@
#include "core/string/translation.h"
#include "core/string/translation_server.h"
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
+String TranslationDomain::_get_override_string(const String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationDomain::_double_vowels(const String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationDomain::_replace_with_accented_string(const String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = _get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationDomain::_wrap_with_fakebidi_characters(const String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
+ for (int i = 0; i < p_message.length(); i++) {
+ if (p_message[i] == '\n') {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += fakebidiprefix;
+ } else if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ return res;
+}
+
+String TranslationDomain::_add_padding(const String &p_message, int p_length) const {
+ String underscores = String("_").repeat(p_length * pseudolocalization.expansion_ratio / 2);
+ String prefix = pseudolocalization.prefix + underscores;
+ String suffix = underscores + pseudolocalization.suffix;
+
+ return prefix + p_message + suffix;
+}
+
+const char32_t *TranslationDomain::_get_accented_version(char32_t p_character) const {
+ if (!is_ascii_alphabet_char(p_character)) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationDomain::_is_placeholder(const String &p_message, int p_index) const {
+ return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
+ (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
+ p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+}
+
StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const {
StringName res;
int best_score = 0;
@@ -129,9 +293,9 @@ StringName TranslationDomain::translate(const StringName &p_message, const Strin
}
if (!res) {
- return p_message;
+ return pseudolocalization.enabled ? pseudolocalize(p_message) : p_message;
}
- return res;
+ return pseudolocalization.enabled ? pseudolocalize(res) : res;
}
StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -152,6 +316,100 @@ StringName TranslationDomain::translate_plural(const StringName &p_message, cons
return res;
}
+bool TranslationDomain::is_pseudolocalization_enabled() const {
+ return pseudolocalization.enabled;
+}
+
+void TranslationDomain::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization.enabled = p_enabled;
+}
+
+bool TranslationDomain::is_pseudolocalization_accents_enabled() const {
+ return pseudolocalization.accents_enabled;
+}
+
+void TranslationDomain::set_pseudolocalization_accents_enabled(bool p_enabled) {
+ pseudolocalization.accents_enabled = p_enabled;
+}
+
+bool TranslationDomain::is_pseudolocalization_double_vowels_enabled() const {
+ return pseudolocalization.double_vowels_enabled;
+}
+
+void TranslationDomain::set_pseudolocalization_double_vowels_enabled(bool p_enabled) {
+ pseudolocalization.double_vowels_enabled = p_enabled;
+}
+
+bool TranslationDomain::is_pseudolocalization_fake_bidi_enabled() const {
+ return pseudolocalization.fake_bidi_enabled;
+}
+
+void TranslationDomain::set_pseudolocalization_fake_bidi_enabled(bool p_enabled) {
+ pseudolocalization.fake_bidi_enabled = p_enabled;
+}
+
+bool TranslationDomain::is_pseudolocalization_override_enabled() const {
+ return pseudolocalization.override_enabled;
+}
+
+void TranslationDomain::set_pseudolocalization_override_enabled(bool p_enabled) {
+ pseudolocalization.override_enabled = p_enabled;
+}
+
+bool TranslationDomain::is_pseudolocalization_skip_placeholders_enabled() const {
+ return pseudolocalization.skip_placeholders_enabled;
+}
+
+void TranslationDomain::set_pseudolocalization_skip_placeholders_enabled(bool p_enabled) {
+ pseudolocalization.skip_placeholders_enabled = p_enabled;
+}
+
+float TranslationDomain::get_pseudolocalization_expansion_ratio() const {
+ return pseudolocalization.expansion_ratio;
+}
+
+void TranslationDomain::set_pseudolocalization_expansion_ratio(float p_ratio) {
+ pseudolocalization.expansion_ratio = p_ratio;
+}
+
+String TranslationDomain::get_pseudolocalization_prefix() const {
+ return pseudolocalization.prefix;
+}
+
+void TranslationDomain::set_pseudolocalization_prefix(const String &p_prefix) {
+ pseudolocalization.prefix = p_prefix;
+}
+
+String TranslationDomain::get_pseudolocalization_suffix() const {
+ return pseudolocalization.suffix;
+}
+
+void TranslationDomain::set_pseudolocalization_suffix(const String &p_suffix) {
+ pseudolocalization.suffix = p_suffix;
+}
+
+StringName TranslationDomain::pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ int length = message.length();
+ if (pseudolocalization.override_enabled) {
+ message = _get_override_string(message);
+ }
+
+ if (pseudolocalization.double_vowels_enabled) {
+ message = _double_vowels(message);
+ }
+
+ if (pseudolocalization.accents_enabled) {
+ message = _replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization.fake_bidi_enabled) {
+ message = _wrap_with_fakebidi_characters(message);
+ }
+
+ return _add_padding(message, length);
+}
+
void TranslationDomain::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object);
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationDomain::add_translation);
@@ -159,6 +417,36 @@ void TranslationDomain::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &TranslationDomain::clear);
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName()));
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationDomain::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_accents_enabled"), &TranslationDomain::is_pseudolocalization_accents_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_accents_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_accents_enabled);
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_double_vowels_enabled"), &TranslationDomain::is_pseudolocalization_double_vowels_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_double_vowels_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_double_vowels_enabled);
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_fake_bidi_enabled"), &TranslationDomain::is_pseudolocalization_fake_bidi_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_fake_bidi_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_fake_bidi_enabled);
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_override_enabled"), &TranslationDomain::is_pseudolocalization_override_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_override_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_override_enabled);
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_skip_placeholders_enabled"), &TranslationDomain::is_pseudolocalization_skip_placeholders_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_skip_placeholders_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_skip_placeholders_enabled);
+ ClassDB::bind_method(D_METHOD("get_pseudolocalization_expansion_ratio"), &TranslationDomain::get_pseudolocalization_expansion_ratio);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_expansion_ratio", "ratio"), &TranslationDomain::set_pseudolocalization_expansion_ratio);
+ ClassDB::bind_method(D_METHOD("get_pseudolocalization_prefix"), &TranslationDomain::get_pseudolocalization_prefix);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_prefix", "prefix"), &TranslationDomain::set_pseudolocalization_prefix);
+ ClassDB::bind_method(D_METHOD("get_pseudolocalization_suffix"), &TranslationDomain::get_pseudolocalization_suffix);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_suffix", "suffix"), &TranslationDomain::set_pseudolocalization_suffix);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationDomain::pseudolocalize);
+
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_accents_enabled"), "set_pseudolocalization_accents_enabled", "is_pseudolocalization_accents_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_double_vowels_enabled"), "set_pseudolocalization_double_vowels_enabled", "is_pseudolocalization_double_vowels_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_fake_bidi_enabled"), "set_pseudolocalization_fake_bidi_enabled", "is_pseudolocalization_fake_bidi_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_override_enabled"), "set_pseudolocalization_override_enabled", "is_pseudolocalization_override_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_skip_placeholders_enabled"), "set_pseudolocalization_skip_placeholders_enabled", "is_pseudolocalization_skip_placeholders_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::FLOAT, "pseudolocalization_expansion_ratio"), "set_pseudolocalization_expansion_ratio", "get_pseudolocalization_expansion_ratio");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_prefix"), "set_pseudolocalization_prefix", "get_pseudolocalization_prefix");
+ ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_suffix"), "set_pseudolocalization_suffix", "get_pseudolocalization_suffix");
}
TranslationDomain::TranslationDomain() {
diff --git a/core/string/translation_domain.h b/core/string/translation_domain.h
index 6139967217..55592d3b35 100644
--- a/core/string/translation_domain.h
+++ b/core/string/translation_domain.h
@@ -38,7 +38,28 @@ class Translation;
class TranslationDomain : public RefCounted {
GDCLASS(TranslationDomain, RefCounted);
+ struct PseudolocalizationConfig {
+ bool enabled = false;
+ bool accents_enabled = true;
+ bool double_vowels_enabled = false;
+ bool fake_bidi_enabled = false;
+ bool override_enabled = false;
+ bool skip_placeholders_enabled = true;
+ float expansion_ratio = 0.0;
+ String prefix = "[";
+ String suffix = "]";
+ };
+
HashSet<Ref<Translation>> translations;
+ PseudolocalizationConfig pseudolocalization;
+
+ String _get_override_string(const String &p_message) const;
+ String _double_vowels(const String &p_message) const;
+ String _replace_with_accented_string(const String &p_message) const;
+ String _wrap_with_fakebidi_characters(const String &p_message) const;
+ String _add_padding(const String &p_message, int p_length) const;
+ const char32_t *_get_accented_version(char32_t p_character) const;
+ bool _is_placeholder(const String &p_message, int p_index) const;
protected:
static void _bind_methods();
@@ -59,6 +80,27 @@ public:
StringName translate(const StringName &p_message, const StringName &p_context) const;
StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const;
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ bool is_pseudolocalization_accents_enabled() const;
+ void set_pseudolocalization_accents_enabled(bool p_enabled);
+ bool is_pseudolocalization_double_vowels_enabled() const;
+ void set_pseudolocalization_double_vowels_enabled(bool p_enabled);
+ bool is_pseudolocalization_fake_bidi_enabled() const;
+ void set_pseudolocalization_fake_bidi_enabled(bool p_enabled);
+ bool is_pseudolocalization_override_enabled() const;
+ void set_pseudolocalization_override_enabled(bool p_enabled);
+ bool is_pseudolocalization_skip_placeholders_enabled() const;
+ void set_pseudolocalization_skip_placeholders_enabled(bool p_enabled);
+ float get_pseudolocalization_expansion_ratio() const;
+ void set_pseudolocalization_expansion_ratio(float p_ratio);
+ String get_pseudolocalization_prefix() const;
+ void set_pseudolocalization_prefix(const String &p_prefix);
+ String get_pseudolocalization_suffix() const;
+ void set_pseudolocalization_suffix(const String &p_suffix);
+
+ StringName pseudolocalize(const StringName &p_message) const;
+
TranslationDomain();
};
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index c6b818a49b..89b37d0b8a 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -39,66 +39,6 @@
#include "main/main.h"
#endif
-struct _character_accent_pair {
- const char32_t character;
- const char32_t *accented_character;
-};
-
-static _character_accent_pair _character_to_accented[] = {
- { 'A', U"Å" },
- { 'B', U"ß" },
- { 'C', U"Ç" },
- { 'D', U"Ð" },
- { 'E', U"É" },
- { 'F', U"F́" },
- { 'G', U"Ĝ" },
- { 'H', U"Ĥ" },
- { 'I', U"Ĩ" },
- { 'J', U"Ĵ" },
- { 'K', U"ĸ" },
- { 'L', U"Ł" },
- { 'M', U"Ḿ" },
- { 'N', U"й" },
- { 'O', U"Ö" },
- { 'P', U"Ṕ" },
- { 'Q', U"Q́" },
- { 'R', U"Ř" },
- { 'S', U"Ŝ" },
- { 'T', U"Ŧ" },
- { 'U', U"Ũ" },
- { 'V', U"Ṽ" },
- { 'W', U"Ŵ" },
- { 'X', U"X́" },
- { 'Y', U"Ÿ" },
- { 'Z', U"Ž" },
- { 'a', U"á" },
- { 'b', U"ḅ" },
- { 'c', U"ć" },
- { 'd', U"d́" },
- { 'e', U"é" },
- { 'f', U"f́" },
- { 'g', U"ǵ" },
- { 'h', U"h̀" },
- { 'i', U"í" },
- { 'j', U"ǰ" },
- { 'k', U"ḱ" },
- { 'l', U"ł" },
- { 'm', U"m̀" },
- { 'n', U"ή" },
- { 'o', U"ô" },
- { 'p', U"ṕ" },
- { 'q', U"q́" },
- { 'r', U"ŕ" },
- { 's', U"š" },
- { 't', U"ŧ" },
- { 'u', U"ü" },
- { 'v', U"ṽ" },
- { 'w', U"ŵ" },
- { 'x', U"x́" },
- { 'y', U"ý" },
- { 'z', U"ź" },
-};
-
Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
HashMap<String, String> TranslationServer::language_map;
@@ -433,8 +373,7 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
return p_message;
}
- const StringName res = main_domain->translate(p_message, p_context);
- return pseudolocalization_enabled ? pseudolocalize(res) : res;
+ return main_domain->translate(p_message, p_context);
}
StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -510,15 +449,15 @@ void TranslationServer::setup() {
}
fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
- pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
- pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
- pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
- pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
- pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
- expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
- pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
- pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+ main_domain->set_pseudolocalization_enabled(GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false));
+ main_domain->set_pseudolocalization_accents_enabled(GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true));
+ main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false));
+ main_domain->set_pseudolocalization_fake_bidi_enabled(GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false));
+ main_domain->set_pseudolocalization_override_enabled(GLOBAL_DEF("internationalization/pseudolocalization/override", false));
+ main_domain->set_pseudolocalization_expansion_ratio(GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0));
+ main_domain->set_pseudolocalization_prefix(GLOBAL_DEF("internationalization/pseudolocalization/prefix", "["));
+ main_domain->set_pseudolocalization_suffix(GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]"));
+ main_domain->set_pseudolocalization_skip_placeholders_enabled(GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true));
#ifdef TOOLS_ENABLED
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
@@ -567,11 +506,11 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
}
bool TranslationServer::is_pseudolocalization_enabled() const {
- return pseudolocalization_enabled;
+ return main_domain->is_pseudolocalization_enabled();
}
void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
- pseudolocalization_enabled = p_enabled;
+ main_domain->set_pseudolocalization_enabled(p_enabled);
ResourceLoader::reload_translation_remaps();
@@ -581,14 +520,14 @@ void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
}
void TranslationServer::reload_pseudolocalization() {
- pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
- pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
- pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
- pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
- expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
- pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
- pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+ main_domain->set_pseudolocalization_accents_enabled(GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"));
+ main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_GET("internationalization/pseudolocalization/double_vowels"));
+ main_domain->set_pseudolocalization_fake_bidi_enabled(GLOBAL_GET("internationalization/pseudolocalization/fake_bidi"));
+ main_domain->set_pseudolocalization_override_enabled(GLOBAL_GET("internationalization/pseudolocalization/override"));
+ main_domain->set_pseudolocalization_expansion_ratio(GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio"));
+ main_domain->set_pseudolocalization_prefix(GLOBAL_GET("internationalization/pseudolocalization/prefix"));
+ main_domain->set_pseudolocalization_suffix(GLOBAL_GET("internationalization/pseudolocalization/suffix"));
+ main_domain->set_pseudolocalization_skip_placeholders_enabled(GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders"));
ResourceLoader::reload_translation_remaps();
@@ -598,138 +537,7 @@ void TranslationServer::reload_pseudolocalization() {
}
StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
- String message = p_message;
- int length = message.length();
- if (pseudolocalization_override_enabled) {
- message = get_override_string(message);
- }
-
- if (pseudolocalization_double_vowels_enabled) {
- message = double_vowels(message);
- }
-
- if (pseudolocalization_accents_enabled) {
- message = replace_with_accented_string(message);
- }
-
- if (pseudolocalization_fake_bidi_enabled) {
- message = wrap_with_fakebidi_characters(message);
- }
-
- StringName res = add_padding(message, length);
- return res;
-}
-
-StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
- String message = p_message;
- message = double_vowels(message);
- message = replace_with_accented_string(message);
- StringName res = "[!!! " + message + " !!!]";
- return res;
-}
-
-String TranslationServer::get_override_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += '*';
- }
- return res;
-}
-
-String TranslationServer::double_vowels(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += p_message[i];
- if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
- p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
- res += p_message[i];
- }
- }
- return res;
-};
-
-String TranslationServer::replace_with_accented_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- const char32_t *accented = get_accented_version(p_message[i]);
- if (accented) {
- res += accented;
- } else {
- res += p_message[i];
- }
- }
- return res;
-}
-
-String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
- String res;
- char32_t fakebidiprefix = U'\u202e';
- char32_t fakebidisuffix = U'\u202c';
- res += fakebidiprefix;
- // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
- for (int i = 0; i < p_message.length(); i++) {
- if (p_message[i] == '\n') {
- res += fakebidisuffix;
- res += p_message[i];
- res += fakebidiprefix;
- } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += fakebidisuffix;
- res += p_message[i];
- res += p_message[i + 1];
- res += fakebidiprefix;
- i++;
- } else {
- res += p_message[i];
- }
- }
- res += fakebidisuffix;
- return res;
-}
-
-String TranslationServer::add_padding(const String &p_message, int p_length) const {
- String underscores = String("_").repeat(p_length * expansion_ratio / 2);
- String prefix = pseudolocalization_prefix + underscores;
- String suffix = underscores + pseudolocalization_suffix;
-
- return prefix + p_message + suffix;
-}
-
-const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!is_ascii_alphabet_char(p_character)) {
- return nullptr;
- }
-
- for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
- if (_character_to_accented[i].character == p_character) {
- return _character_to_accented[i].accented_character;
- }
- }
-
- return nullptr;
-}
-
-bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
- (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
- p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+ return main_domain->pseudolocalize(p_message);
}
#ifdef TOOLS_ENABLED
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
index 272fa1f11c..a09230c019 100644
--- a/core/string/translation_server.h
+++ b/core/string/translation_server.h
@@ -48,25 +48,6 @@ class TranslationServer : public Object {
bool enabled = true;
- bool pseudolocalization_enabled = false;
- bool pseudolocalization_accents_enabled = false;
- bool pseudolocalization_double_vowels_enabled = false;
- bool pseudolocalization_fake_bidi_enabled = false;
- bool pseudolocalization_override_enabled = false;
- bool pseudolocalization_skip_placeholders_enabled = false;
- float expansion_ratio = 0.0;
- String pseudolocalization_prefix;
- String pseudolocalization_suffix;
-
- StringName tool_pseudolocalize(const StringName &p_message) const;
- String get_override_string(String &p_message) const;
- String double_vowels(String &p_message) const;
- String replace_with_accented_string(String &p_message) const;
- String wrap_with_fakebidi_characters(String &p_message) const;
- String add_padding(const String &p_message, int p_length) const;
- const char32_t *get_accented_version(char32_t p_character) const;
- bool is_placeholder(String &p_message, int p_index) const;
-
static TranslationServer *singleton;
bool _load_translations(const String &p_from);
String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
@@ -93,6 +74,8 @@ class TranslationServer : public Object {
public:
_FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
+ Ref<TranslationDomain> get_editor_domain() const { return editor_domain; }
+
void set_enabled(bool p_enabled) { enabled = p_enabled; }
_FORCE_INLINE_ bool is_enabled() const { return enabled; }
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 391a203d5b..e6f7492a18 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -221,18 +221,35 @@ void CharString::copy_from(const char *p_cstr) {
/* String */
/*************************************************************************/
-Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const {
- // Splits the URL into scheme, host, port, path. Strip credentials when present.
+Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path, String &r_fragment) const {
+ // Splits the URL into scheme, host, port, path, fragment. Strip credentials when present.
String base = *this;
r_scheme = "";
r_host = "";
r_port = 0;
r_path = "";
+ r_fragment = "";
+
int pos = base.find("://");
// Scheme
if (pos != -1) {
- r_scheme = base.substr(0, pos + 3).to_lower();
- base = base.substr(pos + 3, base.length() - pos - 3);
+ bool is_scheme_valid = true;
+ for (int i = 0; i < pos; i++) {
+ if (!is_ascii_alphanumeric_char(base[i]) && base[i] != '+' && base[i] != '-' && base[i] != '.') {
+ is_scheme_valid = false;
+ break;
+ }
+ }
+ if (is_scheme_valid) {
+ r_scheme = base.substr(0, pos + 3).to_lower();
+ base = base.substr(pos + 3, base.length() - pos - 3);
+ }
+ }
+ pos = base.find("#");
+ // Fragment
+ if (pos != -1) {
+ r_fragment = base.substr(pos + 1);
+ base = base.substr(0, pos);
}
pos = base.find("/");
// Path
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 5d4b209c25..aa62c9cb18 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -452,7 +452,7 @@ public:
String c_escape_multiline() const;
String c_unescape() const;
String json_escape() const;
- Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const;
+ Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path, String &r_fragment) const;
String property_name_encode() const;
diff --git a/core/templates/hash_map.cpp b/core/templates/hash_map.cpp
new file mode 100644
index 0000000000..93664dd2e1
--- /dev/null
+++ b/core/templates/hash_map.cpp
@@ -0,0 +1,43 @@
+/**************************************************************************/
+/* hash_map.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "hash_map.h"
+
+#include "core/variant/variant.h"
+
+bool _hashmap_variant_less_than(const Variant &p_left, const Variant &p_right) {
+ bool valid = false;
+ Variant res;
+ Variant::evaluate(Variant::OP_LESS, p_left, p_right, res, valid);
+ if (!valid) {
+ res = false;
+ }
+ return res;
+}
diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h
index a3e8c2c788..329952e8d4 100644
--- a/core/templates/hash_map.h
+++ b/core/templates/hash_map.h
@@ -61,6 +61,8 @@ struct HashMapElement {
data(p_key, p_value) {}
};
+bool _hashmap_variant_less_than(const Variant &p_left, const Variant &p_right);
+
template <typename TKey, typename TValue,
typename Hasher = HashMapHasherDefault,
typename Comparator = HashMapComparatorDefault<TKey>,
@@ -271,6 +273,47 @@ public:
num_elements = 0;
}
+ void sort() {
+ if (elements == nullptr || num_elements < 2) {
+ return; // An empty or single element HashMap is already sorted.
+ }
+ // Use insertion sort because we want this operation to be fast for the
+ // common case where the input is already sorted or nearly sorted.
+ HashMapElement<TKey, TValue> *inserting = head_element->next;
+ while (inserting != nullptr) {
+ HashMapElement<TKey, TValue> *after = nullptr;
+ for (HashMapElement<TKey, TValue> *current = inserting->prev; current != nullptr; current = current->prev) {
+ if (_hashmap_variant_less_than(inserting->data.key, current->data.key)) {
+ after = current;
+ } else {
+ break;
+ }
+ }
+ HashMapElement<TKey, TValue> *next = inserting->next;
+ if (after != nullptr) {
+ // Modify the elements around `inserting` to remove it from its current position.
+ inserting->prev->next = next;
+ if (next == nullptr) {
+ tail_element = inserting->prev;
+ } else {
+ next->prev = inserting->prev;
+ }
+ // Modify `before` and `after` to insert `inserting` between them.
+ HashMapElement<TKey, TValue> *before = after->prev;
+ if (before == nullptr) {
+ head_element = inserting;
+ } else {
+ before->next = inserting;
+ }
+ after->prev = inserting;
+ // Point `inserting` to its new surroundings.
+ inserting->prev = before;
+ inserting->next = after;
+ }
+ inserting = next;
+ }
+ }
+
TValue &get(const TKey &p_key) {
uint32_t pos = 0;
bool exists = _lookup_pos(p_key, pos);
diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h
index fc7a78bcf5..21eef10297 100644
--- a/core/templates/hashfuncs.h
+++ b/core/templates/hashfuncs.h
@@ -32,10 +32,17 @@
#define HASHFUNCS_H
#include "core/math/aabb.h"
+#include "core/math/basis.h"
+#include "core/math/color.h"
#include "core/math/math_defs.h"
#include "core/math/math_funcs.h"
+#include "core/math/plane.h"
+#include "core/math/projection.h"
+#include "core/math/quaternion.h"
#include "core/math/rect2.h"
#include "core/math/rect2i.h"
+#include "core/math/transform_2d.h"
+#include "core/math/transform_3d.h"
#include "core/math/vector2.h"
#include "core/math/vector2i.h"
#include "core/math/vector3.h"
@@ -414,6 +421,13 @@ struct HashMapComparatorDefault<double> {
};
template <>
+struct HashMapComparatorDefault<Color> {
+ static bool compare(const Color &p_lhs, const Color &p_rhs) {
+ return ((p_lhs.r == p_rhs.r) || (Math::is_nan(p_lhs.r) && Math::is_nan(p_rhs.r))) && ((p_lhs.g == p_rhs.g) || (Math::is_nan(p_lhs.g) && Math::is_nan(p_rhs.g))) && ((p_lhs.b == p_rhs.b) || (Math::is_nan(p_lhs.b) && Math::is_nan(p_rhs.b))) && ((p_lhs.a == p_rhs.a) || (Math::is_nan(p_lhs.a) && Math::is_nan(p_rhs.a)));
+ }
+};
+
+template <>
struct HashMapComparatorDefault<Vector2> {
static bool compare(const Vector2 &p_lhs, const Vector2 &p_rhs) {
return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y)));
@@ -427,6 +441,87 @@ struct HashMapComparatorDefault<Vector3> {
}
};
+template <>
+struct HashMapComparatorDefault<Vector4> {
+ static bool compare(const Vector4 &p_lhs, const Vector4 &p_rhs) {
+ return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))) && ((p_lhs.z == p_rhs.z) || (Math::is_nan(p_lhs.z) && Math::is_nan(p_rhs.z))) && ((p_lhs.w == p_rhs.w) || (Math::is_nan(p_lhs.w) && Math::is_nan(p_rhs.w)));
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Rect2> {
+ static bool compare(const Rect2 &p_lhs, const Rect2 &p_rhs) {
+ return HashMapComparatorDefault<Vector2>().compare(p_lhs.position, p_rhs.position) && HashMapComparatorDefault<Vector2>().compare(p_lhs.size, p_rhs.size);
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<AABB> {
+ static bool compare(const AABB &p_lhs, const AABB &p_rhs) {
+ return HashMapComparatorDefault<Vector3>().compare(p_lhs.position, p_rhs.position) && HashMapComparatorDefault<Vector3>().compare(p_lhs.size, p_rhs.size);
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Plane> {
+ static bool compare(const Plane &p_lhs, const Plane &p_rhs) {
+ return HashMapComparatorDefault<Vector3>().compare(p_lhs.normal, p_rhs.normal) && ((p_lhs.d == p_rhs.d) || (Math::is_nan(p_lhs.d) && Math::is_nan(p_rhs.d)));
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Transform2D> {
+ static bool compare(const Transform2D &p_lhs, const Transform2D &p_rhs) {
+ for (int i = 0; i < 3; ++i) {
+ if (!HashMapComparatorDefault<Vector2>().compare(p_lhs.columns[i], p_rhs.columns[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Basis> {
+ static bool compare(const Basis &p_lhs, const Basis &p_rhs) {
+ for (int i = 0; i < 3; ++i) {
+ if (!HashMapComparatorDefault<Vector3>().compare(p_lhs.rows[i], p_rhs.rows[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Transform3D> {
+ static bool compare(const Transform3D &p_lhs, const Transform3D &p_rhs) {
+ return HashMapComparatorDefault<Basis>().compare(p_lhs.basis, p_rhs.basis) && HashMapComparatorDefault<Vector3>().compare(p_lhs.origin, p_rhs.origin);
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Projection> {
+ static bool compare(const Projection &p_lhs, const Projection &p_rhs) {
+ for (int i = 0; i < 4; ++i) {
+ if (!HashMapComparatorDefault<Vector4>().compare(p_lhs.columns[i], p_rhs.columns[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct HashMapComparatorDefault<Quaternion> {
+ static bool compare(const Quaternion &p_lhs, const Quaternion &p_rhs) {
+ return ((p_lhs.x == p_rhs.x) || (Math::is_nan(p_lhs.x) && Math::is_nan(p_rhs.x))) && ((p_lhs.y == p_rhs.y) || (Math::is_nan(p_lhs.y) && Math::is_nan(p_rhs.y))) && ((p_lhs.z == p_rhs.z) || (Math::is_nan(p_lhs.z) && Math::is_nan(p_rhs.z))) && ((p_lhs.w == p_rhs.w) || (Math::is_nan(p_lhs.w) && Math::is_nan(p_rhs.w)));
+ }
+};
+
constexpr uint32_t HASH_TABLE_SIZE_MAX = 29;
inline constexpr uint32_t hash_table_size_primes[HASH_TABLE_SIZE_MAX] = {
diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h
index 537413e2ba..4200159054 100644
--- a/core/templates/rid_owner.h
+++ b/core/templates/rid_owner.h
@@ -32,7 +32,7 @@
#define RID_OWNER_H
#include "core/os/memory.h"
-#include "core/os/spin_lock.h"
+#include "core/os/mutex.h"
#include "core/string/print_string.h"
#include "core/templates/hash_set.h"
#include "core/templates/list.h"
@@ -69,42 +69,54 @@ public:
template <typename T, bool THREAD_SAFE = false>
class RID_Alloc : public RID_AllocBase {
- T **chunks = nullptr;
+ struct Chunk {
+ T data;
+ uint32_t validator;
+ };
+ Chunk **chunks = nullptr;
uint32_t **free_list_chunks = nullptr;
- uint32_t **validator_chunks = nullptr;
uint32_t elements_in_chunk;
uint32_t max_alloc = 0;
uint32_t alloc_count = 0;
+ uint32_t chunk_limit = 0;
const char *description = nullptr;
- mutable SpinLock spin_lock;
+ mutable Mutex mutex;
_FORCE_INLINE_ RID _allocate_rid() {
if constexpr (THREAD_SAFE) {
- spin_lock.lock();
+ mutex.lock();
}
if (alloc_count == max_alloc) {
//allocate a new chunk
uint32_t chunk_count = alloc_count == 0 ? 0 : (max_alloc / elements_in_chunk);
+ if (THREAD_SAFE && chunk_count == chunk_limit) {
+ mutex.unlock();
+ if (description != nullptr) {
+ ERR_FAIL_V_MSG(RID(), vformat("Element limit for RID of type '%s' reached.", String(description)));
+ } else {
+ ERR_FAIL_V_MSG(RID(), "Element limit reached.");
+ }
+ }
//grow chunks
- chunks = (T **)memrealloc(chunks, sizeof(T *) * (chunk_count + 1));
- chunks[chunk_count] = (T *)memalloc(sizeof(T) * elements_in_chunk); //but don't initialize
-
- //grow validators
- validator_chunks = (uint32_t **)memrealloc(validator_chunks, sizeof(uint32_t *) * (chunk_count + 1));
- validator_chunks[chunk_count] = (uint32_t *)memalloc(sizeof(uint32_t) * elements_in_chunk);
+ if constexpr (!THREAD_SAFE) {
+ chunks = (Chunk **)memrealloc(chunks, sizeof(Chunk *) * (chunk_count + 1));
+ }
+ chunks[chunk_count] = (Chunk *)memalloc(sizeof(Chunk) * elements_in_chunk); //but don't initialize
//grow free lists
- free_list_chunks = (uint32_t **)memrealloc(free_list_chunks, sizeof(uint32_t *) * (chunk_count + 1));
+ if constexpr (!THREAD_SAFE) {
+ free_list_chunks = (uint32_t **)memrealloc(free_list_chunks, sizeof(uint32_t *) * (chunk_count + 1));
+ }
free_list_chunks[chunk_count] = (uint32_t *)memalloc(sizeof(uint32_t) * elements_in_chunk);
//initialize
for (uint32_t i = 0; i < elements_in_chunk; i++) {
// Don't initialize chunk.
- validator_chunks[chunk_count][i] = 0xFFFFFFFF;
+ chunks[chunk_count][i].validator = 0xFFFFFFFF;
free_list_chunks[chunk_count][i] = alloc_count + i;
}
@@ -122,14 +134,13 @@ class RID_Alloc : public RID_AllocBase {
id <<= 32;
id |= free_index;
- validator_chunks[free_chunk][free_element] = validator;
-
- validator_chunks[free_chunk][free_element] |= 0x80000000; //mark uninitialized bit
+ chunks[free_chunk][free_element].validator = validator;
+ chunks[free_chunk][free_element].validator |= 0x80000000; //mark uninitialized bit
alloc_count++;
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
return _make_from_id(id);
@@ -156,16 +167,10 @@ public:
if (p_rid == RID()) {
return nullptr;
}
- if constexpr (THREAD_SAFE) {
- spin_lock.lock();
- }
uint64_t id = p_rid.get_id();
uint32_t idx = uint32_t(id & 0xFFFFFFFF);
if (unlikely(idx >= max_alloc)) {
- if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
- }
return nullptr;
}
@@ -174,38 +179,26 @@ public:
uint32_t validator = uint32_t(id >> 32);
+ Chunk &c = chunks[idx_chunk][idx_element];
if (unlikely(p_initialize)) {
- if (unlikely(!(validator_chunks[idx_chunk][idx_element] & 0x80000000))) {
- if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
- }
+ if (unlikely(!(c.validator & 0x80000000))) {
ERR_FAIL_V_MSG(nullptr, "Initializing already initialized RID");
}
- if (unlikely((validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) != validator)) {
- if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
- }
+ if (unlikely((c.validator & 0x7FFFFFFF) != validator)) {
ERR_FAIL_V_MSG(nullptr, "Attempting to initialize the wrong RID");
}
- validator_chunks[idx_chunk][idx_element] &= 0x7FFFFFFF; //initialized
+ c.validator &= 0x7FFFFFFF; //initialized
- } else if (unlikely(validator_chunks[idx_chunk][idx_element] != validator)) {
- if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
- }
- if ((validator_chunks[idx_chunk][idx_element] & 0x80000000) && validator_chunks[idx_chunk][idx_element] != 0xFFFFFFFF) {
+ } else if (unlikely(c.validator != validator)) {
+ if ((c.validator & 0x80000000) && c.validator != 0xFFFFFFFF) {
ERR_FAIL_V_MSG(nullptr, "Attempting to use an uninitialized RID");
}
return nullptr;
}
- T *ptr = &chunks[idx_chunk][idx_element];
-
- if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
- }
+ T *ptr = &c.data;
return ptr;
}
@@ -222,14 +215,14 @@ public:
_FORCE_INLINE_ bool owns(const RID &p_rid) const {
if constexpr (THREAD_SAFE) {
- spin_lock.lock();
+ mutex.lock();
}
uint64_t id = p_rid.get_id();
uint32_t idx = uint32_t(id & 0xFFFFFFFF);
if (unlikely(idx >= max_alloc)) {
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
return false;
}
@@ -239,10 +232,10 @@ public:
uint32_t validator = uint32_t(id >> 32);
- bool owned = (validator != 0x7FFFFFFF) && (validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) == validator;
+ bool owned = (validator != 0x7FFFFFFF) && (chunks[idx_chunk][idx_element].validator & 0x7FFFFFFF) == validator;
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
return owned;
@@ -250,14 +243,14 @@ public:
_FORCE_INLINE_ void free(const RID &p_rid) {
if constexpr (THREAD_SAFE) {
- spin_lock.lock();
+ mutex.lock();
}
uint64_t id = p_rid.get_id();
uint32_t idx = uint32_t(id & 0xFFFFFFFF);
if (unlikely(idx >= max_alloc)) {
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
ERR_FAIL();
}
@@ -266,26 +259,26 @@ public:
uint32_t idx_element = idx % elements_in_chunk;
uint32_t validator = uint32_t(id >> 32);
- if (unlikely(validator_chunks[idx_chunk][idx_element] & 0x80000000)) {
+ if (unlikely(chunks[idx_chunk][idx_element].validator & 0x80000000)) {
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
- ERR_FAIL_MSG("Attempted to free an uninitialized or invalid RID.");
- } else if (unlikely(validator_chunks[idx_chunk][idx_element] != validator)) {
+ ERR_FAIL_MSG("Attempted to free an uninitialized or invalid RID");
+ } else if (unlikely(chunks[idx_chunk][idx_element].validator != validator)) {
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
ERR_FAIL();
}
- chunks[idx_chunk][idx_element].~T();
- validator_chunks[idx_chunk][idx_element] = 0xFFFFFFFF; // go invalid
+ chunks[idx_chunk][idx_element].data.~T();
+ chunks[idx_chunk][idx_element].validator = 0xFFFFFFFF; // go invalid
alloc_count--;
free_list_chunks[alloc_count / elements_in_chunk][alloc_count % elements_in_chunk] = idx;
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
}
@@ -294,34 +287,35 @@ public:
}
void get_owned_list(List<RID> *p_owned) const {
if constexpr (THREAD_SAFE) {
- spin_lock.lock();
+ mutex.lock();
}
for (size_t i = 0; i < max_alloc; i++) {
- uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk];
+ uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
if (validator != 0xFFFFFFFF) {
p_owned->push_back(_make_from_id((validator << 32) | i));
}
}
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
}
//used for fast iteration in the elements or RIDs
void fill_owned_buffer(RID *p_rid_buffer) const {
if constexpr (THREAD_SAFE) {
- spin_lock.lock();
+ mutex.lock();
}
uint32_t idx = 0;
for (size_t i = 0; i < max_alloc; i++) {
- uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk];
+ uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
if (validator != 0xFFFFFFFF) {
p_rid_buffer[idx] = _make_from_id((validator << 32) | i);
idx++;
}
}
+
if constexpr (THREAD_SAFE) {
- spin_lock.unlock();
+ mutex.unlock();
}
}
@@ -329,8 +323,13 @@ public:
description = p_descrption;
}
- RID_Alloc(uint32_t p_target_chunk_byte_size = 65536) {
+ RID_Alloc(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) {
elements_in_chunk = sizeof(T) > p_target_chunk_byte_size ? 1 : (p_target_chunk_byte_size / sizeof(T));
+ if constexpr (THREAD_SAFE) {
+ chunk_limit = (p_maximum_number_of_elements / elements_in_chunk) + 1;
+ chunks = (Chunk **)memalloc(sizeof(Chunk *) * chunk_limit);
+ free_list_chunks = (uint32_t **)memalloc(sizeof(uint32_t *) * chunk_limit);
+ }
}
~RID_Alloc() {
@@ -339,12 +338,12 @@ public:
alloc_count, description ? description : typeid(T).name()));
for (size_t i = 0; i < max_alloc; i++) {
- uint64_t validator = validator_chunks[i / elements_in_chunk][i % elements_in_chunk];
+ uint64_t validator = chunks[i / elements_in_chunk][i % elements_in_chunk].validator;
if (validator & 0x80000000) {
continue; //uninitialized
}
if (validator != 0xFFFFFFFF) {
- chunks[i / elements_in_chunk][i % elements_in_chunk].~T();
+ chunks[i / elements_in_chunk][i % elements_in_chunk].data.~T();
}
}
}
@@ -352,14 +351,12 @@ public:
uint32_t chunk_count = max_alloc / elements_in_chunk;
for (uint32_t i = 0; i < chunk_count; i++) {
memfree(chunks[i]);
- memfree(validator_chunks[i]);
memfree(free_list_chunks[i]);
}
if (chunks) {
memfree(chunks);
memfree(free_list_chunks);
- memfree(validator_chunks);
}
}
};
@@ -419,8 +416,8 @@ public:
alloc.set_description(p_descrption);
}
- RID_PtrOwner(uint32_t p_target_chunk_byte_size = 65536) :
- alloc(p_target_chunk_byte_size) {}
+ RID_PtrOwner(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) :
+ alloc(p_target_chunk_byte_size, p_maximum_number_of_elements) {}
};
template <typename T, bool THREAD_SAFE = false>
@@ -473,8 +470,8 @@ public:
void set_description(const char *p_descrption) {
alloc.set_description(p_descrption);
}
- RID_Owner(uint32_t p_target_chunk_byte_size = 65536) :
- alloc(p_target_chunk_byte_size) {}
+ RID_Owner(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) :
+ alloc(p_target_chunk_byte_size, p_maximum_number_of_elements) {}
};
#endif // RID_OWNER_H
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index bb2d0313f6..5ce90cd8ff 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -315,31 +315,32 @@ bool Callable::operator<(const Callable &p_callable) const {
}
void Callable::operator=(const Callable &p_callable) {
+ CallableCustom *cleanup_ref = nullptr;
if (is_custom()) {
if (p_callable.is_custom()) {
if (custom == p_callable.custom) {
return;
}
}
-
- if (custom->ref_count.unref()) {
- memdelete(custom);
- custom = nullptr;
- }
+ cleanup_ref = custom;
+ custom = nullptr;
}
if (p_callable.is_custom()) {
method = StringName();
- if (!p_callable.custom->ref_count.ref()) {
- object = 0;
- } else {
- object = 0;
+ object = 0;
+ if (p_callable.custom->ref_count.ref()) {
custom = p_callable.custom;
}
} else {
method = p_callable.method;
object = p_callable.object;
}
+
+ if (cleanup_ref != nullptr && cleanup_ref->ref_count.unref()) {
+ memdelete(cleanup_ref);
+ }
+ cleanup_ref = nullptr;
}
Callable::operator String() const {
diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp
index 2db754438f..501ca69205 100644
--- a/core/variant/dictionary.cpp
+++ b/core/variant/dictionary.cpp
@@ -294,6 +294,11 @@ void Dictionary::clear() {
_p->variant_map.clear();
}
+void Dictionary::sort() {
+ ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
+ _p->variant_map.sort();
+}
+
void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) {
ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state.");
for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) {
diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h
index 5f3ce40219..bbfb5b3083 100644
--- a/core/variant/dictionary.h
+++ b/core/variant/dictionary.h
@@ -64,6 +64,7 @@ public:
int size() const;
bool is_empty() const;
void clear();
+ void sort();
void merge(const Dictionary &p_dictionary, bool p_overwrite = false);
Dictionary merged(const Dictionary &p_dictionary, bool p_overwrite = false) const;
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 186643b024..e2865a06be 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1072,17 +1072,69 @@ bool Variant::is_null() const {
}
}
+void Variant::ObjData::ref(const ObjData &p_from) {
+ // Mirrors Ref::ref in refcounted.h
+ if (p_from.id == id) {
+ return;
+ }
+
+ ObjData cleanup_ref = *this;
+
+ *this = p_from;
+ if (id.is_ref_counted()) {
+ RefCounted *reference = static_cast<RefCounted *>(obj);
+ // Assuming reference is not null because id.is_ref_counted() was true.
+ if (!reference->reference()) {
+ *this = ObjData();
+ }
+ }
+
+ cleanup_ref.unref();
+}
+
+void Variant::ObjData::ref_pointer(Object *p_object) {
+ // Mirrors Ref::ref_pointer in refcounted.h
+ if (p_object == obj) {
+ return;
+ }
+
+ ObjData cleanup_ref = *this;
+
+ if (p_object) {
+ *this = ObjData{ p_object->get_instance_id(), p_object };
+ if (p_object->is_ref_counted()) {
+ RefCounted *reference = static_cast<RefCounted *>(p_object);
+ if (!reference->init_ref()) {
+ *this = ObjData();
+ }
+ }
+ } else {
+ *this = ObjData();
+ }
+
+ cleanup_ref.unref();
+}
+
+void Variant::ObjData::unref() {
+ // Mirrors Ref::unref in refcounted.h
+ if (id.is_ref_counted()) {
+ RefCounted *reference = static_cast<RefCounted *>(obj);
+ // Assuming reference is not null because id.is_ref_counted() was true.
+ if (reference->unreference()) {
+ memdelete(reference);
+ }
+ }
+ *this = ObjData();
+}
+
void Variant::reference(const Variant &p_variant) {
- switch (type) {
- case NIL:
- case BOOL:
- case INT:
- case FLOAT:
- break;
- default:
- clear();
+ if (type == OBJECT && p_variant.type == OBJECT) {
+ _get_obj().ref(p_variant._get_obj());
+ return;
}
+ clear();
+
type = p_variant.type;
switch (p_variant.type) {
@@ -1165,18 +1217,7 @@ void Variant::reference(const Variant &p_variant) {
} break;
case OBJECT: {
memnew_placement(_data._mem, ObjData);
-
- if (p_variant._get_obj().obj && p_variant._get_obj().id.is_ref_counted()) {
- RefCounted *ref_counted = static_cast<RefCounted *>(p_variant._get_obj().obj);
- if (!ref_counted->reference()) {
- _get_obj().obj = nullptr;
- _get_obj().id = ObjectID();
- break;
- }
- }
-
- _get_obj().obj = const_cast<Object *>(p_variant._get_obj().obj);
- _get_obj().id = p_variant._get_obj().id;
+ _get_obj().ref(p_variant._get_obj());
} break;
case CALLABLE: {
memnew_placement(_data._mem, Callable(*reinterpret_cast<const Callable *>(p_variant._data._mem)));
@@ -1375,15 +1416,7 @@ void Variant::_clear_internal() {
reinterpret_cast<NodePath *>(_data._mem)->~NodePath();
} break;
case OBJECT: {
- if (_get_obj().id.is_ref_counted()) {
- // We are safe that there is a reference here.
- RefCounted *ref_counted = static_cast<RefCounted *>(_get_obj().obj);
- if (ref_counted->unreference()) {
- memdelete(ref_counted);
- }
- }
- _get_obj().obj = nullptr;
- _get_obj().id = ObjectID();
+ _get_obj().unref();
} break;
case RID: {
// Not much need probably.
@@ -2589,24 +2622,8 @@ Variant::Variant(const ::RID &p_rid) :
Variant::Variant(const Object *p_object) :
type(OBJECT) {
- memnew_placement(_data._mem, ObjData);
-
- if (p_object) {
- if (p_object->is_ref_counted()) {
- RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_object));
- if (!ref_counted->init_ref()) {
- _get_obj().obj = nullptr;
- _get_obj().id = ObjectID();
- return;
- }
- }
-
- _get_obj().obj = const_cast<Object *>(p_object);
- _get_obj().id = p_object->get_instance_id();
- } else {
- _get_obj().obj = nullptr;
- _get_obj().id = ObjectID();
- }
+ _get_obj() = ObjData();
+ _get_obj().ref_pointer(const_cast<Object *>(p_object));
}
Variant::Variant(const Callable &p_callable) :
@@ -2828,26 +2845,7 @@ void Variant::operator=(const Variant &p_variant) {
*reinterpret_cast<::RID *>(_data._mem) = *reinterpret_cast<const ::RID *>(p_variant._data._mem);
} break;
case OBJECT: {
- if (_get_obj().id.is_ref_counted()) {
- //we are safe that there is a reference here
- RefCounted *ref_counted = static_cast<RefCounted *>(_get_obj().obj);
- if (ref_counted->unreference()) {
- memdelete(ref_counted);
- }
- }
-
- if (p_variant._get_obj().obj && p_variant._get_obj().id.is_ref_counted()) {
- RefCounted *ref_counted = static_cast<RefCounted *>(p_variant._get_obj().obj);
- if (!ref_counted->reference()) {
- _get_obj().obj = nullptr;
- _get_obj().id = ObjectID();
- break;
- }
- }
-
- _get_obj().obj = const_cast<Object *>(p_variant._get_obj().obj);
- _get_obj().id = p_variant._get_obj().id;
-
+ _get_obj().ref(p_variant._get_obj());
} break;
case CALLABLE: {
*reinterpret_cast<Callable *>(_data._mem) = *reinterpret_cast<const Callable *>(p_variant._data._mem);
diff --git a/core/variant/variant.h b/core/variant/variant.h
index d4e4b330cd..c76b849abd 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -62,6 +62,10 @@
#include "core/variant/dictionary.h"
class Object;
+class RefCounted;
+
+template <typename T>
+class Ref;
struct PropertyInfo;
struct MethodInfo;
@@ -175,6 +179,20 @@ private:
struct ObjData {
ObjectID id;
Object *obj = nullptr;
+
+ void ref(const ObjData &p_from);
+ void ref_pointer(Object *p_object);
+ void ref_pointer(RefCounted *p_object);
+ void unref();
+
+ template <typename T>
+ _ALWAYS_INLINE_ void ref(const Ref<T> &p_from) {
+ if (p_from.is_valid()) {
+ ref(ObjData{ p_from->get_instance_id(), p_from.ptr() });
+ } else {
+ unref();
+ }
+ }
};
/* array helpers */
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index ab33c9db55..29e11462c9 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2272,6 +2272,7 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, is_empty, sarray(), varray());
bind_method(Dictionary, clear, sarray(), varray());
bind_method(Dictionary, assign, sarray("dictionary"), varray());
+ bind_method(Dictionary, sort, sarray(), varray());
bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, has, sarray("key"), varray());
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index fb75a874e7..6c37d5e4b7 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -323,36 +323,6 @@ String Variant::get_constructor_argument_name(Variant::Type p_type, int p_constr
return construct_data[p_type][p_constructor].arg_names[p_argument];
}
-void VariantInternal::refcounted_object_assign(Variant *v, const RefCounted *rc) {
- if (!rc || !const_cast<RefCounted *>(rc)->init_ref()) {
- v->_get_obj().obj = nullptr;
- v->_get_obj().id = ObjectID();
- return;
- }
-
- v->_get_obj().obj = const_cast<RefCounted *>(rc);
- v->_get_obj().id = rc->get_instance_id();
-}
-
-void VariantInternal::object_assign(Variant *v, const Object *o) {
- if (o) {
- if (o->is_ref_counted()) {
- RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(o));
- if (!ref_counted->init_ref()) {
- v->_get_obj().obj = nullptr;
- v->_get_obj().id = ObjectID();
- return;
- }
- }
-
- v->_get_obj().obj = const_cast<Object *>(o);
- v->_get_obj().id = o->get_instance_id();
- } else {
- v->_get_obj().obj = nullptr;
- v->_get_obj().id = ObjectID();
- }
-}
-
void Variant::get_constructor_list(Type p_type, List<MethodInfo> *r_list) {
ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX);
diff --git a/core/variant/variant_construct.h b/core/variant/variant_construct.h
index 68210a9451..f625419da7 100644
--- a/core/variant/variant_construct.h
+++ b/core/variant/variant_construct.h
@@ -156,14 +156,14 @@ public:
if (p_args[0]->get_type() == Variant::NIL) {
VariantInternal::clear(&r_ret);
VariantTypeChanger<Object *>::change(&r_ret);
- VariantInternal::object_assign_null(&r_ret);
+ VariantInternal::object_reset_data(&r_ret);
r_error.error = Callable::CallError::CALL_OK;
} else if (p_args[0]->get_type() == Variant::OBJECT) {
- VariantInternal::clear(&r_ret);
VariantTypeChanger<Object *>::change(&r_ret);
VariantInternal::object_assign(&r_ret, p_args[0]);
r_error.error = Callable::CallError::CALL_OK;
} else {
+ VariantInternal::clear(&r_ret);
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
@@ -171,7 +171,6 @@ public:
}
static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
- VariantInternal::clear(r_ret);
VariantTypeChanger<Object *>::change(r_ret);
VariantInternal::object_assign(r_ret, p_args[0]);
}
@@ -203,13 +202,13 @@ public:
VariantInternal::clear(&r_ret);
VariantTypeChanger<Object *>::change(&r_ret);
- VariantInternal::object_assign_null(&r_ret);
+ VariantInternal::object_reset_data(&r_ret);
}
static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
VariantInternal::clear(r_ret);
VariantTypeChanger<Object *>::change(r_ret);
- VariantInternal::object_assign_null(r_ret);
+ VariantInternal::object_reset_data(r_ret);
}
static void ptr_construct(void *base, const void **p_args) {
PtrConstruct<Object *>::construct(nullptr, base);
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index 58a45c0a1f..58652d26e0 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -220,7 +220,7 @@ public:
// Should be in the same order as Variant::Type for consistency.
// Those primitive and vector types don't need an `init_` method:
// Nil, bool, float, Vector2/i, Rect2/i, Vector3/i, Plane, Quat, RID.
- // Object is a special case, handled via `object_assign_null`.
+ // Object is a special case, handled via `object_reset_data`.
_FORCE_INLINE_ static void init_string(Variant *v) {
memnew_placement(v->_data._mem, String);
v->type = Variant::STRING;
@@ -319,7 +319,7 @@ public:
v->type = Variant::PACKED_VECTOR4_ARRAY;
}
_FORCE_INLINE_ static void init_object(Variant *v) {
- object_assign_null(v);
+ object_reset_data(v);
v->type = Variant::OBJECT;
}
@@ -327,19 +327,28 @@ public:
v->clear();
}
- static void object_assign(Variant *v, const Object *o); // Needs RefCounted, so it's implemented elsewhere.
- static void refcounted_object_assign(Variant *v, const RefCounted *rc);
+ _FORCE_INLINE_ static void object_assign(Variant *v, const Variant *vo) {
+ v->_get_obj().ref(vo->_get_obj());
+ }
+
+ _FORCE_INLINE_ static void object_assign(Variant *v, Object *o) {
+ v->_get_obj().ref_pointer(o);
+ }
- _FORCE_INLINE_ static void object_assign(Variant *v, const Variant *o) {
- object_assign(v, o->_get_obj().obj);
+ _FORCE_INLINE_ static void object_assign(Variant *v, const Object *o) {
+ v->_get_obj().ref_pointer(const_cast<Object *>(o));
+ }
+
+ template <typename T>
+ _FORCE_INLINE_ static void object_assign(Variant *v, const Ref<T> &r) {
+ v->_get_obj().ref(r);
}
- _FORCE_INLINE_ static void object_assign_null(Variant *v) {
- v->_get_obj().obj = nullptr;
- v->_get_obj().id = ObjectID();
+ _FORCE_INLINE_ static void object_reset_data(Variant *v) {
+ v->_get_obj() = Variant::ObjData();
}
- static void update_object_id(Variant *v) {
+ _FORCE_INLINE_ static void update_object_id(Variant *v) {
const Object *o = v->_get_obj().obj;
if (o) {
v->_get_obj().id = o->get_instance_id();
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index a86f41cd9c..55d00b6cf9 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -1209,7 +1209,7 @@
<return type="int" />
<param index="0" name="x" type="int" />
<description>
- Returns [code]-1[/code] if [param x] is negative, [code]1[/code] if [param x] is positive, and [code]0[/code] if if [param x] is zero.
+ Returns [code]-1[/code] if [param x] is negative, [code]1[/code] if [param x] is positive, and [code]0[/code] if [param x] is zero.
[codeblock]
signi(-6) # Returns -1
signi(0) # Returns 0
@@ -2933,7 +2933,15 @@
<constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint">
Hints that a string property is a password, and every character is replaced with the secret character.
</constant>
- <constant name="PROPERTY_HINT_MAX" value="39" enum="PropertyHint">
+ <constant name="PROPERTY_HINT_TOOL_BUTTON" value="39" enum="PropertyHint">
+ Hints that a [Callable] property should be displayed as a clickable button. When the button is pressed, the callable is called. The hint string specifies the button text and optionally an icon from the [code]"EditorIcons"[/code] theme type.
+ [codeblock lang=text]
+ "Click me!" - A button with the text "Click me!" and the default "Callable" icon.
+ "Click me!,ColorRect" - A button with the text "Click me!" and the "ColorRect" icon.
+ [/codeblock]
+ [b]Note:[/b] A [Callable] cannot be properly serialized and stored in a file, so it is recommended to use [constant PROPERTY_USAGE_EDITOR] instead of [constant PROPERTY_USAGE_DEFAULT].
+ </constant>
+ <constant name="PROPERTY_HINT_MAX" value="40" enum="PropertyHint">
Represents the size of the [enum PropertyHint] enum.
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">
diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml
index 887e9cda81..609d7eff39 100644
--- a/doc/classes/Animation.xml
+++ b/doc/classes/Animation.xml
@@ -34,6 +34,14 @@
<link title="Animation documentation index">$DOCS_URL/tutorials/animation/index.html</link>
</tutorials>
<methods>
+ <method name="add_marker">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="time" type="float" />
+ <description>
+ Adds a marker to this Animation.
+ </description>
+ </method>
<method name="add_track">
<return type="int" />
<param index="0" name="type" type="int" enum="Animation.TrackType" />
@@ -271,12 +279,60 @@
Returns the index of the specified track. If the track is not found, return -1.
</description>
</method>
+ <method name="get_marker_at_time" qualifiers="const">
+ <return type="StringName" />
+ <param index="0" name="time" type="float" />
+ <description>
+ Returns the name of the marker located at the given time.
+ </description>
+ </method>
+ <method name="get_marker_color" qualifiers="const">
+ <return type="Color" />
+ <param index="0" name="name" type="StringName" />
+ <description>
+ Returns the given marker's color.
+ </description>
+ </method>
+ <method name="get_marker_names" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns every marker in this Animation, sorted ascending by time.
+ </description>
+ </method>
+ <method name="get_marker_time" qualifiers="const">
+ <return type="float" />
+ <param index="0" name="name" type="StringName" />
+ <description>
+ Returns the given marker's time.
+ </description>
+ </method>
+ <method name="get_next_marker" qualifiers="const">
+ <return type="StringName" />
+ <param index="0" name="time" type="float" />
+ <description>
+ Returns the closest marker that comes after the given time. If no such marker exists, an empty string is returned.
+ </description>
+ </method>
+ <method name="get_prev_marker" qualifiers="const">
+ <return type="StringName" />
+ <param index="0" name="time" type="float" />
+ <description>
+ Returns the closest marker that comes before the given time. If no such marker exists, an empty string is returned.
+ </description>
+ </method>
<method name="get_track_count" qualifiers="const">
<return type="int" />
<description>
Returns the amount of tracks in the animation.
</description>
</method>
+ <method name="has_marker" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="name" type="StringName" />
+ <description>
+ Returns [code]true[/code] if this Animation contains a marker with the given name.
+ </description>
+ </method>
<method name="method_track_get_name" qualifiers="const">
<return type="StringName" />
<param index="0" name="track_idx" type="int" />
@@ -320,6 +376,13 @@
Returns the interpolated position value at the given time (in seconds). The [param track_idx] must be the index of a 3D position track.
</description>
</method>
+ <method name="remove_marker">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" />
+ <description>
+ Removes the marker with the given name from this Animation.
+ </description>
+ </method>
<method name="remove_track">
<return type="void" />
<param index="0" name="track_idx" type="int" />
@@ -363,6 +426,14 @@
Returns the interpolated scale value at the given time (in seconds). The [param track_idx] must be the index of a 3D scale track.
</description>
</method>
+ <method name="set_marker_color">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="color" type="Color" />
+ <description>
+ Sets the given marker's color.
+ </description>
+ </method>
<method name="track_find_key" qualifiers="const">
<return type="int" />
<param index="0" name="track_idx" type="int" />
diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml
index 1ca8ac2fa5..9aeb4b7162 100644
--- a/doc/classes/AnimationPlayer.xml
+++ b/doc/classes/AnimationPlayer.xml
@@ -75,6 +75,24 @@
Returns the node which node path references will travel from.
</description>
</method>
+ <method name="get_section_end_time" qualifiers="const">
+ <return type="float" />
+ <description>
+ Returns the end time of the section currently being played.
+ </description>
+ </method>
+ <method name="get_section_start_time" qualifiers="const">
+ <return type="float" />
+ <description>
+ Returns the start time of the section currently being played.
+ </description>
+ </method>
+ <method name="has_section" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if an animation is currently playing with section.
+ </description>
+ </method>
<method name="is_playing" qualifiers="const">
<return type="bool" />
<description>
@@ -110,6 +128,54 @@
This method is a shorthand for [method play] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], so see its description for more information.
</description>
</method>
+ <method name="play_section">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="1" name="start_time" type="float" default="-1" />
+ <param index="2" name="end_time" type="float" default="-1" />
+ <param index="3" name="custom_blend" type="float" default="-1" />
+ <param index="4" name="custom_speed" type="float" default="1.0" />
+ <param index="5" name="from_end" type="bool" default="false" />
+ <description>
+ Plays the animation with key [param name] and the section starting from [param start_time] and ending on [param end_time]. See also [method play].
+ Setting [param start_time] to a value outside the range of the animation means the start of the animation will be used instead, and setting [param end_time] to a value outside the range of the animation means the end of the animation will be used instead. [param start_time] cannot be equal to [param end_time].
+ </description>
+ </method>
+ <method name="play_section_backwards">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="1" name="start_time" type="float" default="-1" />
+ <param index="2" name="end_time" type="float" default="-1" />
+ <param index="3" name="custom_blend" type="float" default="-1" />
+ <description>
+ Plays the animation with key [param name] and the section starting from [param start_time] and ending on [param end_time] in reverse.
+ This method is a shorthand for [method play_section] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], see its description for more information.
+ </description>
+ </method>
+ <method name="play_section_with_markers">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="1" name="start_marker" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="2" name="end_marker" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="3" name="custom_blend" type="float" default="-1" />
+ <param index="4" name="custom_speed" type="float" default="1.0" />
+ <param index="5" name="from_end" type="bool" default="false" />
+ <description>
+ Plays the animation with key [param name] and the section starting from [param start_marker] and ending on [param end_marker].
+ If the start marker is empty, the section starts from the beginning of the animation. If the end marker is empty, the section ends on the end of the animation. See also [method play].
+ </description>
+ </method>
+ <method name="play_section_with_markers_backwards">
+ <return type="void" />
+ <param index="0" name="name" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="1" name="start_marker" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="2" name="end_marker" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="3" name="custom_blend" type="float" default="-1" />
+ <description>
+ Plays the animation with key [param name] and the section starting from [param start_marker] and ending on [param end_marker] in reverse.
+ This method is a shorthand for [method play_section_with_markers] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], see its description for more information.
+ </description>
+ </method>
<method name="play_with_capture">
<return type="void" />
<param index="0" name="name" type="StringName" default="&amp;&quot;&quot;" />
@@ -139,6 +205,12 @@
[b]Note:[/b] If a looped animation is currently playing, the queued animation will never play unless the looped animation is stopped somehow.
</description>
</method>
+ <method name="reset_section">
+ <return type="void" />
+ <description>
+ Resets the current section if section is set.
+ </description>
+ </method>
<method name="seek">
<return type="void" />
<param index="0" name="seconds" type="float" />
@@ -180,6 +252,23 @@
Sets the node which node path references will travel from.
</description>
</method>
+ <method name="set_section">
+ <return type="void" />
+ <param index="0" name="start_time" type="float" default="-1" />
+ <param index="1" name="end_time" type="float" default="-1" />
+ <description>
+ Changes the start and end times of the section being played. The current playback position will be clamped within the new section. See also [method play_section].
+ </description>
+ </method>
+ <method name="set_section_with_markers">
+ <return type="void" />
+ <param index="0" name="start_marker" type="StringName" default="&amp;&quot;&quot;" />
+ <param index="1" name="end_marker" type="StringName" default="&amp;&quot;&quot;" />
+ <description>
+ Changes the start and end markers of the section being played. The current playback position will be clamped within the new section. See also [method play_section_with_markers].
+ If the argument is empty, the section uses the beginning or end of the animation. If both are empty, it means that the section is not set.
+ </description>
+ </method>
<method name="stop">
<return type="void" />
<param index="0" name="keep_state" type="bool" default="false" />
diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml
index d7770f2cd5..04ee95457c 100644
--- a/doc/classes/CPUParticles3D.xml
+++ b/doc/classes/CPUParticles3D.xml
@@ -11,6 +11,12 @@
<link title="Particle systems (3D)">$DOCS_URL/tutorials/3d/particles/index.html</link>
</tutorials>
<methods>
+ <method name="capture_aabb" qualifiers="const">
+ <return type="AABB" />
+ <description>
+ Returns the axis-aligned bounding box that contains all the particles that are active in the current frame.
+ </description>
+ </method>
<method name="convert_from_particles">
<return type="void" />
<param index="0" name="particles" type="Node" />
diff --git a/doc/classes/CameraAttributesPhysical.xml b/doc/classes/CameraAttributesPhysical.xml
index faedfee712..e2036162c7 100644
--- a/doc/classes/CameraAttributesPhysical.xml
+++ b/doc/classes/CameraAttributesPhysical.xml
@@ -25,7 +25,7 @@
The maximum luminance (in EV100) used when calculating auto exposure. When calculating scene average luminance, color values will be clamped to at least this value. This limits the auto-exposure from exposing below a certain brightness, resulting in a cut off point where the scene will remain bright.
</member>
<member name="auto_exposure_min_exposure_value" type="float" setter="set_auto_exposure_min_exposure_value" getter="get_auto_exposure_min_exposure_value" default="-8.0">
- The minimum luminance luminance (in EV100) used when calculating auto exposure. When calculating scene average luminance, color values will be clamped to at least this value. This limits the auto-exposure from exposing above a certain brightness, resulting in a cut off point where the scene will remain dark.
+ The minimum luminance (in EV100) used when calculating auto exposure. When calculating scene average luminance, color values will be clamped to at least this value. This limits the auto-exposure from exposing above a certain brightness, resulting in a cut off point where the scene will remain dark.
</member>
<member name="exposure_aperture" type="float" setter="set_aperture" getter="get_aperture" default="16.0">
Size of the aperture of the camera, measured in f-stops. An f-stop is a unitless ratio between the focal length of the camera and the diameter of the aperture. A high aperture setting will result in a smaller aperture which leads to a dimmer image and sharper focus. A low aperture results in a wide aperture which lets in more light resulting in a brighter, less-focused image. Default is appropriate for outdoors at daytime (i.e. for use with a default [DirectionalLight3D]), for indoor lighting, a value between 2 and 4 is more appropriate.
diff --git a/doc/classes/CameraFeed.xml b/doc/classes/CameraFeed.xml
index 8033c0880b..2b6db13906 100644
--- a/doc/classes/CameraFeed.xml
+++ b/doc/classes/CameraFeed.xml
@@ -45,6 +45,34 @@
[code]copy[/code] will result in FEED_YCBCR
</description>
</method>
+ <method name="set_name">
+ <return type="void" />
+ <param index="0" name="name" type="String" />
+ <description>
+ Sets the camera's name.
+ </description>
+ </method>
+ <method name="set_position">
+ <return type="void" />
+ <param index="0" name="position" type="int" enum="CameraFeed.FeedPosition" />
+ <description>
+ Sets the position of this camera.
+ </description>
+ </method>
+ <method name="set_rgb_image">
+ <return type="void" />
+ <param index="0" name="rgb_image" type="Image" />
+ <description>
+ Sets RGB image for this feed.
+ </description>
+ </method>
+ <method name="set_ycbcr_image">
+ <return type="void" />
+ <param index="0" name="ycbcr_image" type="Image" />
+ <description>
+ Sets YCbCr image for this feed.
+ </description>
+ </method>
</methods>
<members>
<member name="feed_is_active" type="bool" setter="set_active" getter="is_active" default="false">
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index 4441c3cb79..5c9b22fe4a 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -474,6 +474,12 @@
Returns the number of entries in the dictionary. Empty dictionaries ([code]{ }[/code]) always return [code]0[/code]. See also [method is_empty].
</description>
</method>
+ <method name="sort">
+ <return type="void" />
+ <description>
+ Sorts the dictionary in-place by key. This can be used to ensure dictionaries with the same contents produce equivalent results when getting the [method keys], getting the [method values], and converting to a string. This is also useful when wanting a JSON representation consistent with what is in memory, and useful for storing on a database that requires dictionaries to be sorted.
+ </description>
+ </method>
<method name="values" qualifiers="const">
<return type="Array" />
<description>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 79064a88ba..37bf265d20 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -893,6 +893,13 @@
Returns [code]true[/code] if the specified [param feature] is supported by the current [DisplayServer], [code]false[/code] otherwise.
</description>
</method>
+ <method name="has_hardware_keyboard" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if hardware keyboard is connected.
+ [b]Note:[/b] This method is implemented on Android and iOS, on other platforms this method always returns [code]true[/code].
+ </description>
+ </method>
<method name="help_set_search_callbacks">
<return type="void" />
<param index="0" name="search_callback" type="Callable" />
diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml
index 71c4ca0f9b..fb90a2a5cd 100644
--- a/doc/classes/EditorContextMenuPlugin.xml
+++ b/doc/classes/EditorContextMenuPlugin.xml
@@ -47,6 +47,24 @@
[/codeblock]
</description>
</method>
+ <method name="add_context_submenu_item">
+ <return type="void" />
+ <param index="0" name="name" type="String" />
+ <param index="1" name="menu" type="PopupMenu" />
+ <param index="2" name="icon" type="Texture2D" default="null" />
+ <description>
+ Add a submenu to the context menu of the plugin's specified slot. The submenu is not automatically handled, you need to connect to its signals yourself. Also the submenu is freed on every popup, so provide a new [PopupMenu] every time.
+ [codeblock]
+ func _popup_menu(paths):
+ var popup_menu = PopupMenu.new()
+ popup_menu.add_item("Blue")
+ popup_menu.add_item("White")
+ popup_menu.id_pressed.connect(_on_color_submenu_option)
+
+ add_context_menu_item("Set Node Color", popup_menu)
+ [/codeblock]
+ </description>
+ </method>
<method name="add_menu_shortcut">
<return type="void" />
<param index="0" name="shortcut" type="Shortcut" />
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 42e1968eb0..aa8e4b3d56 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -159,6 +159,15 @@
Return a [PackedStringArray] of additional features this preset, for the given [param platform], should have.
</description>
</method>
+ <method name="_get_export_option_visibility" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <param index="1" name="option" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns the visibility for the specified [param platform]. The default implementation returns [code]true[/code] for all options.
+ </description>
+ </method>
<method name="_get_export_option_warning" qualifiers="virtual const">
<return type="String" />
<param index="0" name="platform" type="EditorExportPlatform" />
diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml
index cfdc172fd1..6b25be490e 100644
--- a/doc/classes/EditorInspector.xml
+++ b/doc/classes/EditorInspector.xml
@@ -28,6 +28,7 @@
</method>
</methods>
<members>
+ <member name="follow_focus" type="bool" setter="set_follow_focus" getter="is_following_focus" overrides="ScrollContainer" default="true" />
<member name="horizontal_scroll_mode" type="int" setter="set_horizontal_scroll_mode" getter="get_horizontal_scroll_mode" overrides="ScrollContainer" enum="ScrollContainer.ScrollMode" default="0" />
</members>
<signals>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index 795c5c1c2f..43059db8b2 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -343,6 +343,14 @@
[/codeblock]
</description>
</method>
+ <method name="popup_quick_open">
+ <return type="void" />
+ <param index="0" name="callback" type="Callable" />
+ <param index="1" name="base_types" type="StringName[]" default="[]" />
+ <description>
+ Pops up an editor dialog for quick selecting a resource file. The [param callback] must take a single argument of type [String] which will contain the path of the selected resource or be empty if the dialog is canceled. If [param base_types] is provided, the dialog will only show resources that match these types. Only types deriving from [Resource] are supported.
+ </description>
+ </method>
<method name="reload_scene_from_path">
<return type="void" />
<param index="0" name="scene_filepath" type="String" />
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 5eb8ac6199..a5097521dc 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -714,6 +714,12 @@
If [code]true[/code], when saving a file, the editor will rename the old file to a different name, save a new file, then only remove the old file once the new file has been saved. This makes loss of data less likely to happen if the editor or operating system exits unexpectedly while saving (e.g. due to a crash or power outage).
[b]Note:[/b] On Windows, this feature can interact negatively with certain antivirus programs. In this case, you may have to set this to [code]false[/code] to prevent file locking issues.
</member>
+ <member name="filesystem/quick_open_dialog/default_display_mode" type="int" setter="" getter="">
+ If set to [code]Adaptive[/code], the dialog opens in list view or grid view depending on the requested type. If set to [code]Last Used[/code], the display mode will always open the way you last used it.
+ </member>
+ <member name="filesystem/quick_open_dialog/include_addons" type="bool" setter="" getter="">
+ If [code]true[/code], results will include files located in the [code]addons[/code] folder.
+ </member>
<member name="filesystem/tools/oidn/oidn_denoise_path" type="String" setter="" getter="">
The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url].
To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser].
@@ -772,7 +778,7 @@
Translations are provided by the community. If you spot a mistake, [url=$DOCS_URL/contributing/documentation/editor_and_docs_localization.html]contribute to editor translations on Weblate![/url]
</member>
<member name="interface/editor/editor_screen" type="int" setter="" getter="">
- The preferred monitor to display the editor.
+ The preferred monitor to display the editor. If [b]Auto[/b], the editor will remember the last screen it was displayed on across restarts.
</member>
<member name="interface/editor/expand_to_title" type="bool" setter="" getter="">
Expanding main editor window content to the title, if supported by [DisplayServer]. See [constant DisplayServer.WINDOW_FLAG_EXTEND_TO_TITLE].
@@ -826,9 +832,6 @@
<member name="interface/editor/project_manager_screen" type="int" setter="" getter="">
The preferred monitor to display the project manager.
</member>
- <member name="interface/editor/remember_window_size_and_position" type="bool" setter="" getter="">
- If [code]true[/code], the editor window will remember its size, position, and which screen it was displayed on across restarts.
- </member>
<member name="interface/editor/save_each_scene_on_quit" type="bool" setter="" getter="">
If [code]false[/code], the editor will save all scenes when confirming the [b]Save[/b] action when quitting the editor or quitting to the project list. If [code]true[/code], the editor will ask to save each scene individually.
</member>
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index de3295fbe0..1779408a4d 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -81,8 +81,9 @@
The background mode. See [enum BGMode] for possible values.
</member>
<member name="fog_aerial_perspective" type="float" setter="set_fog_aerial_perspective" getter="get_fog_aerial_perspective" default="0.0">
- If set above [code]0.0[/code] (exclusive), blends between the fog's color and the color of the background [Sky]. This has a small performance cost when set above [code]0.0[/code]. Must have [member background_mode] set to [constant BG_SKY].
+ If set above [code]0.0[/code] (exclusive), blends between the fog's color and the color of the background [Sky], as read from the radiance cubemap. This has a small performance cost when set above [code]0.0[/code]. Must have [member background_mode] set to [constant BG_SKY].
This is useful to simulate [url=https://en.wikipedia.org/wiki/Aerial_perspective]aerial perspective[/url] in large scenes with low density fog. However, it is not very useful for high-density fog, as the sky will shine through. When set to [code]1.0[/code], the fog color comes completely from the [Sky]. If set to [code]0.0[/code], aerial perspective is disabled.
+ Notice that this does not sample the [Sky] directly, but rather the radiance cubemap. The cubemap is sampled at a mipmap level depending on the depth of the rendered pixel; the farther away, the higher the resolution of the sampled mipmap. This results in the actual color being a blurred version of the sky, with more blur closer to the camera. The highest mipmap resolution is used at a depth of [member Camera3D.far].
</member>
<member name="fog_density" type="float" setter="set_fog_density" getter="get_fog_density" default="0.01">
The fog density to be used. This is demonstrated in different ways depending on the [member fog_mode] mode chosen:
diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml
index 1ae889adc1..18b8eb1d39 100644
--- a/doc/classes/FileDialog.xml
+++ b/doc/classes/FileDialog.xml
@@ -29,6 +29,12 @@
[param default_value_index] should be an index of the value in the [param values]. If [param values] is empty it should be either [code]1[/code] (checked), or [code]0[/code] (unchecked).
</description>
</method>
+ <method name="clear_filename_filter">
+ <return type="void" />
+ <description>
+ Clear the filter for file names.
+ </description>
+ </method>
<method name="clear_filters">
<return type="void" />
<description>
@@ -134,6 +140,10 @@
<member name="file_mode" type="int" setter="set_file_mode" getter="get_file_mode" enum="FileDialog.FileMode" default="4">
The dialog's open or save mode, which affects the selection behavior. See [enum FileMode].
</member>
+ <member name="filename_filter" type="String" setter="set_filename_filter" getter="get_filename_filter" default="&quot;&quot;">
+ The filter for file names (case-insensitive). When set to a non-empty string, only files that contains the substring will be shown. [member filename_filter] can be edited by the user with the filter button at the top of the file dialog.
+ See also [member filters], which should be used to restrict the file types that can be selected instead of [member filename_filter] which is meant to be set by the user.
+ </member>
<member name="filters" type="PackedStringArray" setter="set_filters" getter="get_filters" default="PackedStringArray()">
The available file type filters. Each filter string in the array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted.
</member>
@@ -154,7 +164,7 @@
<member name="size" type="Vector2i" setter="set_size" getter="get_size" overrides="Window" default="Vector2i(640, 360)" />
<member name="title" type="String" setter="set_title" getter="get_title" overrides="Window" default="&quot;Save a File&quot;" />
<member name="use_native_dialog" type="bool" setter="set_use_native_dialog" getter="get_use_native_dialog" default="false">
- If [code]true[/code], [member access] is set to [constant ACCESS_FILESYSTEM], and it is supported by the current [DisplayServer], OS native dialog will be used instead of custom one.
+ If [code]true[/code], and if supported by the current [DisplayServer], OS native dialog will be used instead of custom one.
[b]Note:[/b] On Linux and macOS, sandboxed apps always use native dialogs to access the host file system.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
[b]Note:[/b] Native dialogs are isolated from the base process, file dialog properties can't be modified once the dialog is shown.
@@ -173,6 +183,12 @@
Emitted when the user selects a file by double-clicking it or pressing the [b]OK[/b] button.
</description>
</signal>
+ <signal name="filename_filter_changed">
+ <param index="0" name="filter" type="String" />
+ <description>
+ Emitted when the filter for file names changes.
+ </description>
+ </signal>
<signal name="files_selected">
<param index="0" name="paths" type="PackedStringArray" />
<description>
@@ -237,6 +253,9 @@
<theme_item name="reload" data_type="icon" type="Texture2D">
Custom icon for the reload button.
</theme_item>
+ <theme_item name="toggle_filename_filter" data_type="icon" type="Texture2D">
+ Custom icon for the toggle button for the filter for file names.
+ </theme_item>
<theme_item name="toggle_hidden" data_type="icon" type="Texture2D">
Custom icon for the toggle hidden button.
</theme_item>
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml
index 670df10a89..3451e427f3 100644
--- a/doc/classes/GraphEdit.xml
+++ b/doc/classes/GraphEdit.xml
@@ -399,6 +399,11 @@
Emitted when this [GraphEdit] captures a [code]ui_copy[/code] action ([kbd]Ctrl + C[/kbd] by default). In general, this signal indicates that the selected [GraphElement]s should be copied.
</description>
</signal>
+ <signal name="cut_nodes_request">
+ <description>
+ Emitted when this [GraphEdit] captures a [code]ui_cut[/code] action ([kbd]Ctrl + X[/kbd] by default). In general, this signal indicates that the selected [GraphElement]s should be cut.
+ </description>
+ </signal>
<signal name="delete_nodes_request">
<param index="0" name="nodes" type="StringName[]" />
<description>
diff --git a/doc/classes/InputMap.xml b/doc/classes/InputMap.xml
index ff04040826..0abd7c6974 100644
--- a/doc/classes/InputMap.xml
+++ b/doc/classes/InputMap.xml
@@ -67,7 +67,7 @@
<method name="add_action">
<return type="void" />
<param index="0" name="action" type="StringName" />
- <param index="1" name="deadzone" type="float" default="0.5" />
+ <param index="1" name="deadzone" type="float" default="0.2" />
<description>
Adds an empty action to the [InputMap] with a configurable [param deadzone].
An [InputEvent] can then be added to this action with [method action_add_event].
diff --git a/doc/classes/ItemList.xml b/doc/classes/ItemList.xml
index c60a2ca887..fdaeb54bdf 100644
--- a/doc/classes/ItemList.xml
+++ b/doc/classes/ItemList.xml
@@ -73,6 +73,13 @@
[b]Note:[/b] The returned value is unreliable if called right after modifying the [ItemList], before it redraws in the next frame.
</description>
</method>
+ <method name="get_item_auto_translate_mode" qualifiers="const">
+ <return type="int" enum="Node.AutoTranslateMode" />
+ <param index="0" name="idx" type="int" />
+ <description>
+ Returns item's auto translate mode.
+ </description>
+ </method>
<method name="get_item_custom_bg_color" qualifiers="const">
<return type="Color" />
<param index="0" name="idx" type="int" />
@@ -230,6 +237,15 @@
[b]Note:[/b] This method does not trigger the item selection signal.
</description>
</method>
+ <method name="set_item_auto_translate_mode">
+ <return type="void" />
+ <param index="0" name="idx" type="int" />
+ <param index="1" name="mode" type="int" enum="Node.AutoTranslateMode" />
+ <description>
+ Sets the auto translate mode of the item associated with the specified index.
+ Items use [constant Node.AUTO_TRANSLATE_MODE_INHERIT] by default, which uses the same auto translate mode as the [ItemList] itself.
+ </description>
+ </method>
<method name="set_item_custom_bg_color">
<return type="void" />
<param index="0" name="idx" type="int" />
@@ -363,6 +379,9 @@
<member name="auto_height" type="bool" setter="set_auto_height" getter="has_auto_height" default="false">
If [code]true[/code], the control will automatically resize the height to fit its content.
</member>
+ <member name="auto_width" type="bool" setter="set_auto_width" getter="has_auto_width" default="false">
+ If [code]true[/code], the control will automatically resize the width to fit its content.
+ </member>
<member name="clip_contents" type="bool" setter="set_clip_contents" getter="is_clipping_contents" overrides="Control" default="true" />
<member name="fixed_column_width" type="int" setter="set_fixed_column_width" getter="get_fixed_column_width" default="0">
The width all columns will be adjusted to.
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index 41f42392de..3e0c328dcb 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -8,7 +8,7 @@
- When the [LineEdit] control is focused using the keyboard arrow keys, it will only gain focus and not enter edit mode.
- To enter edit mode, click on the control with the mouse or press the [code]ui_text_submit[/code] action (by default [kbd]Enter[/kbd] or [kbd]Kp Enter[/kbd]).
- To exit edit mode, press [code]ui_text_submit[/code] or [code]ui_cancel[/code] (by default [kbd]Escape[/kbd]) actions.
- - Check [method is_editing] and [signal editing_toggled] for more information.
+ - Check [method edit], [method unedit], [method is_editing], and [signal editing_toggled] for more information.
[b]Important:[/b]
- Focusing the [LineEdit] with [code]ui_focus_next[/code] (by default [kbd]Tab[/kbd]) or [code]ui_focus_prev[/code] (by default [kbd]Shift + Tab[/kbd]) or [method Control.grab_focus] still enters edit mode (for compatibility).
[LineEdit] features many built-in shortcuts that are always available ([kbd]Ctrl[/kbd] here maps to [kbd]Cmd[/kbd] on macOS):
@@ -75,6 +75,13 @@
Clears the current selection.
</description>
</method>
+ <method name="edit">
+ <return type="void" />
+ <description>
+ Allows entering edit mode whether the [LineEdit] is focused or not.
+ Use [method Callable.call_deferred] if you want to enter edit mode on [signal text_submitted].
+ </description>
+ </method>
<method name="get_menu" qualifiers="const">
<return type="PopupMenu" />
<description>
@@ -223,6 +230,12 @@
Selects the whole [String].
</description>
</method>
+ <method name="unedit">
+ <return type="void" />
+ <description>
+ Allows exiting edit mode while preserving focus.
+ </description>
+ </method>
</methods>
<members>
<member name="alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0">
diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml
index da41812e0b..4d487b6dc2 100644
--- a/doc/classes/PackedVector2Array.xml
+++ b/doc/classes/PackedVector2Array.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
An array specifically designed to hold [Vector2]. Packs data tightly, so it saves memory for large array sizes.
- [b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedVector3Array] versus [code]Array[Vector2][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays.
+ [b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedVector2Array] versus [code]Array[Vector2][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays.
[b]Note:[/b] Packed arrays are always passed by reference. To get a copy of an array that can be modified independently of the original array, use [method duplicate]. This is [i]not[/i] the case for built-in properties and methods. The returned packed array of these are a copies, and changing it will [i]not[/i] affect the original value. To update a built-in property you need to modify the returned array, and then assign it to the property again.
</description>
<tutorials>
diff --git a/doc/classes/Performance.xml b/doc/classes/Performance.xml
index 6bb71932dd..66078d2642 100644
--- a/doc/classes/Performance.xml
+++ b/doc/classes/Performance.xml
@@ -224,7 +224,22 @@
<constant name="NAVIGATION_OBSTACLE_COUNT" value="33" enum="Monitor">
Number of active navigation obstacles in the [NavigationServer3D].
</constant>
- <constant name="MONITOR_MAX" value="34" enum="Monitor">
+ <constant name="PIPELINE_COMPILATIONS_CANVAS" value="34" enum="Monitor">
+ Number of pipeline compilations that were triggered by the 2D canvas renderer.
+ </constant>
+ <constant name="PIPELINE_COMPILATIONS_MESH" value="35" enum="Monitor">
+ Number of pipeline compilations that were triggered by loading meshes. These compilations will show up as longer loading times the first time a user runs the game and the pipeline is required.
+ </constant>
+ <constant name="PIPELINE_COMPILATIONS_SURFACE" value="36" enum="Monitor">
+ Number of pipeline compilations that were triggered by building the surface cache before rendering the scene. These compilations will show up as a stutter when loading an scene the first time a user runs the game and the pipeline is required.
+ </constant>
+ <constant name="PIPELINE_COMPILATIONS_DRAW" value="37" enum="Monitor">
+ Number of pipeline compilations that were triggered while drawing the scene. These compilations will show up as stutters during gameplay the first time a user runs the game and the pipeline is required.
+ </constant>
+ <constant name="PIPELINE_COMPILATIONS_SPECIALIZATION" value="38" enum="Monitor">
+ Number of pipeline compilations that were triggered to optimize the current scene. These compilations are done in the background and should not cause any stutters whatsoever.
+ </constant>
+ <constant name="MONITOR_MAX" value="39" enum="Monitor">
Represents the size of the [enum Monitor] enum.
</constant>
</constants>
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 004bbe2286..d73cda7460 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -776,7 +776,7 @@
[StyleBox] for the right side of labeled separator. See [method add_separator].
</theme_item>
<theme_item name="panel" data_type="style" type="StyleBox">
- [StyleBox] for the the background panel.
+ [StyleBox] for the background panel.
</theme_item>
<theme_item name="separator" data_type="style" type="StyleBox">
[StyleBox] used for the separators. See [method add_separator].
diff --git a/doc/classes/PopupPanel.xml b/doc/classes/PopupPanel.xml
index 399e285402..b581f32686 100644
--- a/doc/classes/PopupPanel.xml
+++ b/doc/classes/PopupPanel.xml
@@ -10,7 +10,7 @@
</tutorials>
<theme_items>
<theme_item name="panel" data_type="style" type="StyleBox">
- [StyleBox] for the the background panel.
+ [StyleBox] for the background panel.
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 7b6d8d0cd3..d0f0459193 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1369,7 +1369,7 @@
macOS specific override for the shortcut to select the word currently under the caret.
</member>
<member name="input/ui_text_skip_selection_for_next_occurrence" type="Dictionary" setter="" getter="">
- If no selection is currently active with the last caret in text fields, searches for the next occurrence of the the word currently under the caret and moves the caret to the next occurrence. The action can be performed sequentially for other occurrences of the word under the last caret.
+ If no selection is currently active with the last caret in text fields, searches for the next occurrence of the word currently under the caret and moves the caret to the next occurrence. The action can be performed sequentially for other occurrences of the word under the last caret.
If a selection is currently active with the last caret in text fields, searches for the next occurrence of the selection, adds a caret, selects the next occurrence then deselects the previous selection and its associated caret. The action can be performed sequentially for other occurrences of the selection of the last caret.
The viewport is adjusted to the latest newly added caret.
[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
@@ -2863,11 +2863,6 @@
</member>
<member name="rendering/shading/overrides/force_vertex_shading" type="bool" setter="" getter="" default="false">
If [code]true[/code], forces vertex shading for all rendering. This can increase performance a lot, but also reduces quality immensely. Can be used to optimize performance on low-end mobile devices.
- [b]Note:[/b] This setting currently has no effect, as vertex shading is not implemented yet.
- </member>
- <member name="rendering/shading/overrides/force_vertex_shading.mobile" type="bool" setter="" getter="" default="true">
- Lower-end override for [member rendering/shading/overrides/force_vertex_shading] on mobile devices, due to performance concerns or driver support.
- [b]Note:[/b] This setting currently has no effect, as vertex shading is not implemented yet.
</member>
<member name="rendering/textures/canvas_textures/default_texture_filter" type="int" setter="" getter="" default="1">
The default texture filtering mode to use on [CanvasItem]s.
@@ -2905,8 +2900,8 @@
</member>
<member name="rendering/textures/vram_compression/compress_with_gpu" type="bool" setter="" getter="" default="true">
If [code]true[/code], the texture importer will utilize the GPU for compressing textures, improving the import time of large images.
- [b]Note:[/b] This setting requires either Vulkan or D3D12 available as a rendering backend.
- [b]Note:[/b] Currently this only affects BC1 and BC6H compression, which are used on Desktop and Console for fully opaque and HDR images respectively.
+ [b]Note:[/b] This only functions on a device which supports either Vulkan, D3D12, or Metal available as a rendering backend.
+ [b]Note:[/b] Currently this only affects certain compressed formats (BC1, BC4, and BC6), all of which are exclusive to desktop platforms and consoles.
</member>
<member name="rendering/textures/vram_compression/import_etc2_astc" type="bool" setter="" getter="" default="false">
If [code]true[/code], the texture importer will import VRAM-compressed textures using the Ericsson Texture Compression 2 algorithm for lower quality textures and normal maps and Adaptable Scalable Texture Compression algorithm for high quality textures (in 4×4 block size).
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index a57f6adec8..b73315219b 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -913,7 +913,7 @@
<param index="1" name="transform" type="Transform2D" />
<description>
Transforms both the current and previous stored transform for a canvas light.
- This allows transforming a light without creating a "glitch" in the interpolation, which is is particularly useful for large worlds utilizing a shifting origin.
+ This allows transforming a light without creating a "glitch" in the interpolation, which is particularly useful for large worlds utilizing a shifting origin.
</description>
</method>
<method name="canvas_occluder_polygon_create">
@@ -5687,6 +5687,39 @@
<constant name="RENDERING_INFO_VIDEO_MEM_USED" value="5" enum="RenderingInfo">
Video memory used (in bytes). When using the Forward+ or mobile rendering backends, this is always greater than the sum of [constant RENDERING_INFO_TEXTURE_MEM_USED] and [constant RENDERING_INFO_BUFFER_MEM_USED], since there is miscellaneous data not accounted for by those two metrics. When using the GL Compatibility backend, this is equal to the sum of [constant RENDERING_INFO_TEXTURE_MEM_USED] and [constant RENDERING_INFO_BUFFER_MEM_USED].
</constant>
+ <constant name="RENDERING_INFO_PIPELINE_COMPILATIONS_CANVAS" value="6" enum="RenderingInfo">
+ Number of pipeline compilations that were triggered by the 2D canvas renderer.
+ </constant>
+ <constant name="RENDERING_INFO_PIPELINE_COMPILATIONS_MESH" value="7" enum="RenderingInfo">
+ Number of pipeline compilations that were triggered by loading meshes. These compilations will show up as longer loading times the first time a user runs the game and the pipeline is required.
+ </constant>
+ <constant name="RENDERING_INFO_PIPELINE_COMPILATIONS_SURFACE" value="8" enum="RenderingInfo">
+ Number of pipeline compilations that were triggered by building the surface cache before rendering the scene. These compilations will show up as a stutter when loading an scene the first time a user runs the game and the pipeline is required.
+ </constant>
+ <constant name="RENDERING_INFO_PIPELINE_COMPILATIONS_DRAW" value="9" enum="RenderingInfo">
+ Number of pipeline compilations that were triggered while drawing the scene. These compilations will show up as stutters during gameplay the first time a user runs the game and the pipeline is required.
+ </constant>
+ <constant name="RENDERING_INFO_PIPELINE_COMPILATIONS_SPECIALIZATION" value="10" enum="RenderingInfo">
+ Number of pipeline compilations that were triggered to optimize the current scene. These compilations are done in the background and should not cause any stutters whatsoever.
+ </constant>
+ <constant name="PIPELINE_SOURCE_CANVAS" value="0" enum="PipelineSource">
+ Pipeline compilation that was triggered by the 2D canvas renderer.
+ </constant>
+ <constant name="PIPELINE_SOURCE_MESH" value="1" enum="PipelineSource">
+ Pipeline compilation that was triggered by loading a mesh.
+ </constant>
+ <constant name="PIPELINE_SOURCE_SURFACE" value="2" enum="PipelineSource">
+ Pipeline compilation that was triggered by building the surface cache before rendering the scene.
+ </constant>
+ <constant name="PIPELINE_SOURCE_DRAW" value="3" enum="PipelineSource">
+ Pipeline compilation that was triggered while drawing the scene.
+ </constant>
+ <constant name="PIPELINE_SOURCE_SPECIALIZATION" value="4" enum="PipelineSource">
+ Pipeline compilation that was triggered to optimize the current scene.
+ </constant>
+ <constant name="PIPELINE_SOURCE_MAX" value="5" enum="PipelineSource">
+ Represents the size of the [enum PipelineSource] enum.
+ </constant>
<constant name="FEATURE_SHADERS" value="0" enum="Features" deprecated="This constant has not been used since Godot 3.0.">
</constant>
<constant name="FEATURE_MULTITHREADED" value="1" enum="Features" deprecated="This constant has not been used since Godot 3.0.">
diff --git a/doc/classes/Signal.xml b/doc/classes/Signal.xml
index c970ccb094..b9ec34956f 100644
--- a/doc/classes/Signal.xml
+++ b/doc/classes/Signal.xml
@@ -4,7 +4,7 @@
A built-in type representing a signal of an [Object].
</brief_description>
<description>
- [Signal] is a built-in [Variant] type that represents a signal of an [Object] instance. Like all [Variant] types, it can be stored in variables and passed to functions. Signals allow all connected [Callable]s (and by extension their respective objects) to listen and react to events, without directly referencing one another. This keeps the code flexible and easier to manage.
+ [Signal] is a built-in [Variant] type that represents a signal of an [Object] instance. Like all [Variant] types, it can be stored in variables and passed to functions. Signals allow all connected [Callable]s (and by extension their respective objects) to listen and react to events, without directly referencing one another. This keeps the code flexible and easier to manage. You can check whether an [Object] has a given signal name using [method Object.has_signal].
In GDScript, signals can be declared with the [code]signal[/code] keyword. In C#, you may use the [code][Signal][/code] attribute on a delegate.
[codeblocks]
[gdscript]
diff --git a/doc/classes/Sprite2D.xml b/doc/classes/Sprite2D.xml
index d73cb02d94..239c4dcf09 100644
--- a/doc/classes/Sprite2D.xml
+++ b/doc/classes/Sprite2D.xml
@@ -76,7 +76,7 @@
If [code]true[/code], texture is cut from a larger atlas texture. See [member region_rect].
</member>
<member name="region_filter_clip_enabled" type="bool" setter="set_region_filter_clip_enabled" getter="is_region_filter_clip_enabled" default="false">
- If [code]true[/code], the outermost pixels get blurred out. [member region_enabled] must be [code]true[/code].
+ If [code]true[/code], the area outside of the [member region_rect] is clipped to avoid bleeding of the surrounding texture pixels. [member region_enabled] must be [code]true[/code].
</member>
<member name="region_rect" type="Rect2" setter="set_region_rect" getter="get_region_rect" default="Rect2(0, 0, 0, 0)">
The region of the atlas texture to display. [member region_enabled] must be [code]true[/code].
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index aed041c5ad..d76e65b618 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -229,6 +229,11 @@
[code]points[/code] - [PackedVector3Array], containing outline points. [code]x[/code] and [code]y[/code] are point coordinates. [code]z[/code] is the type of the point, using the [enum ContourPointTag] values.
[code]contours[/code] - [PackedInt32Array], containing indices the end points of each contour.
[code]orientation[/code] - [bool], contour orientation. If [code]true[/code], clockwise contours must be filled.
+ - Two successive [constant CONTOUR_CURVE_TAG_ON] points indicate a line segment.
+ - One [constant CONTOUR_CURVE_TAG_OFF_CONIC] point between two [constant CONTOUR_CURVE_TAG_ON] points indicates a single conic (quadratic) Bézier arc.
+ - Two [constant CONTOUR_CURVE_TAG_OFF_CUBIC] points between two [constant CONTOUR_CURVE_TAG_ON] points indicate a single cubic Bézier arc.
+ - Two successive [constant CONTOUR_CURVE_TAG_OFF_CONIC] points indicate two successive conic (quadratic) Bézier arcs with a virtual [constant CONTOUR_CURVE_TAG_ON] point at their middle.
+ - Each contour is closed. The last point of a contour uses the first point of a contour as its next point, and vice versa. The first point can be [constant CONTOUR_CURVE_TAG_OFF_CONIC] point.
</description>
</method>
<method name="font_get_glyph_index" qualifiers="const">
diff --git a/doc/classes/TranslationDomain.xml b/doc/classes/TranslationDomain.xml
index da6f2704bf..5045f86260 100644
--- a/doc/classes/TranslationDomain.xml
+++ b/doc/classes/TranslationDomain.xml
@@ -30,6 +30,13 @@
Returns the [Translation] instance that best matches [param locale]. Returns [code]null[/code] if there are no matches.
</description>
</method>
+ <method name="pseudolocalize" qualifiers="const">
+ <return type="StringName" />
+ <param index="0" name="message" type="StringName" />
+ <description>
+ Returns the pseudolocalized string based on the [param message] passed in.
+ </description>
+ </method>
<method name="remove_translation">
<return type="void" />
<param index="0" name="translation" type="Translation" />
@@ -57,4 +64,42 @@
</description>
</method>
</methods>
+ <members>
+ <member name="pseudolocalization_accents_enabled" type="bool" setter="set_pseudolocalization_accents_enabled" getter="is_pseudolocalization_accents_enabled" default="true">
+ Replace all characters with their accented variants during pseudolocalization.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_double_vowels_enabled" type="bool" setter="set_pseudolocalization_double_vowels_enabled" getter="is_pseudolocalization_double_vowels_enabled" default="false">
+ Double vowels in strings during pseudolocalization to simulate the lengthening of text due to localization.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_enabled" type="bool" setter="set_pseudolocalization_enabled" getter="is_pseudolocalization_enabled" default="false">
+ If [code]true[/code], enables pseudolocalization for the project. This can be used to spot untranslatable strings or layout issues that may occur once the project is localized to languages that have longer strings than the source language.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_expansion_ratio" type="float" setter="set_pseudolocalization_expansion_ratio" getter="get_pseudolocalization_expansion_ratio" default="0.0">
+ The expansion ratio to use during pseudolocalization. A value of [code]0.3[/code] is sufficient for most practical purposes, and will increase the length of each string by 30%.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_fake_bidi_enabled" type="bool" setter="set_pseudolocalization_fake_bidi_enabled" getter="is_pseudolocalization_fake_bidi_enabled" default="false">
+ If [code]true[/code], emulate bidirectional (right-to-left) text when pseudolocalization is enabled. This can be used to spot issues with RTL layout and UI mirroring that will crop up if the project is localized to RTL languages such as Arabic or Hebrew.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_override_enabled" type="bool" setter="set_pseudolocalization_override_enabled" getter="is_pseudolocalization_override_enabled" default="false">
+ Replace all characters in the string with [code]*[/code]. Useful for finding non-localizable strings.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_prefix" type="String" setter="set_pseudolocalization_prefix" getter="get_pseudolocalization_prefix" default="&quot;[&quot;">
+ Prefix that will be prepended to the pseudolocalized string.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_skip_placeholders_enabled" type="bool" setter="set_pseudolocalization_skip_placeholders_enabled" getter="is_pseudolocalization_skip_placeholders_enabled" default="true">
+ Skip placeholders for string formatting like [code]%s[/code] or [code]%f[/code] during pseudolocalization. Useful to identify strings which need additional control characters to display correctly.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ <member name="pseudolocalization_suffix" type="String" setter="set_pseudolocalization_suffix" getter="get_pseudolocalization_suffix" default="&quot;]&quot;">
+ Suffix that will be appended to the pseudolocalized string.
+ [b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
+ </member>
+ </members>
</class>
diff --git a/doc/classes/TranslationServer.xml b/doc/classes/TranslationServer.xml
index 0a4965c36c..69ca984f67 100644
--- a/doc/classes/TranslationServer.xml
+++ b/doc/classes/TranslationServer.xml
@@ -125,12 +125,13 @@
<param index="0" name="message" type="StringName" />
<description>
Returns the pseudolocalized string based on the [param message] passed in.
+ [b]Note:[/b] This method always uses the main translation domain.
</description>
</method>
<method name="reload_pseudolocalization">
<return type="void" />
<description>
- Reparses the pseudolocalization options and reloads the translation.
+ Reparses the pseudolocalization options and reloads the translation for the main translation domain.
</description>
</method>
<method name="remove_domain">
@@ -187,7 +188,7 @@
</methods>
<members>
<member name="pseudolocalization_enabled" type="bool" setter="set_pseudolocalization_enabled" getter="is_pseudolocalization_enabled" default="false">
- If [code]true[/code], enables the use of pseudolocalization. See [member ProjectSettings.internationalization/pseudolocalization/use_pseudolocalization] for details.
+ If [code]true[/code], enables the use of pseudolocalization on the main translation domain. See [member ProjectSettings.internationalization/pseudolocalization/use_pseudolocalization] for details.
</member>
</members>
</class>
diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml
index 861a53aaad..132ecc3f92 100644
--- a/doc/classes/TreeItem.xml
+++ b/doc/classes/TreeItem.xml
@@ -73,6 +73,13 @@
Removes the button at index [param button_index] in column [param column].
</description>
</method>
+ <method name="get_auto_translate_mode" qualifiers="const">
+ <return type="int" enum="Node.AutoTranslateMode" />
+ <param index="0" name="column" type="int" />
+ <description>
+ Returns the column's auto translate mode.
+ </description>
+ </method>
<method name="get_autowrap_mode" qualifiers="const">
<return type="int" enum="TextServer.AutowrapMode" />
<param index="0" name="column" type="int" />
@@ -493,6 +500,15 @@
Selects the given [param column].
</description>
</method>
+ <method name="set_auto_translate_mode">
+ <return type="void" />
+ <param index="0" name="column" type="int" />
+ <param index="1" name="mode" type="int" enum="Node.AutoTranslateMode" />
+ <description>
+ Sets the given column's auto translate mode to [param mode].
+ All columns use [constant Node.AUTO_TRANSLATE_MODE_INHERIT] by default, which uses the same auto translate mode as the [Tree] itself.
+ </description>
+ </method>
<method name="set_autowrap_mode">
<return type="void" />
<param index="0" name="column" type="int" />
diff --git a/doc/classes/Vector4i.xml b/doc/classes/Vector4i.xml
index 8f54b767e0..b351f2ccb6 100644
--- a/doc/classes/Vector4i.xml
+++ b/doc/classes/Vector4i.xml
@@ -216,7 +216,7 @@
<return type="Vector4i" />
<param index="0" name="right" type="int" />
<description>
- Gets the remainder of each component of the [Vector4i] with the the given [int]. This operation uses truncated division, which is often not desired as it does not work well with negative numbers. Consider using [method @GlobalScope.posmod] instead if you want to handle negative numbers.
+ Gets the remainder of each component of the [Vector4i] with the given [int]. This operation uses truncated division, which is often not desired as it does not work well with negative numbers. Consider using [method @GlobalScope.posmod] instead if you want to handle negative numbers.
[codeblock]
print(Vector4i(10, -20, 30, -40) % 7) # Prints "(3, -6, 2, -5)"
[/codeblock]
diff --git a/drivers/SCsub b/drivers/SCsub
index 219c4451ee..e0bfa138f5 100644
--- a/drivers/SCsub
+++ b/drivers/SCsub
@@ -1,6 +1,8 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
+from methods import print_error
+
Import("env")
env.drivers_sources = []
@@ -20,7 +22,7 @@ if env["platform"] == "windows":
SConscript("backtrace/SCsub")
if env["xaudio2"]:
if "xaudio2" not in supported:
- print("Target platform '{}' does not support the XAudio2 audio driver. Aborting.".format(env["platform"]))
+ print_error("Target platform '{}' does not support the XAudio2 audio driver".format(env["platform"]))
Exit(255)
SConscript("xaudio2/SCsub")
@@ -34,7 +36,7 @@ if env["vulkan"]:
SConscript("vulkan/SCsub")
if env["d3d12"]:
if "d3d12" not in supported:
- print("Target platform '{}' does not support the D3D12 rendering driver. Aborting.".format(env["platform"]))
+ print_error("Target platform '{}' does not support the D3D12 rendering driver".format(env["platform"]))
Exit(255)
SConscript("d3d12/SCsub")
if env["opengl3"]:
@@ -43,7 +45,7 @@ if env["opengl3"]:
SConscript("egl/SCsub")
if env["metal"]:
if "metal" not in supported:
- print("Target platform '{}' does not support the Metal rendering driver. Aborting.".format(env["platform"]))
+ print_error("Target platform '{}' does not support the Metal rendering driver".format(env["platform"]))
Exit(255)
SConscript("metal/SCsub")
diff --git a/drivers/d3d12/SCsub b/drivers/d3d12/SCsub
index b6ceed23ac..beeb13398e 100644
--- a/drivers/d3d12/SCsub
+++ b/drivers/d3d12/SCsub
@@ -4,6 +4,8 @@ from misc.utility.scons_hints import *
import os
from pathlib import Path
+import methods
+
Import("env")
env_d3d12_rdd = env.Clone()
@@ -139,6 +141,10 @@ else:
extra_defines += [
"HAVE_STRUCT_TIMESPEC",
]
+ if methods.using_gcc(env) and methods.get_compiler_version(env)["major"] < 13:
+ # `region` & `endregion` not recognized as valid pragmas.
+ env_d3d12_rdd.Append(CCFLAGS=["-Wno-unknown-pragmas"])
+ env.Append(CCFLAGS=["-Wno-unknown-pragmas"])
# This is needed since rendering_device_d3d12.cpp needs to include some Mesa internals.
env_d3d12_rdd.Prepend(CPPPATH=mesa_private_inc_paths)
diff --git a/drivers/d3d12/dxil_hash.cpp b/drivers/d3d12/dxil_hash.cpp
index f94a4a30df..e08492c9ea 100644
--- a/drivers/d3d12/dxil_hash.cpp
+++ b/drivers/d3d12/dxil_hash.cpp
@@ -96,7 +96,7 @@ void compute_dxil_hash(const BYTE *pData, UINT byteCount, BYTE *pOutHash) {
UINT NextEndState = bTwoRowsPadding ? N - 2 : N - 1;
const BYTE *pCurrData = pData;
for (UINT i = 0; i < N; i++, offset += 64, pCurrData += 64) {
- UINT x[16];
+ UINT x[16] = {};
const UINT *pX;
if (i == NextEndState) {
if (!bTwoRowsPadding && i == N - 1) {
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index 52883de45e..0ef88e7d52 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -481,44 +481,6 @@ void RenderingDeviceDriverD3D12::_debug_message_func(D3D12_MESSAGE_CATEGORY p_ca
}
}
-/****************/
-/**** MEMORY ****/
-/****************/
-
-static const uint32_t SMALL_ALLOCATION_MAX_SIZE = 4096;
-
-#ifdef USE_SMALL_ALLOCS_POOL
-D3D12MA::Pool *RenderingDeviceDriverD3D12::_find_or_create_small_allocs_pool(D3D12_HEAP_TYPE p_heap_type, D3D12_HEAP_FLAGS p_heap_flags) {
- D3D12_HEAP_FLAGS effective_heap_flags = p_heap_flags;
- if (allocator->GetD3D12Options().ResourceHeapTier != D3D12_RESOURCE_HEAP_TIER_1) {
- // Heap tier 2 allows mixing resource types liberally.
- effective_heap_flags &= ~(D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS | D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES | D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES);
- }
-
- AllocPoolKey pool_key;
- pool_key.heap_type = p_heap_type;
- pool_key.heap_flags = effective_heap_flags;
- if (small_allocs_pools.has(pool_key.key)) {
- return small_allocs_pools[pool_key.key].Get();
- }
-
-#ifdef DEV_ENABLED
- print_verbose("Creating D3D12MA small objects pool for heap type " + itos(p_heap_type) + " and heap flags " + itos(p_heap_flags));
-#endif
-
- D3D12MA::POOL_DESC poolDesc = {};
- poolDesc.HeapProperties.Type = p_heap_type;
- poolDesc.HeapFlags = effective_heap_flags;
-
- ComPtr<D3D12MA::Pool> pool;
- HRESULT res = allocator->CreatePool(&poolDesc, pool.GetAddressOf());
- small_allocs_pools[pool_key.key] = pool; // Don't try to create it again if failed the first time.
- ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), nullptr, "CreatePool failed with error " + vformat("0x%08ux", (uint64_t)res) + ".");
-
- return pool.Get();
-}
-#endif
-
/******************/
/**** RESOURCE ****/
/******************/
@@ -533,13 +495,9 @@ static const D3D12_RESOURCE_DIMENSION RD_TEXTURE_TYPE_TO_D3D12_RESOURCE_DIMENSIO
D3D12_RESOURCE_DIMENSION_TEXTURE2D,
};
-void RenderingDeviceDriverD3D12::_resource_transition_batch(ResourceInfo *p_resource, uint32_t p_subresource, uint32_t p_num_planes, D3D12_RESOURCE_STATES p_new_state) {
+void RenderingDeviceDriverD3D12::_resource_transition_batch(CommandBufferInfo *p_command_buffer, ResourceInfo *p_resource, uint32_t p_subresource, uint32_t p_num_planes, D3D12_RESOURCE_STATES p_new_state) {
DEV_ASSERT(p_subresource != UINT32_MAX); // We don't support an "all-resources" command here.
-#ifdef DEBUG_COUNT_BARRIERS
- uint64_t start = OS::get_singleton()->get_ticks_usec();
-#endif
-
ResourceInfo::States *res_states = p_resource->states_ptr;
D3D12_RESOURCE_STATES *curr_state = &res_states->subresource_states[p_subresource];
@@ -549,21 +507,21 @@ void RenderingDeviceDriverD3D12::_resource_transition_batch(ResourceInfo *p_reso
bool redundant_transition = any_state_is_common ? *curr_state == p_new_state : ((*curr_state) & p_new_state) == p_new_state;
if (redundant_transition) {
bool just_written = *curr_state == D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
- bool needs_uav_barrier = just_written && res_states->last_batch_with_uav_barrier != res_barriers_batch;
+ bool needs_uav_barrier = just_written && res_states->last_batch_with_uav_barrier != p_command_buffer->res_barriers_batch;
if (needs_uav_barrier) {
- if (res_barriers.size() < res_barriers_count + 1) {
- res_barriers.resize(res_barriers_count + 1);
+ if (p_command_buffer->res_barriers.size() < p_command_buffer->res_barriers_count + 1) {
+ p_command_buffer->res_barriers.resize(p_command_buffer->res_barriers_count + 1);
}
- res_barriers[res_barriers_count] = CD3DX12_RESOURCE_BARRIER::UAV(p_resource->resource);
- res_barriers_count++;
- res_states->last_batch_with_uav_barrier = res_barriers_batch;
+ p_command_buffer->res_barriers[p_command_buffer->res_barriers_count] = CD3DX12_RESOURCE_BARRIER::UAV(p_resource->resource);
+ p_command_buffer->res_barriers_count++;
+ res_states->last_batch_with_uav_barrier = p_command_buffer->res_barriers_batch;
}
} else {
uint64_t subres_mask_piece = ((uint64_t)1 << (p_subresource & 0b111111));
uint8_t subres_qword = p_subresource >> 6;
- if (res_barriers_requests.has(res_states)) {
- BarrierRequest &br = res_barriers_requests.get(res_states);
+ if (p_command_buffer->res_barriers_requests.has(res_states)) {
+ BarrierRequest &br = p_command_buffer->res_barriers_requests.get(res_states);
DEV_ASSERT(br.dx_resource == p_resource->resource);
DEV_ASSERT(br.subres_mask_qwords == STEPIFY(res_states->subresource_states.size(), 64) / 64);
DEV_ASSERT(br.planes == p_num_planes);
@@ -681,7 +639,7 @@ void RenderingDeviceDriverD3D12::_resource_transition_batch(ResourceInfo *p_reso
}
}
} else {
- BarrierRequest &br = res_barriers_requests[res_states];
+ BarrierRequest &br = p_command_buffer->res_barriers_requests[res_states];
br.dx_resource = p_resource->resource;
br.subres_mask_qwords = STEPIFY(p_resource->states_ptr->subresource_states.size(), 64) / 64;
CRASH_COND(p_resource->states_ptr->subresource_states.size() > BarrierRequest::MAX_SUBRESOURCES);
@@ -697,18 +655,10 @@ void RenderingDeviceDriverD3D12::_resource_transition_batch(ResourceInfo *p_reso
br.groups_count = 1;
}
}
-
-#ifdef DEBUG_COUNT_BARRIERS
- frame_barriers_cpu_time += OS::get_singleton()->get_ticks_usec() - start;
-#endif
}
-void RenderingDeviceDriverD3D12::_resource_transitions_flush(ID3D12GraphicsCommandList *p_cmd_list) {
-#ifdef DEBUG_COUNT_BARRIERS
- uint64_t start = OS::get_singleton()->get_ticks_usec();
-#endif
-
- for (const KeyValue<ResourceInfo::States *, BarrierRequest> &E : res_barriers_requests) {
+void RenderingDeviceDriverD3D12::_resource_transitions_flush(CommandBufferInfo *p_command_buffer) {
+ for (const KeyValue<ResourceInfo::States *, BarrierRequest> &E : p_command_buffer->res_barriers_requests) {
ResourceInfo::States *res_states = E.key;
const BarrierRequest &br = E.value;
@@ -760,22 +710,22 @@ void RenderingDeviceDriverD3D12::_resource_transitions_flush(ID3D12GraphicsComma
// Hurray!, we can do a single barrier (plus maybe a UAV one, too).
bool just_written = res_states->subresource_states[0] == D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
- bool needs_uav_barrier = just_written && res_states->last_batch_with_uav_barrier != res_barriers_batch;
+ bool needs_uav_barrier = just_written && res_states->last_batch_with_uav_barrier != p_command_buffer->res_barriers_batch;
uint32_t needed_barriers = (needs_uav_barrier ? 1 : 0) + 1;
- if (res_barriers.size() < res_barriers_count + needed_barriers) {
- res_barriers.resize(res_barriers_count + needed_barriers);
+ if (p_command_buffer->res_barriers.size() < p_command_buffer->res_barriers_count + needed_barriers) {
+ p_command_buffer->res_barriers.resize(p_command_buffer->res_barriers_count + needed_barriers);
}
if (needs_uav_barrier) {
- res_barriers[res_barriers_count] = CD3DX12_RESOURCE_BARRIER::UAV(br.dx_resource);
- res_barriers_count++;
- res_states->last_batch_with_uav_barrier = res_barriers_batch;
+ p_command_buffer->res_barriers[p_command_buffer->res_barriers_count] = CD3DX12_RESOURCE_BARRIER::UAV(br.dx_resource);
+ p_command_buffer->res_barriers_count++;
+ res_states->last_batch_with_uav_barrier = p_command_buffer->res_barriers_batch;
}
if (res_states->subresource_states[0] != br.groups[0].states) {
- res_barriers[res_barriers_count] = CD3DX12_RESOURCE_BARRIER::Transition(br.dx_resource, res_states->subresource_states[0], br.groups[0].states, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES);
- res_barriers_count++;
+ p_command_buffer->res_barriers[p_command_buffer->res_barriers_count] = CD3DX12_RESOURCE_BARRIER::Transition(br.dx_resource, res_states->subresource_states[0], br.groups[0].states, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES);
+ p_command_buffer->res_barriers_count++;
}
for (uint32_t i = 0; i < num_subresources; i++) {
@@ -811,23 +761,23 @@ void RenderingDeviceDriverD3D12::_resource_transitions_flush(ID3D12GraphicsComma
D3D12_RESOURCE_STATES *curr_state = &res_states->subresource_states[subresource];
bool just_written = *curr_state == D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
- bool needs_uav_barrier = just_written && res_states->last_batch_with_uav_barrier != res_barriers_batch;
+ bool needs_uav_barrier = just_written && res_states->last_batch_with_uav_barrier != p_command_buffer->res_barriers_batch;
uint32_t needed_barriers = (needs_uav_barrier ? 1 : 0) + br.planes;
- if (res_barriers.size() < res_barriers_count + needed_barriers) {
- res_barriers.resize(res_barriers_count + needed_barriers);
+ if (p_command_buffer->res_barriers.size() < p_command_buffer->res_barriers_count + needed_barriers) {
+ p_command_buffer->res_barriers.resize(p_command_buffer->res_barriers_count + needed_barriers);
}
if (needs_uav_barrier) {
- res_barriers[res_barriers_count] = CD3DX12_RESOURCE_BARRIER::UAV(br.dx_resource);
- res_barriers_count++;
- res_states->last_batch_with_uav_barrier = res_barriers_batch;
+ p_command_buffer->res_barriers[p_command_buffer->res_barriers_count] = CD3DX12_RESOURCE_BARRIER::UAV(br.dx_resource);
+ p_command_buffer->res_barriers_count++;
+ res_states->last_batch_with_uav_barrier = p_command_buffer->res_barriers_batch;
}
if (*curr_state != g.states) {
for (uint8_t k = 0; k < br.planes; k++) {
- res_barriers[res_barriers_count] = CD3DX12_RESOURCE_BARRIER::Transition(br.dx_resource, *curr_state, g.states, subresource + k * num_subresources);
- res_barriers_count++;
+ p_command_buffer->res_barriers[p_command_buffer->res_barriers_count] = CD3DX12_RESOURCE_BARRIER::Transition(br.dx_resource, *curr_state, g.states, subresource + k * num_subresources);
+ p_command_buffer->res_barriers_count++;
}
}
@@ -839,19 +789,13 @@ void RenderingDeviceDriverD3D12::_resource_transitions_flush(ID3D12GraphicsComma
}
}
- if (res_barriers_count) {
- p_cmd_list->ResourceBarrier(res_barriers_count, res_barriers.ptr());
- res_barriers_requests.clear();
+ if (p_command_buffer->res_barriers_count) {
+ p_command_buffer->cmd_list->ResourceBarrier(p_command_buffer->res_barriers_count, p_command_buffer->res_barriers.ptr());
+ p_command_buffer->res_barriers_requests.clear();
}
-#ifdef DEBUG_COUNT_BARRIERS
- frame_barriers_count += res_barriers_count;
- frame_barriers_batches_count++;
- frame_barriers_cpu_time += OS::get_singleton()->get_ticks_usec() - start;
-#endif
-
- res_barriers_count = 0;
- res_barriers_batch++;
+ p_command_buffer->res_barriers_count = 0;
+ p_command_buffer->res_barriers_batch++;
}
/*****************/
@@ -889,11 +833,7 @@ RDD::BufferID RenderingDeviceDriverD3D12::buffer_create(uint64_t p_size, BitFiel
}
} break;
case MEMORY_ALLOCATION_TYPE_GPU: {
-#ifdef USE_SMALL_ALLOCS_POOL
- if (p_size <= SMALL_ALLOCATION_MAX_SIZE) {
- allocation_desc.CustomPool = _find_or_create_small_allocs_pool(allocation_desc.HeapType, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS);
- }
-#endif
+ // Use default parameters.
} break;
}
@@ -1002,6 +942,7 @@ static const D3D12_UAV_DIMENSION RD_TEXTURE_TYPE_TO_D3D12_VIEW_DIMENSION_FOR_UAV
uint32_t RenderingDeviceDriverD3D12::_find_max_common_supported_sample_count(VectorView<DXGI_FORMAT> p_formats) {
uint32_t common = UINT32_MAX;
+ MutexLock lock(format_sample_counts_mask_cache_mutex);
for (uint32_t i = 0; i < p_formats.size(); i++) {
if (format_sample_counts_mask_cache.has(p_formats[i])) {
common &= format_sample_counts_mask_cache[p_formats[i]];
@@ -1292,14 +1233,6 @@ RDD::TextureID RenderingDeviceDriverD3D12::texture_create(const TextureFormat &p
allocation_desc.ExtraHeapFlags |= D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS;
}
-#ifdef USE_SMALL_ALLOCS_POOL
- uint32_t width = 0, height = 0;
- uint32_t image_size = get_image_format_required_size(p_format.format, p_format.width, p_format.height, p_format.depth, p_format.mipmaps, &width, &height);
- if (image_size <= SMALL_ALLOCATION_MAX_SIZE) {
- allocation_desc.CustomPool = _find_or_create_small_allocs_pool(allocation_desc.HeapType, allocation_desc.ExtraHeapFlags);
- }
-#endif
-
D3D12_RESOURCE_STATES initial_state = {};
ID3D12Resource *texture = nullptr;
ComPtr<ID3D12Resource> main_texture;
@@ -1415,7 +1348,14 @@ RDD::TextureID RenderingDeviceDriverD3D12::texture_create(const TextureFormat &p
}
tex_info->states_ptr = &tex_info->owner_info.states;
tex_info->format = p_format.format;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
tex_info->desc = *(CD3DX12_RESOURCE_DESC *)&resource_desc;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
tex_info->base_layer = 0;
tex_info->layers = resource_desc.ArraySize();
tex_info->base_mip = 0;
@@ -4132,6 +4072,7 @@ void RenderingDeviceDriverD3D12::command_uniform_set_prepare_for_use(CommandBuff
}
}
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
const UniformSetInfo *uniform_set_info = (const UniformSetInfo *)p_uniform_set.id;
const ShaderInfo *shader_info_in = (const ShaderInfo *)p_shader.id;
const ShaderInfo::UniformSet &shader_set = shader_info_in->sets[p_set_index];
@@ -4247,7 +4188,7 @@ void RenderingDeviceDriverD3D12::command_uniform_set_prepare_for_use(CommandBuff
if (likely(wanted_state)) {
if (sr.is_buffer) {
- _resource_transition_batch(sr.resource, 0, 1, wanted_state);
+ _resource_transition_batch(cmd_buf_info, sr.resource, 0, 1, wanted_state);
} else {
TextureInfo *tex_info = (TextureInfo *)sr.resource;
uint32_t planes = 1;
@@ -4257,7 +4198,7 @@ void RenderingDeviceDriverD3D12::command_uniform_set_prepare_for_use(CommandBuff
for (uint32_t i = 0; i < tex_info->layers; i++) {
for (uint32_t j = 0; j < tex_info->mipmaps; j++) {
uint32_t subresource = D3D12CalcSubresource(tex_info->base_mip + j, tex_info->base_layer + i, 0, tex_info->desc.MipLevels, tex_info->desc.ArraySize());
- _resource_transition_batch(tex_info, subresource, planes, wanted_state);
+ _resource_transition_batch(cmd_buf_info, tex_info, subresource, planes, wanted_state);
}
}
}
@@ -4266,8 +4207,7 @@ void RenderingDeviceDriverD3D12::command_uniform_set_prepare_for_use(CommandBuff
}
if (p_set_index == shader_info_in->sets.size() - 1) {
- CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
}
@@ -4520,7 +4460,7 @@ void RenderingDeviceDriverD3D12::_command_bind_uniform_set(CommandBufferID p_cmd
void RenderingDeviceDriverD3D12::command_clear_buffer(CommandBufferID p_cmd_buffer, BufferID p_buffer, uint64_t p_offset, uint64_t p_size) {
_command_check_descriptor_sets(p_cmd_buffer);
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
BufferInfo *buf_info = (BufferInfo *)p_buffer.id;
if (frames[frame_idx].desc_heap_walkers.resources.is_at_eof()) {
@@ -4545,8 +4485,8 @@ void RenderingDeviceDriverD3D12::command_clear_buffer(CommandBufferID p_cmd_buff
}
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(buf_info, 0, 1, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, buf_info, 0, 1, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+ _resource_transitions_flush(cmd_buf_info);
}
D3D12_UNORDERED_ACCESS_VIEW_DESC uav_desc = {};
@@ -4583,14 +4523,14 @@ void RenderingDeviceDriverD3D12::command_clear_buffer(CommandBufferID p_cmd_buff
}
void RenderingDeviceDriverD3D12::command_copy_buffer(CommandBufferID p_cmd_buffer, BufferID p_src_buffer, BufferID p_buf_locfer, VectorView<BufferCopyRegion> p_regions) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
BufferInfo *src_buf_info = (BufferInfo *)p_src_buffer.id;
BufferInfo *buf_loc_info = (BufferInfo *)p_buf_locfer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(src_buf_info, 0, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
- _resource_transition_batch(buf_loc_info, 0, 1, D3D12_RESOURCE_STATE_COPY_DEST);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, src_buf_info, 0, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ _resource_transition_batch(cmd_buf_info, buf_loc_info, 0, 1, D3D12_RESOURCE_STATE_COPY_DEST);
+ _resource_transitions_flush(cmd_buf_info);
}
for (uint32_t i = 0; i < p_regions.size(); i++) {
@@ -4599,7 +4539,7 @@ void RenderingDeviceDriverD3D12::command_copy_buffer(CommandBufferID p_cmd_buffe
}
void RenderingDeviceDriverD3D12::command_copy_texture(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, VectorView<TextureCopyRegion> p_regions) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
TextureInfo *src_tex_info = (TextureInfo *)p_src_texture.id;
TextureInfo *dst_tex_info = (TextureInfo *)p_dst_texture.id;
@@ -4610,12 +4550,12 @@ void RenderingDeviceDriverD3D12::command_copy_texture(CommandBufferID p_cmd_buff
for (uint32_t j = 0; j < layer_count; j++) {
UINT src_subresource = _compute_subresource_from_layers(src_tex_info, p_regions[i].src_subresources, j);
UINT dst_subresource = _compute_subresource_from_layers(dst_tex_info, p_regions[i].dst_subresources, j);
- _resource_transition_batch(src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
- _resource_transition_batch(dst_tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_COPY_DEST);
+ _resource_transition_batch(cmd_buf_info, src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ _resource_transition_batch(cmd_buf_info, dst_tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_COPY_DEST);
}
}
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
CD3DX12_BOX src_box;
@@ -4638,23 +4578,23 @@ void RenderingDeviceDriverD3D12::command_copy_texture(CommandBufferID p_cmd_buff
}
void RenderingDeviceDriverD3D12::command_resolve_texture(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, uint32_t p_src_layer, uint32_t p_src_mipmap, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, uint32_t p_dst_layer, uint32_t p_dst_mipmap) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
TextureInfo *src_tex_info = (TextureInfo *)p_src_texture.id;
TextureInfo *dst_tex_info = (TextureInfo *)p_dst_texture.id;
UINT src_subresource = D3D12CalcSubresource(p_src_mipmap, p_src_layer, 0, src_tex_info->desc.MipLevels, src_tex_info->desc.ArraySize());
UINT dst_subresource = D3D12CalcSubresource(p_dst_mipmap, p_dst_layer, 0, dst_tex_info->desc.MipLevels, dst_tex_info->desc.ArraySize());
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
- _resource_transition_batch(dst_tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_DEST);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
+ _resource_transition_batch(cmd_buf_info, dst_tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_DEST);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->ResolveSubresource(dst_tex_info->resource, dst_subresource, src_tex_info->resource, src_subresource, RD_TO_D3D12_FORMAT[src_tex_info->format].general_format);
}
void RenderingDeviceDriverD3D12::command_clear_color_texture(CommandBufferID p_cmd_buffer, TextureID p_texture, TextureLayout p_texture_layout, const Color &p_color, const TextureSubresourceRange &p_subresources) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
TextureInfo *tex_info = (TextureInfo *)p_texture.id;
if (tex_info->main_texture) {
tex_info = tex_info->main_texture;
@@ -4669,10 +4609,10 @@ void RenderingDeviceDriverD3D12::command_clear_color_texture(CommandBufferID p_c
0,
tex_info->desc.MipLevels,
tex_info->desc.ArraySize());
- _resource_transition_batch(tex_info, subresource, 1, p_new_state);
+ _resource_transition_batch(cmd_buf_info, tex_info, subresource, 1, p_new_state);
}
}
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
};
if ((tex_info->desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET)) {
@@ -4775,11 +4715,11 @@ void RenderingDeviceDriverD3D12::command_clear_color_texture(CommandBufferID p_c
}
void RenderingDeviceDriverD3D12::command_copy_buffer_to_texture(CommandBufferID p_cmd_buffer, BufferID p_src_buffer, TextureID p_dst_texture, TextureLayout p_dst_texture_layout, VectorView<BufferTextureCopyRegion> p_regions) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
BufferInfo *buf_info = (BufferInfo *)p_src_buffer.id;
TextureInfo *tex_info = (TextureInfo *)p_dst_texture.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(buf_info, 0, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ _resource_transition_batch(cmd_buf_info, buf_info, 0, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
}
uint32_t pixel_size = get_image_format_pixel_size(tex_info->format);
@@ -4816,10 +4756,10 @@ void RenderingDeviceDriverD3D12::command_copy_buffer_to_texture(CommandBufferID
tex_info->desc.ArraySize());
CD3DX12_TEXTURE_COPY_LOCATION copy_dst(tex_info->resource, dst_subresource);
- _resource_transition_batch(tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_COPY_DEST);
+ _resource_transition_batch(cmd_buf_info, tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_COPY_DEST);
}
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
for (uint32_t j = 0; j < p_regions[i].texture_subresources.layer_count; j++) {
@@ -4843,12 +4783,12 @@ void RenderingDeviceDriverD3D12::command_copy_buffer_to_texture(CommandBufferID
}
void RenderingDeviceDriverD3D12::command_copy_texture_to_buffer(CommandBufferID p_cmd_buffer, TextureID p_src_texture, TextureLayout p_src_texture_layout, BufferID p_buf_locfer, VectorView<BufferTextureCopyRegion> p_regions) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
TextureInfo *tex_info = (TextureInfo *)p_src_texture.id;
BufferInfo *buf_info = (BufferInfo *)p_buf_locfer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(buf_info, 0, 1, D3D12_RESOURCE_STATE_COPY_DEST);
+ _resource_transition_batch(cmd_buf_info, buf_info, 0, 1, D3D12_RESOURCE_STATE_COPY_DEST);
}
uint32_t block_w = 0, block_h = 0;
@@ -4864,10 +4804,10 @@ void RenderingDeviceDriverD3D12::command_copy_texture_to_buffer(CommandBufferID
tex_info->desc.MipLevels,
tex_info->desc.ArraySize());
- _resource_transition_batch(tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
+ _resource_transition_batch(cmd_buf_info, tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_COPY_SOURCE);
}
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
for (uint32_t j = 0; j < p_regions[i].texture_subresources.layer_count; j++) {
@@ -4910,10 +4850,9 @@ void RenderingDeviceDriverD3D12::command_copy_texture_to_buffer(CommandBufferID
/******************/
void RenderingDeviceDriverD3D12::pipeline_free(PipelineID p_pipeline) {
- ID3D12PipelineState *pso = (ID3D12PipelineState *)p_pipeline.id;
- pso->Release();
- pipelines_shaders.erase(pso);
- render_psos_extra_info.erase(pso);
+ PipelineInfo *pipeline_info = (PipelineInfo *)(p_pipeline.id);
+ pipeline_info->pso->Release();
+ memdelete(pipeline_info);
}
// ----- BINDING -----
@@ -5013,7 +4952,8 @@ void RenderingDeviceDriverD3D12::command_begin_render_pass(CommandBufferID p_cmd
0,
p_texture_info->desc.MipLevels,
p_texture_info->desc.ArraySize());
- _resource_transition_batch(p_texture_info, subresource, planes, p_states);
+
+ _resource_transition_batch(cmd_buf_info, p_texture_info, subresource, planes, p_states);
}
}
};
@@ -5035,7 +4975,7 @@ void RenderingDeviceDriverD3D12::command_begin_render_pass(CommandBufferID p_cmd
_transition_subresources(tex_info, D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE);
}
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->render_pass_state.region_rect = CD3DX12_RECT(
@@ -5109,7 +5049,7 @@ void RenderingDeviceDriverD3D12::command_begin_render_pass(CommandBufferID p_cmd
}
void RenderingDeviceDriverD3D12::_end_render_pass(CommandBufferID p_cmd_buffer) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
DEV_ASSERT(cmd_buf_info->render_pass_state.current_subpass != UINT32_MAX);
@@ -5122,7 +5062,7 @@ void RenderingDeviceDriverD3D12::_end_render_pass(CommandBufferID p_cmd_buffer)
for (uint32_t i = 0; i < fb_info->attachments.size(); i++) {
TextureInfo *src_tex_info = (TextureInfo *)(fb_info->attachments[i].id);
uint32_t src_subresource = D3D12CalcSubresource(src_tex_info->base_mip, src_tex_info->base_layer, 0, src_tex_info->desc.MipLevels, src_tex_info->desc.ArraySize());
- _resource_transition_batch(src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_PRESENT);
+ _resource_transition_batch(cmd_buf_info, src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_PRESENT);
}
}
@@ -5146,11 +5086,11 @@ void RenderingDeviceDriverD3D12::_end_render_pass(CommandBufferID p_cmd_buffer)
TextureInfo *src_tex_info = (TextureInfo *)fb_info->attachments[color_index].id;
uint32_t src_subresource = D3D12CalcSubresource(src_tex_info->base_mip, src_tex_info->base_layer, 0, src_tex_info->desc.MipLevels, src_tex_info->desc.ArraySize());
- _resource_transition_batch(src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
+ _resource_transition_batch(cmd_buf_info, src_tex_info, src_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
TextureInfo *dst_tex_info = (TextureInfo *)fb_info->attachments[resolve_index].id;
uint32_t dst_subresource = D3D12CalcSubresource(dst_tex_info->base_mip, dst_tex_info->base_layer, 0, dst_tex_info->desc.MipLevels, dst_tex_info->desc.ArraySize());
- _resource_transition_batch(dst_tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_DEST);
+ _resource_transition_batch(cmd_buf_info, dst_tex_info, dst_subresource, 1, D3D12_RESOURCE_STATE_RESOLVE_DEST);
resolves[num_resolves].src_res = src_tex_info->resource;
resolves[num_resolves].src_subres = src_subresource;
@@ -5160,7 +5100,7 @@ void RenderingDeviceDriverD3D12::_end_render_pass(CommandBufferID p_cmd_buffer)
num_resolves++;
}
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
for (uint32_t i = 0; i < num_resolves; i++) {
cmd_buf_info->cmd_list->ResolveSubresource(resolves[i].dst_res, resolves[i].dst_subres, resolves[i].src_res, resolves[i].src_subres, resolves[i].format);
@@ -5348,36 +5288,36 @@ void RenderingDeviceDriverD3D12::command_render_clear_attachments(CommandBufferI
void RenderingDeviceDriverD3D12::command_bind_render_pipeline(CommandBufferID p_cmd_buffer, PipelineID p_pipeline) {
CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
- ID3D12PipelineState *pso = (ID3D12PipelineState *)p_pipeline.id;
+ const PipelineInfo *pipeline_info = (const PipelineInfo *)p_pipeline.id;
- if (cmd_buf_info->graphics_pso == pso) {
+ if (cmd_buf_info->graphics_pso == pipeline_info->pso) {
return;
}
- const ShaderInfo *shader_info_in = pipelines_shaders[pso];
- const RenderPipelineExtraInfo &pso_extra_info = render_psos_extra_info[pso];
+ const ShaderInfo *shader_info_in = pipeline_info->shader_info;
+ const RenderPipelineInfo &render_info = pipeline_info->render_info;
- cmd_buf_info->cmd_list->SetPipelineState(pso);
+ cmd_buf_info->cmd_list->SetPipelineState(pipeline_info->pso);
if (cmd_buf_info->graphics_root_signature_crc != shader_info_in->root_signature_crc) {
cmd_buf_info->cmd_list->SetGraphicsRootSignature(shader_info_in->root_signature.Get());
cmd_buf_info->graphics_root_signature_crc = shader_info_in->root_signature_crc;
}
- cmd_buf_info->cmd_list->IASetPrimitiveTopology(pso_extra_info.dyn_params.primitive_topology);
- cmd_buf_info->cmd_list->OMSetBlendFactor(pso_extra_info.dyn_params.blend_constant.components);
- cmd_buf_info->cmd_list->OMSetStencilRef(pso_extra_info.dyn_params.stencil_reference);
+ cmd_buf_info->cmd_list->IASetPrimitiveTopology(render_info.dyn_params.primitive_topology);
+ cmd_buf_info->cmd_list->OMSetBlendFactor(render_info.dyn_params.blend_constant.components);
+ cmd_buf_info->cmd_list->OMSetStencilRef(render_info.dyn_params.stencil_reference);
if (misc_features_support.depth_bounds_supported) {
ComPtr<ID3D12GraphicsCommandList1> command_list_1;
cmd_buf_info->cmd_list->QueryInterface(command_list_1.GetAddressOf());
if (command_list_1) {
- command_list_1->OMSetDepthBounds(pso_extra_info.dyn_params.depth_bounds_min, pso_extra_info.dyn_params.depth_bounds_max);
+ command_list_1->OMSetDepthBounds(render_info.dyn_params.depth_bounds_min, render_info.dyn_params.depth_bounds_max);
}
}
- cmd_buf_info->render_pass_state.vf_info = pso_extra_info.vf_info;
+ cmd_buf_info->render_pass_state.vf_info = render_info.vf_info;
- cmd_buf_info->graphics_pso = pso;
+ cmd_buf_info->graphics_pso = pipeline_info->pso;
cmd_buf_info->compute_pso = nullptr;
}
@@ -5402,8 +5342,8 @@ void RenderingDeviceDriverD3D12::command_render_draw_indexed_indirect(CommandBuf
_bind_vertex_buffers(cmd_buf_info);
BufferInfo *indirect_buf_info = (BufferInfo *)p_indirect_buffer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->ExecuteIndirect(indirect_cmd_signatures.draw_indexed.Get(), p_draw_count, indirect_buf_info->resource, p_offset, nullptr, 0);
@@ -5415,9 +5355,9 @@ void RenderingDeviceDriverD3D12::command_render_draw_indexed_indirect_count(Comm
BufferInfo *indirect_buf_info = (BufferInfo *)p_indirect_buffer.id;
BufferInfo *count_buf_info = (BufferInfo *)p_count_buffer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transition_batch(count_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transition_batch(cmd_buf_info, count_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->ExecuteIndirect(indirect_cmd_signatures.draw_indexed.Get(), p_max_draw_count, indirect_buf_info->resource, p_offset, count_buf_info->resource, p_count_buffer_offset);
@@ -5428,8 +5368,8 @@ void RenderingDeviceDriverD3D12::command_render_draw_indirect(CommandBufferID p_
_bind_vertex_buffers(cmd_buf_info);
BufferInfo *indirect_buf_info = (BufferInfo *)p_indirect_buffer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->ExecuteIndirect(indirect_cmd_signatures.draw.Get(), p_draw_count, indirect_buf_info->resource, p_offset, nullptr, 0);
@@ -5441,9 +5381,9 @@ void RenderingDeviceDriverD3D12::command_render_draw_indirect_count(CommandBuffe
BufferInfo *indirect_buf_info = (BufferInfo *)p_indirect_buffer.id;
BufferInfo *count_buf_info = (BufferInfo *)p_count_buffer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transition_batch(count_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transition_batch(cmd_buf_info, count_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->ExecuteIndirect(indirect_cmd_signatures.draw.Get(), p_max_draw_count, indirect_buf_info->resource, p_offset, count_buf_info->resource, p_count_buffer_offset);
@@ -5465,19 +5405,19 @@ void RenderingDeviceDriverD3D12::command_render_bind_vertex_buffers(CommandBuffe
cmd_buf_info->render_pass_state.vertex_buffer_views[i].BufferLocation = buffer_info->resource->GetGPUVirtualAddress() + p_offsets[i];
cmd_buf_info->render_pass_state.vertex_buffer_views[i].SizeInBytes = buffer_info->size - p_offsets[i];
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(buffer_info, 0, 1, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
+ _resource_transition_batch(cmd_buf_info, buffer_info, 0, 1, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
}
}
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->render_pass_state.vertex_buffer_count = p_binding_count;
}
void RenderingDeviceDriverD3D12::command_render_bind_index_buffer(CommandBufferID p_cmd_buffer, BufferID p_buffer, IndexBufferFormat p_format, uint64_t p_offset) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
BufferInfo *buffer_info = (BufferInfo *)p_buffer.id;
D3D12_INDEX_BUFFER_VIEW d3d12_ib_view = {};
@@ -5486,8 +5426,8 @@ void RenderingDeviceDriverD3D12::command_render_bind_index_buffer(CommandBufferI
d3d12_ib_view.Format = p_format == INDEX_BUFFER_FORMAT_UINT16 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(buffer_info, 0, 1, D3D12_RESOURCE_STATE_INDEX_BUFFER);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, buffer_info, 0, 1, D3D12_RESOURCE_STATE_INDEX_BUFFER);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->IASetIndexBuffer(&d3d12_ib_view);
@@ -5628,9 +5568,9 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
const ShaderInfo *shader_info_in = (const ShaderInfo *)p_shader.id;
CD3DX12_PIPELINE_STATE_STREAM pipeline_desc = {};
- RenderPipelineExtraInfo pso_extra_info;
const RenderPassInfo *pass_info = (const RenderPassInfo *)p_render_pass.id;
+ RenderPipelineInfo render_info;
// Attachments.
LocalVector<uint32_t> color_attachments;
@@ -5664,7 +5604,7 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
const VertexFormatInfo *vf_info = (const VertexFormatInfo *)p_vertex_format.id;
(&pipeline_desc.InputLayout)->pInputElementDescs = vf_info->input_elem_descs.ptr();
(&pipeline_desc.InputLayout)->NumElements = vf_info->input_elem_descs.size();
- pso_extra_info.vf_info = vf_info;
+ render_info.vf_info = vf_info;
}
// Input assembly & tessellation.
@@ -5673,9 +5613,9 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
if (p_render_primitive == RENDER_PRIMITIVE_TESSELATION_PATCH) {
// Is there any way to get the true point count limit?
ERR_FAIL_COND_V(p_rasterization_state.patch_control_points < 1 || p_rasterization_state.patch_control_points > 32, PipelineID());
- pso_extra_info.dyn_params.primitive_topology = (D3D12_PRIMITIVE_TOPOLOGY)((int)D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + p_rasterization_state.patch_control_points);
+ render_info.dyn_params.primitive_topology = (D3D12_PRIMITIVE_TOPOLOGY)((int)D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + p_rasterization_state.patch_control_points);
} else {
- pso_extra_info.dyn_params.primitive_topology = RD_PRIMITIVE_TO_D3D12_TOPOLOGY[p_render_primitive];
+ render_info.dyn_params.primitive_topology = RD_PRIMITIVE_TO_D3D12_TOPOLOGY[p_render_primitive];
}
if (p_render_primitive == RENDER_PRIMITIVE_TRIANGLE_STRIPS_WITH_RESTART_INDEX) {
// TODO: This is right for 16-bit indices; for 32-bit there's a different enum value to set, but we don't know at this point.
@@ -5763,15 +5703,15 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
(&pipeline_desc.DepthStencilState)->BackFace.StencilFunc = RD_TO_D3D12_COMPARE_OP[p_depth_stencil_state.back_op.compare];
if (misc_features_support.depth_bounds_supported) {
- pso_extra_info.dyn_params.depth_bounds_min = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_min : 0.0f;
- pso_extra_info.dyn_params.depth_bounds_max = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_max : 1.0f;
+ render_info.dyn_params.depth_bounds_min = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_min : 0.0f;
+ render_info.dyn_params.depth_bounds_max = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_max : 1.0f;
} else {
if (p_depth_stencil_state.enable_depth_range) {
WARN_PRINT_ONCE("Depth bounds test is not supported by the GPU driver.");
}
}
- pso_extra_info.dyn_params.stencil_reference = p_depth_stencil_state.front_op.reference;
+ render_info.dyn_params.stencil_reference = p_depth_stencil_state.front_op.reference;
}
// Blend states.
@@ -5818,7 +5758,7 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
(&pipeline_desc.BlendState)->IndependentBlendEnable = !all_attachments_same_blend;
}
- pso_extra_info.dyn_params.blend_constant = p_blend_state.blend_constant;
+ render_info.dyn_params.blend_constant = p_blend_state.blend_constant;
// Stages bytecodes + specialization constants.
@@ -5852,12 +5792,12 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
}
ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), PipelineID(), "Create(Graphics)PipelineState failed with error " + vformat("0x%08ux", (uint64_t)res) + ".");
- // Bookkeep ancillary info.
-
- pipelines_shaders[pso] = shader_info_in;
- render_psos_extra_info[pso] = pso_extra_info;
+ PipelineInfo *pipeline_info = memnew(PipelineInfo);
+ pipeline_info->pso = pso;
+ pipeline_info->shader_info = shader_info_in;
+ pipeline_info->render_info = render_info;
- return PipelineID(pso);
+ return PipelineID(pipeline_info);
}
/*****************/
@@ -5868,20 +5808,20 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create(
void RenderingDeviceDriverD3D12::command_bind_compute_pipeline(CommandBufferID p_cmd_buffer, PipelineID p_pipeline) {
CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
- ID3D12PipelineState *pso = (ID3D12PipelineState *)p_pipeline.id;
- const ShaderInfo *shader_info_in = pipelines_shaders[pso];
+ const PipelineInfo *pipeline_info = (const PipelineInfo *)p_pipeline.id;
- if (cmd_buf_info->compute_pso == pso) {
+ if (cmd_buf_info->compute_pso == pipeline_info->pso) {
return;
}
- cmd_buf_info->cmd_list->SetPipelineState(pso);
+ const ShaderInfo *shader_info_in = pipeline_info->shader_info;
+ cmd_buf_info->cmd_list->SetPipelineState(pipeline_info->pso);
if (cmd_buf_info->compute_root_signature_crc != shader_info_in->root_signature_crc) {
cmd_buf_info->cmd_list->SetComputeRootSignature(shader_info_in->root_signature.Get());
cmd_buf_info->compute_root_signature_crc = shader_info_in->root_signature_crc;
}
- cmd_buf_info->compute_pso = pso;
+ cmd_buf_info->compute_pso = pipeline_info->pso;
cmd_buf_info->graphics_pso = nullptr;
}
@@ -5890,20 +5830,20 @@ void RenderingDeviceDriverD3D12::command_bind_compute_uniform_set(CommandBufferI
}
void RenderingDeviceDriverD3D12::command_compute_dispatch(CommandBufferID p_cmd_buffer, uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->Dispatch(p_x_groups, p_y_groups, p_z_groups);
}
void RenderingDeviceDriverD3D12::command_compute_dispatch_indirect(CommandBufferID p_cmd_buffer, BufferID p_indirect_buffer, uint64_t p_offset) {
- const CommandBufferInfo *cmd_buf_info = (const CommandBufferInfo *)p_cmd_buffer.id;
+ CommandBufferInfo *cmd_buf_info = (CommandBufferInfo *)p_cmd_buffer.id;
BufferInfo *indirect_buf_info = (BufferInfo *)p_indirect_buffer.id;
if (!barrier_capabilities.enhanced_barriers_supported) {
- _resource_transition_batch(indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
- _resource_transitions_flush(cmd_buf_info->cmd_list.Get());
+ _resource_transition_batch(cmd_buf_info, indirect_buf_info, 0, 1, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
+ _resource_transitions_flush(cmd_buf_info);
}
cmd_buf_info->cmd_list->ExecuteIndirect(indirect_cmd_signatures.dispatch.Get(), 1, indirect_buf_info->resource, p_offset, nullptr, 0);
@@ -5944,11 +5884,11 @@ RDD::PipelineID RenderingDeviceDriverD3D12::compute_pipeline_create(ShaderID p_s
}
ERR_FAIL_COND_V_MSG(!SUCCEEDED(res), PipelineID(), "Create(Compute)PipelineState failed with error " + vformat("0x%08ux", (uint64_t)res) + ".");
- // Bookkeep ancillary info.
+ PipelineInfo *pipeline_info = memnew(PipelineInfo);
+ pipeline_info->pso = pso;
+ pipeline_info->shader_info = shader_info_in;
- pipelines_shaders[pso] = shader_info_in;
-
- return PipelineID(pso);
+ return PipelineID(pipeline_info);
}
/*****************/
@@ -6111,8 +6051,8 @@ void RenderingDeviceDriverD3D12::set_object_name(ObjectType p_type, ID p_driver_
}
} break;
case OBJECT_TYPE_PIPELINE: {
- ID3D12PipelineState *pso = (ID3D12PipelineState *)p_driver_id.id;
- _set_object_name(pso, p_name);
+ const PipelineInfo *pipeline_info = (const PipelineInfo *)p_driver_id.id;
+ _set_object_name(pipeline_info->pso, p_name);
} break;
default: {
DEV_ASSERT(false);
@@ -6645,8 +6585,6 @@ static Error create_command_signature(ID3D12Device *device, D3D12_INDIRECT_ARGUM
Error RenderingDeviceDriverD3D12::_initialize_frames(uint32_t p_frame_count) {
Error err;
- D3D12MA::ALLOCATION_DESC allocation_desc = {};
- allocation_desc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
//CD3DX12_RESOURCE_DESC resource_desc = CD3DX12_RESOURCE_DESC::Buffer(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT);
uint32_t resource_descriptors_per_frame = GLOBAL_GET("rendering/rendering_device/d3d12/max_resource_descriptors_per_frame");
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h
index d8381279ec..b449a90876 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.h
+++ b/drivers/d3d12/rendering_device_driver_d3d12.h
@@ -80,7 +80,6 @@ using Microsoft::WRL::ComPtr;
#define D3D12_BITCODE_OFFSETS_NUM_STAGES 3
#ifdef DEV_ENABLED
-//#define DEBUG_COUNT_BARRIERS
#define CUSTOM_INFO_QUEUE_ENABLED 0
#endif
@@ -223,20 +222,6 @@ private:
ComPtr<D3D12MA::Allocator> allocator;
-#define USE_SMALL_ALLOCS_POOL // Disabled by now; seems not to be beneficial as it is in Vulkan.
-#ifdef USE_SMALL_ALLOCS_POOL
- union AllocPoolKey {
- struct {
- D3D12_HEAP_TYPE heap_type;
- D3D12_HEAP_FLAGS heap_flags;
- };
- uint64_t key = 0;
- };
- HashMap<uint64_t, ComPtr<D3D12MA::Pool>> small_allocs_pools;
-
- D3D12MA::Pool *_find_or_create_small_allocs_pool(D3D12_HEAP_TYPE p_heap_type, D3D12_HEAP_FLAGS p_heap_flags);
-#endif
-
/******************/
/**** RESOURCE ****/
/******************/
@@ -274,20 +259,11 @@ private:
uint8_t groups_count = 0;
static const D3D12_RESOURCE_STATES DELETED_GROUP = D3D12_RESOURCE_STATES(0xFFFFFFFFU);
};
- PagedAllocator<HashMapElement<ResourceInfo::States *, BarrierRequest>> res_barriers_requests_allocator;
- HashMap<ResourceInfo::States *, BarrierRequest, HashMapHasherDefault, HashMapComparatorDefault<ResourceInfo::States *>, decltype(res_barriers_requests_allocator)> res_barriers_requests;
-
- LocalVector<D3D12_RESOURCE_BARRIER> res_barriers;
- uint32_t res_barriers_count = 0;
- uint32_t res_barriers_batch = 0;
-#ifdef DEBUG_COUNT_BARRIERS
- int frame_barriers_count = 0;
- int frame_barriers_batches_count = 0;
- uint64_t frame_barriers_cpu_time = 0;
-#endif
- void _resource_transition_batch(ResourceInfo *p_resource, uint32_t p_subresource, uint32_t p_num_planes, D3D12_RESOURCE_STATES p_new_state);
- void _resource_transitions_flush(ID3D12GraphicsCommandList *p_cmd_list);
+ struct CommandBufferInfo;
+
+ void _resource_transition_batch(CommandBufferInfo *p_command_buffer, ResourceInfo *p_resource, uint32_t p_subresource, uint32_t p_num_planes, D3D12_RESOURCE_STATES p_new_state);
+ void _resource_transitions_flush(CommandBufferInfo *p_command_buffer);
/*****************/
/**** BUFFERS ****/
@@ -334,6 +310,7 @@ private:
SelfList<TextureInfo>::List textures_pending_clear;
HashMap<DXGI_FORMAT, uint32_t> format_sample_counts_mask_cache;
+ Mutex format_sample_counts_mask_cache_mutex;
uint32_t _find_max_common_supported_sample_count(VectorView<DXGI_FORMAT> p_formats);
UINT _compute_component_mapping(const TextureView &p_view);
@@ -341,7 +318,6 @@ private:
UINT _compute_plane_slice(DataFormat p_format, TextureAspect p_aspect);
UINT _compute_subresource_from_layers(TextureInfo *p_texture, const TextureSubresourceLayers &p_layers, uint32_t p_layer_offset);
- struct CommandBufferInfo;
void _discard_texture_subresources(const TextureInfo *p_tex_info, const CommandBufferInfo *p_cmd_buf_info);
protected:
@@ -492,6 +468,11 @@ private:
RenderPassState render_pass_state;
bool descriptor_heaps_set = false;
+
+ HashMap<ResourceInfo::States *, BarrierRequest> res_barriers_requests;
+ LocalVector<D3D12_RESOURCE_BARRIER> res_barriers;
+ uint32_t res_barriers_count = 0;
+ uint32_t res_barriers_batch = 0;
};
public:
@@ -797,10 +778,25 @@ public:
/**** PIPELINE ****/
/******************/
- virtual void pipeline_free(PipelineID p_pipeline) override final;
+ struct RenderPipelineInfo {
+ const VertexFormatInfo *vf_info = nullptr;
-private:
- HashMap<ID3D12PipelineState *, const ShaderInfo *> pipelines_shaders;
+ struct {
+ D3D12_PRIMITIVE_TOPOLOGY primitive_topology = {};
+ Color blend_constant;
+ float depth_bounds_min = 0.0f;
+ float depth_bounds_max = 0.0f;
+ uint32_t stencil_reference = 0;
+ } dyn_params;
+ };
+
+ struct PipelineInfo {
+ ID3D12PipelineState *pso = nullptr;
+ const ShaderInfo *shader_info = nullptr;
+ RenderPipelineInfo render_info;
+ };
+
+ virtual void pipeline_free(PipelineID p_pipeline) override final;
public:
// ----- BINDING -----
@@ -873,20 +869,6 @@ public:
// ----- PIPELINE -----
-private:
- struct RenderPipelineExtraInfo {
- struct {
- D3D12_PRIMITIVE_TOPOLOGY primitive_topology = {};
- Color blend_constant;
- float depth_bounds_min = 0.0f;
- float depth_bounds_max = 0.0f;
- uint32_t stencil_reference = 0;
- } dyn_params;
-
- const VertexFormatInfo *vf_info = nullptr;
- };
- HashMap<ID3D12PipelineState *, RenderPipelineExtraInfo> render_psos_extra_info;
-
public:
virtual PipelineID render_pipeline_create(
ShaderID p_shader,
@@ -1034,7 +1016,7 @@ private:
UniformSetInfo,
RenderPassInfo,
TimestampQueryPoolInfo>;
- PagedAllocator<VersatileResource> resources_allocator;
+ PagedAllocator<VersatileResource, true> resources_allocator;
/******************/
diff --git a/drivers/egl/egl_manager.cpp b/drivers/egl/egl_manager.cpp
index 4477ba7752..603dfadd4b 100644
--- a/drivers/egl/egl_manager.cpp
+++ b/drivers/egl/egl_manager.cpp
@@ -30,6 +30,8 @@
#include "egl_manager.h"
+#include "drivers/gles3/rasterizer_gles3.h"
+
#ifdef EGL_ENABLED
#if defined(EGL_STATIC)
@@ -51,6 +53,16 @@ extern "C" EGLAPI EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT(EGLenum platfo
#define GLAD_EGL_EXT_platform_base 0
#endif
+#ifdef WINDOWS_ENABLED
+// Unofficial ANGLE extension: EGL_ANGLE_surface_orientation
+#ifndef EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE
+#define EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE 0x33A7
+#define EGL_SURFACE_ORIENTATION_ANGLE 0x33A8
+#define EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE 0x0001
+#define EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE 0x0002
+#endif
+#endif
+
// Creates and caches a GLDisplay. Returns -1 on error.
int EGLManager::_get_gldisplay_id(void *p_display) {
// Look for a cached GLDisplay.
@@ -115,6 +127,18 @@ int EGLManager::_get_gldisplay_id(void *p_display) {
}
#endif
+#ifdef WINDOWS_ENABLED
+ String client_extensions_string = eglQueryString(new_gldisplay.egl_display, EGL_EXTENSIONS);
+ if (eglGetError() == EGL_SUCCESS) {
+ Vector<String> egl_extensions = client_extensions_string.split(" ");
+
+ if (egl_extensions.has("EGL_ANGLE_surface_orientation")) {
+ new_gldisplay.has_EGL_ANGLE_surface_orientation = true;
+ print_verbose("EGL: EGL_ANGLE_surface_orientation is supported.");
+ }
+ }
+#endif
+
displays.push_back(new_gldisplay);
// Return the new GLDisplay's ID.
@@ -237,8 +261,29 @@ Error EGLManager::window_create(DisplayServer::WindowID p_window_id, void *p_dis
GLWindow &glwindow = windows[p_window_id];
glwindow.gldisplay_id = gldisplay_id;
+ Vector<EGLAttrib> egl_attribs;
+
+#ifdef WINDOWS_ENABLED
+ if (gldisplay.has_EGL_ANGLE_surface_orientation) {
+ EGLint optimal_orientation;
+ if (eglGetConfigAttrib(gldisplay.egl_display, gldisplay.egl_config, EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, &optimal_orientation)) {
+ // We only need to support inverting Y for optimizing ANGLE on D3D11.
+ if (optimal_orientation & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE && !(optimal_orientation & EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE)) {
+ egl_attribs.push_back(EGL_SURFACE_ORIENTATION_ANGLE);
+ egl_attribs.push_back(EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE);
+ }
+ } else {
+ ERR_PRINT(vformat("Failed to get EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE, error: 0x%08X", eglGetError()));
+ }
+ }
+
+ if (!egl_attribs.is_empty()) {
+ egl_attribs.push_back(EGL_NONE);
+ }
+#endif
+
if (GLAD_EGL_VERSION_1_5) {
- glwindow.egl_surface = eglCreatePlatformWindowSurface(gldisplay.egl_display, gldisplay.egl_config, p_native_window, nullptr);
+ glwindow.egl_surface = eglCreatePlatformWindowSurface(gldisplay.egl_display, gldisplay.egl_config, p_native_window, egl_attribs.ptr());
} else {
EGLNativeWindowType *native_window_type = (EGLNativeWindowType *)p_native_window;
glwindow.egl_surface = eglCreateWindowSurface(gldisplay.egl_display, gldisplay.egl_config, *native_window_type, nullptr);
@@ -250,6 +295,20 @@ Error EGLManager::window_create(DisplayServer::WindowID p_window_id, void *p_dis
glwindow.initialized = true;
+#ifdef WINDOWS_ENABLED
+ if (gldisplay.has_EGL_ANGLE_surface_orientation) {
+ EGLint orientation;
+ if (eglQuerySurface(gldisplay.egl_display, glwindow.egl_surface, EGL_SURFACE_ORIENTATION_ANGLE, &orientation)) {
+ if (orientation & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE && !(orientation & EGL_SURFACE_ORIENTATION_INVERT_X_ANGLE)) {
+ glwindow.flipped_y = true;
+ print_verbose("EGL: Using optimal surface orientation: Invert Y");
+ }
+ } else {
+ ERR_PRINT(vformat("Failed to get EGL_SURFACE_ORIENTATION_ANGLE, error: 0x%08X", eglGetError()));
+ }
+ }
+#endif
+
window_make_current(p_window_id);
return OK;
@@ -316,6 +375,10 @@ void EGLManager::window_make_current(DisplayServer::WindowID p_window_id) {
GLDisplay &current_display = displays[current_window->gldisplay_id];
eglMakeCurrent(current_display.egl_display, current_window->egl_surface, current_window->egl_surface, current_display.egl_context);
+
+#ifdef WINDOWS_ENABLED
+ RasterizerGLES3::set_screen_flipped_y(glwindow.flipped_y);
+#endif
}
void EGLManager::set_use_vsync(bool p_use) {
diff --git a/drivers/egl/egl_manager.h b/drivers/egl/egl_manager.h
index a4502c0687..f1b3dc99b7 100644
--- a/drivers/egl/egl_manager.h
+++ b/drivers/egl/egl_manager.h
@@ -53,11 +53,18 @@ private:
EGLDisplay egl_display = EGL_NO_DISPLAY;
EGLContext egl_context = EGL_NO_CONTEXT;
EGLConfig egl_config = nullptr;
+
+#ifdef WINDOWS_ENABLED
+ bool has_EGL_ANGLE_surface_orientation = false;
+#endif
};
// EGL specific window data.
struct GLWindow {
bool initialized = false;
+#ifdef WINDOWS_ENABLED
+ bool flipped_y = false;
+#endif
// An handle to the GLDisplay associated with this window.
int gldisplay_id = -1;
diff --git a/drivers/gles3/effects/cubemap_filter.cpp b/drivers/gles3/effects/cubemap_filter.cpp
index b88e655492..685fee6d1b 100644
--- a/drivers/gles3/effects/cubemap_filter.cpp
+++ b/drivers/gles3/effects/cubemap_filter.cpp
@@ -41,7 +41,10 @@ CubemapFilter *CubemapFilter::singleton = nullptr;
CubemapFilter::CubemapFilter() {
singleton = this;
- ggx_samples = GLOBAL_GET("rendering/reflections/sky_reflections/ggx_samples");
+ // Use a factor 4 larger for the compatibility renderer to make up for the fact
+ // That we don't use an array texture. We will reduce samples on low roughness
+ // to compensate.
+ ggx_samples = 4 * uint32_t(GLOBAL_GET("rendering/reflections/sky_reflections/ggx_samples"));
{
String defines;
@@ -57,10 +60,10 @@ CubemapFilter::CubemapFilter() {
const float qv[6] = {
-1.0f,
-1.0f,
- 3.0f,
- -1.0f,
-1.0f,
3.0f,
+ 3.0f,
+ -1.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6, qv, GL_STATIC_DRAW);
@@ -146,10 +149,11 @@ void CubemapFilter::filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubem
}
if (p_layer > 0) {
- const uint32_t sample_counts[4] = { 1, ggx_samples / 4, ggx_samples / 2, ggx_samples };
- uint32_t sample_count = sample_counts[MIN(3, p_layer)];
+ const uint32_t sample_counts[5] = { 1, ggx_samples / 16, ggx_samples / 8, ggx_samples / 4, ggx_samples };
+ uint32_t sample_count = sample_counts[MIN(4, p_layer)];
- float roughness = float(p_layer) / (p_mipmap_count);
+ float roughness = float(p_layer) / (p_mipmap_count - 1);
+ roughness *= roughness; // Convert to non-perceptual roughness.
float roughness4 = roughness * roughness;
roughness4 *= roughness4;
@@ -165,7 +169,7 @@ void CubemapFilter::filter_radiance(GLuint p_source_cubemap, GLuint p_dest_cubem
Vector3 dir = importance_sample_GGX(xi, roughness4);
Vector3 light_vec = (2.0 * dir.z * dir - Vector3(0.0, 0.0, 1.0));
- if (light_vec.z < 0.0) {
+ if (light_vec.z <= 0.0) {
continue;
}
diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h
index 9c0d0abccb..a82e2713e0 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.h
+++ b/drivers/gles3/rasterizer_canvas_gles3.h
@@ -379,6 +379,8 @@ public:
}
}
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override { return 0; }
+
static RasterizerCanvasGLES3 *get_singleton();
RasterizerCanvasGLES3();
~RasterizerCanvasGLES3();
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index e79f1db08d..6e508c6ebf 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -86,6 +86,10 @@
#define strcpy strcpy_s
#endif
+#ifdef WINDOWS_ENABLED
+bool RasterizerGLES3::screen_flipped_y = false;
+#endif
+
bool RasterizerGLES3::gles_over_gl = true;
void RasterizerGLES3::begin_frame(double frame_step) {
@@ -389,6 +393,12 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display
flip_y = false;
}
+#ifdef WINDOWS_ENABLED
+ if (screen_flipped_y) {
+ flip_y = !flip_y;
+ }
+#endif
+
GLuint read_fbo = 0;
glGenFramebuffers(1, &read_fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, read_fbo);
@@ -485,9 +495,14 @@ void RasterizerGLES3::set_boot_image(const Ref<Image> &p_image, const Color &p_c
screenrect.position += ((Size2(win_size.width, win_size.height) - screenrect.size) / 2.0).floor();
}
- // Flip Y.
- screenrect.position.y = win_size.y - screenrect.position.y;
- screenrect.size.y = -screenrect.size.y;
+#ifdef WINDOWS_ENABLED
+ if (!screen_flipped_y)
+#endif
+ {
+ // Flip Y.
+ screenrect.position.y = win_size.y - screenrect.position.y;
+ screenrect.size.y = -screenrect.size.y;
+ }
// Normalize texture coordinates to window size.
screenrect.position /= win_size;
diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h
index 92c96519d2..6765d8b4d5 100644
--- a/drivers/gles3/rasterizer_gles3.h
+++ b/drivers/gles3/rasterizer_gles3.h
@@ -58,6 +58,10 @@ private:
double time_total = 0.0;
bool flip_xy_workaround = false;
+#ifdef WINDOWS_ENABLED
+ static bool screen_flipped_y;
+#endif
+
static bool gles_over_gl;
protected:
@@ -118,9 +122,16 @@ public:
low_end = true;
}
+#ifdef WINDOWS_ENABLED
+ static void set_screen_flipped_y(bool p_flipped) {
+ screen_flipped_y = p_flipped;
+ }
+#endif
+
_ALWAYS_INLINE_ uint64_t get_frame_number() const { return frame; }
_ALWAYS_INLINE_ double get_frame_delta_time() const { return delta; }
_ALWAYS_INLINE_ double get_total_time() const { return time_total; }
+ _ALWAYS_INLINE_ bool can_create_resources_async() const { return false; }
static RasterizerGLES3 *get_singleton() { return singleton; }
RasterizerGLES3();
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 65749323fd..a73f14c796 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -3443,6 +3443,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
}
material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::MODEL_FLAGS, inst->flags_cache, shader->version, instance_variant, spec_constants);
+ material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::INSTANCE_OFFSET, uint32_t(inst->shader_uniforms_offset), shader->version, instance_variant, spec_constants);
if (p_pass_mode == PASS_MODE_MATERIAL) {
material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::UV_OFFSET, p_params->uv_offset, shader->version, instance_variant, spec_constants);
@@ -4125,6 +4126,9 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() {
global_defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n";
global_defines += "\n#define MAX_FORWARD_LIGHTS " + itos(config->max_lights_per_object) + "u\n";
global_defines += "\n#define MAX_ROUGHNESS_LOD " + itos(sky_globals.roughness_layers - 1) + ".0\n";
+ if (config->force_vertex_shading) {
+ global_defines += "\n#define USE_VERTEX_LIGHTING\n";
+ }
material_storage->shaders.scene_shader.initialize(global_defines);
scene_globals.shader_default_version = material_storage->shaders.scene_shader.version_create();
material_storage->shaders.scene_shader.version_bind_shader(scene_globals.shader_default_version, SceneShaderGLES3::MODE_COLOR);
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index e4af8f99e9..06371b2b7f 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -767,6 +767,11 @@ public:
uint32_t geometry_instance_get_pair_mask() override;
+ /* PIPELINES */
+
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) override {}
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override { return 0; }
+
/* SDFGI UPDATE */
void sdfgi_update(const Ref<RenderSceneBuffers> &p_render_buffers, RID p_environment, const Vector3 &p_world_position) override {}
diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl
index 76881c8032..5e7fb3b338 100644
--- a/drivers/gles3/shaders/canvas.glsl
+++ b/drivers/gles3/shaders/canvas.glsl
@@ -579,7 +579,8 @@ void main() {
#endif
if (bool(read_draw_data_flags & FLAGS_CLIP_RECT_UV)) {
- uv = clamp(uv, read_draw_data_src_rect.xy, read_draw_data_src_rect.xy + abs(read_draw_data_src_rect.zw));
+ vec2 half_texpixel = read_draw_data_color_texture_pixel_size * 0.5;
+ uv = clamp(uv, read_draw_data_src_rect.xy + half_texpixel, read_draw_data_src_rect.xy + abs(read_draw_data_src_rect.zw) - half_texpixel);
}
#endif
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 393ba014c6..fcfbeddb9e 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -248,6 +248,175 @@ uniform lowp uint directional_shadow_index;
#endif // !(defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT))
#endif // USE_ADDITIVE_LIGHTING
+#ifdef USE_VERTEX_LIGHTING
+
+out vec3 diffuse_light_interp;
+out vec3 specular_light_interp;
+
+#ifdef USE_ADDITIVE_LIGHTING
+out vec3 additive_diffuse_light_interp;
+out vec3 additive_specular_light_interp;
+#endif // USE_ADDITIVE_LIGHTING
+
+// Directional light data.
+#if !defined(DISABLE_LIGHT_DIRECTIONAL) || (!defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING))
+
+struct DirectionalLightData {
+ mediump vec3 direction;
+ mediump float energy;
+ mediump vec3 color;
+ mediump float size;
+ lowp uint unused;
+ lowp uint bake_mode;
+ mediump float shadow_opacity;
+ mediump float specular;
+};
+
+layout(std140) uniform DirectionalLights { // ubo:7
+ DirectionalLightData directional_lights[MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS];
+};
+#endif // !DISABLE_LIGHT_DIRECTIONAL
+
+// Omni and spot light data.
+#if !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || (defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING))
+
+struct LightData { // This structure needs to be as packed as possible.
+ highp vec3 position;
+ highp float inv_radius;
+
+ mediump vec3 direction;
+ highp float size;
+
+ mediump vec3 color;
+ mediump float attenuation;
+
+ mediump float cone_attenuation;
+ mediump float cone_angle;
+ mediump float specular_amount;
+ mediump float shadow_opacity;
+
+ lowp vec3 pad;
+ lowp uint bake_mode;
+};
+
+#if !defined(DISABLE_LIGHT_OMNI) || defined(ADDITIVE_OMNI)
+layout(std140) uniform OmniLightData { // ubo:5
+ LightData omni_lights[MAX_LIGHT_DATA_STRUCTS];
+};
+#ifdef BASE_PASS
+uniform uint omni_light_indices[MAX_FORWARD_LIGHTS];
+uniform uint omni_light_count;
+#endif // BASE_PASS
+#endif // DISABLE_LIGHT_OMNI
+
+#if !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT)
+layout(std140) uniform SpotLightData { // ubo:6
+ LightData spot_lights[MAX_LIGHT_DATA_STRUCTS];
+};
+#ifdef BASE_PASS
+uniform uint spot_light_indices[MAX_FORWARD_LIGHTS];
+uniform uint spot_light_count;
+#endif // BASE_PASS
+#endif // DISABLE_LIGHT_SPOT
+#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || (defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING))
+
+#ifdef USE_ADDITIVE_LIGHTING
+#ifdef ADDITIVE_OMNI
+uniform lowp uint omni_light_index;
+#endif
+#ifdef ADDITIVE_SPOT
+uniform lowp uint spot_light_index;
+#endif
+#endif // USE_ADDITIVE_LIGHTING
+
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+
+// Eyeballed approximation of `exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25`.
+// Uses slightly more FMA instructions (2x rate) to avoid special instructions (0.25x rate).
+// Range is reduced to [0.64,4977] from [068,2,221,528] which makes mediump feasible for the rest of the shader.
+mediump float roughness_to_shininess(mediump float roughness) {
+ mediump float r = 1.2 - roughness;
+ mediump float r2 = r * r;
+ return r * r2 * r2 * 2000.0;
+}
+
+void light_compute(vec3 N, vec3 L, vec3 V, vec3 light_color, bool is_directional, float roughness,
+ inout vec3 diffuse_light, inout vec3 specular_light) {
+ float NdotL = min(dot(N, L), 1.0);
+ float cNdotL = max(NdotL, 0.0); // clamped NdotL
+
+#if defined(DIFFUSE_LAMBERT_WRAP)
+ // Energy conserving lambert wrap shader.
+ // https://web.archive.org/web/20210228210901/http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
+ float diffuse_brdf_NL = max(0.0, (cNdotL + roughness) / ((1.0 + roughness) * (1.0 + roughness))) * (1.0 / M_PI);
+#else
+ // lambert
+ float diffuse_brdf_NL = cNdotL * (1.0 / M_PI);
+#endif
+
+ diffuse_light += light_color * diffuse_brdf_NL;
+
+#if !defined(SPECULAR_DISABLED)
+ float specular_brdf_NL = 0.0;
+ // Normalized blinn always unless disabled.
+ vec3 H = normalize(V + L);
+ float cNdotH = clamp(dot(N, H), 0.0, 1.0);
+ float shininess = roughness_to_shininess(roughness);
+ float blinn = pow(cNdotH, shininess);
+ blinn *= (shininess + 2.0) * (1.0 / (8.0 * M_PI)) * cNdotL;
+ specular_brdf_NL = blinn;
+ specular_light += specular_brdf_NL * light_color;
+#endif
+}
+
+float get_omni_spot_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
+#if !defined(DISABLE_LIGHT_OMNI) || (defined(ADDITIVE_OMNI) && defined(USE_ADDITIVE_LIGHTING))
+void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, float roughness,
+ inout vec3 diffuse_light, inout vec3 specular_light) {
+ vec3 light_rel_vec = omni_lights[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ float omni_attenuation = get_omni_spot_attenuation(light_length, omni_lights[idx].inv_radius, omni_lights[idx].attenuation);
+ vec3 color = omni_lights[idx].color * omni_attenuation; // No light shaders here, so combine.
+
+ light_compute(normal, normalize(light_rel_vec), eye_vec, color, false, roughness,
+ diffuse_light,
+ specular_light);
+}
+#endif // !defined(DISABLE_LIGHT_OMNI) || (defined(ADDITIVE_OMNI) && defined(USE_ADDITIVE_LIGHTING))
+
+#if !defined(DISABLE_LIGHT_SPOT) || (defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING))
+void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, float roughness,
+ inout vec3 diffuse_light,
+ inout vec3 specular_light) {
+ vec3 light_rel_vec = spot_lights[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ float spot_attenuation = get_omni_spot_attenuation(light_length, spot_lights[idx].inv_radius, spot_lights[idx].attenuation);
+ vec3 spot_dir = spot_lights[idx].direction;
+ float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_lights[idx].cone_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - spot_lights[idx].cone_angle));
+
+ mediump float cone_attenuation = spot_lights[idx].cone_attenuation;
+ spot_attenuation *= 1.0 - pow(spot_rim, cone_attenuation);
+
+ vec3 color = spot_lights[idx].color * spot_attenuation;
+
+ light_compute(normal, normalize(light_rel_vec), eye_vec, color, false, roughness,
+ diffuse_light, specular_light);
+}
+#endif // !defined(DISABLE_LIGHT_SPOT) || (defined(ADDITIVE_SPOT) && defined(USE_ADDITIVE_LIGHTING))
+
+#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+
+#endif // USE_VERTEX_LIGHTING
+
#ifdef USE_MULTIVIEW
layout(std140) uniform MultiviewData { // ubo:8
highp mat4 projection_matrix_view[MAX_VIEWS];
@@ -261,6 +430,7 @@ uniform highp mat4 world_transform;
uniform highp vec3 compressed_aabb_position;
uniform highp vec3 compressed_aabb_size;
uniform highp vec4 uv_scale;
+uniform highp uint instance_offset;
uniform highp uint model_flags;
@@ -540,8 +710,65 @@ void main() {
gl_Position.z = 0.00001;
gl_Position.w = 1.0;
#endif
-}
+#ifdef USE_VERTEX_LIGHTING
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#ifdef USE_MULTIVIEW
+ vec3 view = -normalize(vertex_interp - eye_offset);
+#else
+ vec3 view = -normalize(vertex_interp);
+#endif
+ diffuse_light_interp = vec3(0.0);
+ specular_light_interp = vec3(0.0);
+#ifdef BASE_PASS
+#ifndef DISABLE_LIGHT_DIRECTIONAL
+ for (uint i = uint(0); i < scene_data.directional_light_count; i++) {
+ light_compute(normal_interp, normalize(directional_lights[i].direction), normalize(view), directional_lights[i].color * directional_lights[i].energy, true, roughness,
+ diffuse_light_interp.rgb,
+ specular_light_interp.rgb);
+ }
+#endif // !DISABLE_LIGHT_DIRECTIONAL
+
+#ifndef DISABLE_LIGHT_OMNI
+ for (uint i = 0u; i < omni_light_count; i++) {
+ light_process_omni(omni_light_indices[i], vertex_interp, view, normal_interp, roughness,
+ diffuse_light_interp.rgb, specular_light_interp.rgb);
+ }
+#endif // !DISABLE_LIGHT_OMNI
+
+#ifndef DISABLE_LIGHT_SPOT
+ for (uint i = 0u; i < spot_light_count; i++) {
+ light_process_spot(spot_light_indices[i], vertex_interp, view, normal_interp, roughness,
+ diffuse_light_interp.rgb, specular_light_interp.rgb);
+ }
+#endif // !DISABLE_LIGHT_SPOT
+#endif // BASE_PASS
+
+/* ADDITIVE LIGHTING PASS */
+#ifdef USE_ADDITIVE_LIGHTING
+ additive_diffuse_light_interp = vec3(0.0);
+ additive_specular_light_interp = vec3(0.0);
+#if !defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT)
+
+ light_compute(normal_interp, normalize(directional_lights[directional_shadow_index].direction), normalize(view), directional_lights[directional_shadow_index].color * directional_lights[directional_shadow_index].energy, true, roughness,
+ additive_diffuse_light_interp.rgb,
+ additive_specular_light_interp.rgb);
+#endif // !defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT)
+
+#ifdef ADDITIVE_OMNI
+ light_process_omni(omni_light_index, vertex_interp, view, normal_interp, roughness,
+ additive_diffuse_light_interp.rgb, additive_specular_light_interp.rgb);
+#endif // ADDITIVE_OMNI
+
+#ifdef ADDITIVE_SPOT
+ light_process_spot(spot_light_index, vertex_interp, view, normal_interp, roughness,
+ additive_diffuse_light_interp.rgb, additive_specular_light_interp.rgb);
+#endif // ADDITIVE_SPOT
+
+#endif // USE_ADDITIVE_LIGHTING
+#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#endif // USE_VERTEX_LIGHTING
+}
/* clang-format off */
#[fragment]
@@ -758,6 +985,16 @@ multiview_data;
#define LIGHT_BAKE_DYNAMIC 2u
#ifndef MODE_RENDER_DEPTH
+#ifdef USE_VERTEX_LIGHTING
+in vec3 diffuse_light_interp;
+in vec3 specular_light_interp;
+
+#ifdef USE_ADDITIVE_LIGHTING
+in vec3 additive_diffuse_light_interp;
+in vec3 additive_specular_light_interp;
+#endif // USE_ADDITIVE_LIGHTING
+#endif // USE_VERTEX_LIGHTING
+
// Directional light data.
#if !defined(DISABLE_LIGHT_DIRECTIONAL) || (!defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT))
@@ -809,22 +1046,22 @@ struct LightData { // This structure needs to be as packed as possible.
layout(std140) uniform OmniLightData { // ubo:5
LightData omni_lights[MAX_LIGHT_DATA_STRUCTS];
};
-#ifdef BASE_PASS
+#if defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING)
uniform uint omni_light_indices[MAX_FORWARD_LIGHTS];
uniform uint omni_light_count;
-#endif // BASE_PASS
-#endif // DISABLE_LIGHT_OMNI
+#endif // defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING)
+#endif // !defined(DISABLE_LIGHT_OMNI) || defined(ADDITIVE_OMNI)
#if !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT)
layout(std140) uniform SpotLightData { // ubo:6
LightData spot_lights[MAX_LIGHT_DATA_STRUCTS];
};
-#ifdef BASE_PASS
+#if defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING)
uniform uint spot_light_indices[MAX_FORWARD_LIGHTS];
uniform uint spot_light_count;
-#endif // BASE_PASS
-#endif // DISABLE_LIGHT_SPOT
-#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT)
+#endif // defined(BASE_PASS) && !defined(USE_VERTEX_LIGHTING)
+#endif // !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT)
+#endif // !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_OMNI) || defined(ADDITIVE_SPOT)
#ifdef USE_ADDITIVE_LIGHTING
#ifdef ADDITIVE_OMNI
@@ -965,6 +1202,7 @@ ivec2 multiview_uv(ivec2 uv) {
uniform highp mat4 world_transform;
uniform mediump float opaque_prepass_threshold;
uniform highp uint model_flags;
+uniform highp uint instance_offset;
#if defined(RENDER_MATERIAL)
layout(location = 0) out vec4 albedo_output_buffer;
@@ -985,6 +1223,8 @@ vec3 F0(float metallic, float specular, vec3 albedo) {
return mix(vec3(dielectric), albedo, vec3(metallic));
}
#ifndef MODE_RENDER_DEPTH
+
+#ifndef USE_VERTEX_LIGHTING
#if !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT) || defined(USE_ADDITIVE_LIGHTING)
float D_GGX(float cos_theta_m, float alpha) {
@@ -1284,6 +1524,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f
#endif // !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT)
#endif // !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) || !defined(DISABLE_LIGHT_SPOT)
+#endif // !USE_VERTEX_LIGHTING
vec4 fog_process(vec3 vertex) {
vec3 fog_color = scene_data.fog_light_color;
@@ -1859,9 +2100,13 @@ void main() {
specular_light *= env.x * f0 + env.y * clamp(50.0 * f0.g, metallic, 1.0);
#endif
}
-
#endif // !AMBIENT_LIGHT_DISABLED
+#ifdef USE_VERTEX_LIGHTING
+ specular_light += specular_light_interp * f0;
+ diffuse_light += diffuse_light_interp;
+#else
+
#ifndef DISABLE_LIGHT_DIRECTIONAL
for (uint i = uint(0); i < scene_data.directional_light_count; i++) {
#if defined(USE_LIGHTMAP) && !defined(DISABLE_LIGHTMAP)
@@ -1944,6 +2189,7 @@ void main() {
diffuse_light, specular_light);
}
#endif // !DISABLE_LIGHT_SPOT
+#endif // !USE_VERTEX_LIGHTING
#endif // BASE_PASS
#endif // !MODE_UNSHADED
@@ -1993,7 +2239,6 @@ void main() {
#else
diffuse_light *= albedo;
-
diffuse_light *= 1.0 - metallic;
ambient_light *= 1.0 - metallic;
@@ -2024,6 +2269,11 @@ void main() {
diffuse_light = vec3(0.0);
specular_light = vec3(0.0);
+#ifdef USE_VERTEX_LIGHTING
+ diffuse_light = additive_diffuse_light_interp;
+ specular_light = additive_specular_light_interp * f0;
+#endif // USE_VERTEX_LIGHTING
+
#if !defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT)
#ifndef SHADOWS_DISABLED
@@ -2137,6 +2387,8 @@ void main() {
#else
float directional_shadow = 1.0f;
#endif // SHADOWS_DISABLED
+
+#ifndef USE_VERTEX_LIGHTING
light_compute(normal, normalize(directional_lights[directional_shadow_index].direction), normalize(view), directional_lights[directional_shadow_index].size, directional_lights[directional_shadow_index].color * directional_lights[directional_shadow_index].energy, true, directional_shadow, f0, roughness, metallic, 1.0, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
backlight,
@@ -2153,6 +2405,11 @@ void main() {
#endif
diffuse_light,
specular_light);
+#else
+ // Just apply shadows to vertex lighting.
+ diffuse_light *= directional_shadow;
+ specular_light *= directional_shadow;
+#endif // !USE_VERTEX_LIGHTING
#endif // !defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT)
#ifdef ADDITIVE_OMNI
@@ -2162,6 +2419,8 @@ void main() {
omni_shadow = texture(omni_shadow_texture, vec4(light_ray, 1.0 - length(light_ray) * omni_lights[omni_light_index].inv_radius));
omni_shadow = mix(1.0, omni_shadow, omni_lights[omni_light_index].shadow_opacity);
#endif // SHADOWS_DISABLED
+
+#ifndef USE_VERTEX_LIGHTING
light_process_omni(omni_light_index, vertex, view, normal, f0, roughness, metallic, omni_shadow, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
backlight,
@@ -2177,6 +2436,11 @@ void main() {
binormal, tangent, anisotropy,
#endif
diffuse_light, specular_light);
+#else
+ // Just apply shadows to vertex lighting.
+ diffuse_light *= omni_shadow;
+ specular_light *= omni_shadow;
+#endif // !USE_VERTEX_LIGHTING
#endif // ADDITIVE_OMNI
#ifdef ADDITIVE_SPOT
@@ -2185,6 +2449,8 @@ void main() {
spot_shadow = sample_shadow(spot_shadow_texture, positional_shadows[positional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
spot_shadow = mix(1.0, spot_shadow, spot_lights[spot_light_index].shadow_opacity);
#endif // SHADOWS_DISABLED
+
+#ifndef USE_VERTEX_LIGHTING
light_process_spot(spot_light_index, vertex, view, normal, f0, roughness, metallic, spot_shadow, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
backlight,
@@ -2201,6 +2467,11 @@ void main() {
binormal, anisotropy,
#endif
diffuse_light, specular_light);
+#else
+ // Just apply shadows to vertex lighting.
+ diffuse_light *= spot_shadow;
+ specular_light *= spot_shadow;
+#endif // !USE_VERTEX_LIGHTING
#endif // ADDITIVE_SPOT
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 0100719151..209b7dd7d2 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -178,7 +178,7 @@ Config::Config() {
}
#endif
- force_vertex_shading = false; //GLOBAL_GET("rendering/quality/shading/force_vertex_shading");
+ force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
use_nearest_mip_filter = GLOBAL_GET("rendering/textures/default_filters/use_nearest_mipmap_filter");
use_depth_prepass = bool(GLOBAL_GET("rendering/driver/depth_prepass/enable"));
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index c29c741c2a..684f179492 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1368,6 +1368,10 @@ MaterialStorage::MaterialStorage() {
actions.render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
actions.render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";
actions.render_mode_defines["unshaded"] = "#define MODE_UNSHADED\n";
+ if (!GLES3::Config::get_singleton()->force_vertex_shading) {
+ // If forcing vertex shading, this will be defined already.
+ actions.render_mode_defines["vertex_lighting"] = "#define USE_VERTEX_LIGHTING\n";
+ }
actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";
actions.default_filter = ShaderLanguage::FILTER_LINEAR_MIPMAP;
@@ -1375,6 +1379,7 @@ MaterialStorage::MaterialStorage() {
actions.check_multiview_samplers = RasterizerGLES3::get_singleton()->is_xr_enabled();
actions.global_buffer_array_variable = "global_shader_uniforms";
+ actions.instance_uniform_index_variable = "instance_offset";
shaders.compiler_scene.initialize(actions);
}
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index 5058554659..73d95d75ba 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -2201,7 +2201,7 @@ void MeshStorage::skeleton_allocate_data(RID p_skeleton, int p_bones, bool p_2d_
glBindTexture(GL_TEXTURE_2D, 0);
GLES3::Utilities::get_singleton()->texture_allocated_data(skeleton->transforms_texture, skeleton->data.size() * sizeof(float), "Skeleton transforms texture");
- memset(skeleton->data.ptrw(), 0, skeleton->data.size() * sizeof(float));
+ memset(skeleton->data.ptr(), 0, skeleton->data.size() * sizeof(float));
_skeleton_make_dirty(skeleton);
}
@@ -2232,7 +2232,7 @@ void MeshStorage::skeleton_bone_set_transform(RID p_skeleton, int p_bone, const
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(skeleton->use_2d);
- float *dataptr = skeleton->data.ptrw() + p_bone * 12;
+ float *dataptr = skeleton->data.ptr() + p_bone * 12;
dataptr[0] = p_transform.basis.rows[0][0];
dataptr[1] = p_transform.basis.rows[0][1];
@@ -2284,7 +2284,7 @@ void MeshStorage::skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, con
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(!skeleton->use_2d);
- float *dataptr = skeleton->data.ptrw() + p_bone * 8;
+ float *dataptr = skeleton->data.ptr() + p_bone * 8;
dataptr[0] = p_transform.columns[0][0];
dataptr[1] = p_transform.columns[1][0];
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index 31858cd372..8615b89a30 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -214,7 +214,7 @@ struct Skeleton {
bool use_2d = false;
int size = 0;
int height = 0;
- Vector<float> data;
+ LocalVector<float> data;
bool dirty = false;
Skeleton *dirty_list = nullptr;
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 27ba89aa5f..d7b4d6911d 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -251,11 +251,6 @@ TextureStorage::~TextureStorage() {
sdf_shader.shader.version_free(sdf_shader.shader_version);
}
-//TODO, move back to storage
-bool TextureStorage::can_create_resources_async() const {
- return false;
-}
-
/* Canvas Texture API */
RID TextureStorage::canvas_texture_allocate() {
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index 2bf8546c0f..3786c8c690 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -505,8 +505,6 @@ public:
texture_owner.initialize_rid(p_texture, p_tex);
}
- virtual bool can_create_resources_async() const override;
-
virtual RID texture_allocate() override;
virtual void texture_free(RID p_rid) override;
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index 9d691a0d23..a4a408356a 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -358,7 +358,11 @@ RDD::TextureID RenderingDeviceDriverMetal::texture_create(const TextureFormat &p
}
RDD::TextureID RenderingDeviceDriverMetal::texture_create_from_extension(uint64_t p_native_texture, TextureType p_type, DataFormat p_format, uint32_t p_array_layers, bool p_depth_stencil) {
- ERR_FAIL_V_MSG(RDD::TextureID(), "not implemented");
+ id<MTLTexture> obj = (__bridge id<MTLTexture>)(void *)(uintptr_t)p_native_texture;
+
+ // We only need to create a RDD::TextureID for an existing, natively-provided texture.
+
+ return rid::make(obj);
}
RDD::TextureID RenderingDeviceDriverMetal::texture_create_shared(TextureID p_original_texture, const TextureView &p_view) {
@@ -3667,7 +3671,8 @@ void RenderingDeviceDriverMetal::set_object_name(ObjectType p_type, ID p_driver_
uint64_t RenderingDeviceDriverMetal::get_resource_native_handle(DriverResource p_type, ID p_driver_id) {
switch (p_type) {
case DRIVER_RESOURCE_LOGICAL_DEVICE: {
- return 0;
+ uintptr_t devicePtr = (uintptr_t)(__bridge void *)device;
+ return (uint64_t)devicePtr;
}
case DRIVER_RESOURCE_PHYSICAL_DEVICE: {
return 0;
diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index e4bad88083..816ffd7a32 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -410,7 +410,17 @@ Error DirAccessUnix::rename(String p_path, String p_new_path) {
p_new_path = p_new_path.left(-1);
}
- return ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data()) == 0 ? OK : FAILED;
+ int res = ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data());
+ if (res != 0 && errno == EXDEV) { // Cross-device move, use copy and remove.
+ Error err = OK;
+ err = copy(p_path, p_new_path);
+ if (err != OK) {
+ return err;
+ }
+ return remove(p_path);
+ } else {
+ return (res == 0) ? OK : FAILED;
+ }
}
Error DirAccessUnix::remove(String p_path) {
diff --git a/modules/squish/image_decompress_squish.h b/drivers/vulkan/godot_vulkan.h
index 53c5d344a5..f911c5520a 100644
--- a/modules/squish/image_decompress_squish.h
+++ b/drivers/vulkan/godot_vulkan.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* image_decompress_squish.h */
+/* godot_vulkan.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,11 +28,15 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef IMAGE_DECOMPRESS_SQUISH_H
-#define IMAGE_DECOMPRESS_SQUISH_H
+#ifndef GODOT_VULKAN_H
+#define GODOT_VULKAN_H
-#include "core/io/image.h"
+#ifdef USE_VOLK
+#include <volk.h>
+#else
+#include <stdint.h>
+#define VK_NO_STDINT_H
+#include <vulkan/vulkan.h>
+#endif
-void image_decompress_squish(Image *p_image);
-
-#endif // IMAGE_DECOMPRESS_SQUISH_H
+#endif // GODOT_VULKAN_H
diff --git a/drivers/vulkan/rendering_context_driver_vulkan.h b/drivers/vulkan/rendering_context_driver_vulkan.h
index f9352d617b..26de386206 100644
--- a/drivers/vulkan/rendering_context_driver_vulkan.h
+++ b/drivers/vulkan/rendering_context_driver_vulkan.h
@@ -40,11 +40,7 @@
#define VK_TRACK_DEVICE_MEMORY
#endif
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
class RenderingContextDriverVulkan : public RenderingContextDriver {
public:
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index bd395f41e2..d20f396281 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -4103,10 +4103,6 @@ bool RenderingDeviceDriverVulkan::pipeline_cache_create(const Vector<uint8_t> &p
cache_info.initialDataSize = pipelines_cache.buffer.size() - sizeof(PipelineCacheHeader);
cache_info.pInitialData = pipelines_cache.buffer.ptr() + sizeof(PipelineCacheHeader);
- if (pipeline_cache_control_support) {
- cache_info.flags = VK_PIPELINE_CACHE_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
- }
-
VkResult err = vkCreatePipelineCache(vk_device, &cache_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_PIPELINE_CACHE), &pipelines_cache.vk_cache);
if (err != VK_SUCCESS) {
WARN_PRINT("vkCreatePipelinecache failed with error " + itos(err) + ".");
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index 81f4256941..58f7a97ec0 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -43,11 +43,7 @@
#endif
#include "thirdparty/vulkan/vk_mem_alloc.h"
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
// Design principles:
// - Vulkan structs are zero-initialized and fields not requiring a non-zero value are omitted (except in cases where expresivity reasons apply).
@@ -672,7 +668,7 @@ private:
VertexFormatInfo,
ShaderInfo,
UniformSetInfo>;
- PagedAllocator<VersatileResource> resources_allocator;
+ PagedAllocator<VersatileResource, true> resources_allocator;
/******************/
diff --git a/drivers/vulkan/vulkan_hooks.h b/drivers/vulkan/vulkan_hooks.h
index bb30b29cec..82bcc9a064 100644
--- a/drivers/vulkan/vulkan_hooks.h
+++ b/drivers/vulkan/vulkan_hooks.h
@@ -31,11 +31,7 @@
#ifndef VULKAN_HOOKS_H
#define VULKAN_HOOKS_H
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
class VulkanHooks {
private:
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index d277ba2f6d..25e3925653 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -39,6 +39,7 @@
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_spin_slider.h"
+#include "editor/gui/editor_validation_panel.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
#include "editor/multi_node_edit.h"
@@ -48,6 +49,7 @@
#include "scene/animation/animation_player.h"
#include "scene/animation/tween.h"
#include "scene/gui/check_box.h"
+#include "scene/gui/color_picker.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
@@ -59,8 +61,9 @@
#include "scene/main/window.h"
#include "servers/audio/audio_stream.h"
-constexpr double FPS_DECIMAL = 1;
+constexpr double FPS_DECIMAL = 1.0;
constexpr double SECOND_DECIMAL = 0.0001;
+constexpr double FPS_STEP_FRACTION = 0.0625;
void AnimationTrackKeyEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj);
@@ -1467,7 +1470,7 @@ void AnimationTimelineEdit::_notification(int p_what) {
case NOTIFICATION_DRAW: {
int key_range = get_size().width - get_buttons_width() - get_name_limit();
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return;
}
@@ -1522,6 +1525,18 @@ void AnimationTimelineEdit::_notification(int p_what) {
}
}
+ PackedStringArray markers = animation->get_marker_names();
+ if (markers.size() > 0) {
+ float min_marker = animation->get_marker_time(markers[0]);
+ float max_marker = animation->get_marker_time(markers[markers.size() - 1]);
+ if (min_marker < time_min) {
+ time_min = min_marker;
+ }
+ if (max_marker > time_max) {
+ time_max = max_marker;
+ }
+ }
+
float extra = (zoomw / scale) * 0.5;
time_max += extra;
@@ -1701,7 +1716,7 @@ void AnimationTimelineEdit::set_zoom(Range *p_zoom) {
}
void AnimationTimelineEdit::auto_fit() {
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return;
}
@@ -1780,18 +1795,19 @@ void AnimationTimelineEdit::update_play_position() {
}
void AnimationTimelineEdit::update_values() {
- if (!animation.is_valid() || editing) {
+ if (animation.is_null() || editing) {
return;
}
editing = true;
- if (use_fps && animation->get_step() > 0) {
+ if (use_fps && animation->get_step() > 0.0) {
length->set_value(animation->get_length() / animation->get_step());
length->set_step(FPS_DECIMAL);
length->set_tooltip_text(TTR("Animation length (frames)"));
time_icon->set_tooltip_text(TTR("Animation length (frames)"));
if (track_edit) {
track_edit->editor->_update_key_edit();
+ track_edit->editor->marker_edit->_update_key_edit();
}
} else {
length->set_value(animation->get_length());
@@ -1821,7 +1837,7 @@ void AnimationTimelineEdit::update_values() {
}
void AnimationTimelineEdit::_play_position_draw() {
- if (!animation.is_valid() || play_position_pos < 0) {
+ if (animation.is_null() || play_position_pos < 0) {
return;
}
@@ -1972,6 +1988,7 @@ AnimationTimelineEdit::AnimationTimelineEdit() {
Control *expander = memnew(Control);
expander->set_h_size_flags(SIZE_EXPAND_FILL);
+ expander->set_mouse_filter(MOUSE_FILTER_IGNORE);
len_hb->add_child(expander);
time_icon = memnew(TextureRect);
time_icon->set_v_size_flags(SIZE_SHRINK_CENTER);
@@ -2124,6 +2141,62 @@ void AnimationTrackEdit::_notification(int p_what) {
draw_line(Point2(limit, 0), Point2(limit, get_size().height), h_line_color, Math::round(EDSCALE));
}
+ // Marker sections.
+
+ {
+ float scale = timeline->get_zoom_scale();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+
+ PackedStringArray section = editor->get_selected_section();
+ if (section.size() == 2) {
+ StringName start_marker = section[0];
+ StringName end_marker = section[1];
+ double start_time = animation->get_marker_time(start_marker);
+ double end_time = animation->get_marker_time(end_marker);
+
+ // When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (editor->is_marker_moving_selection() && !(player && player->is_playing())) {
+ start_time += editor->get_marker_moving_selection_offset();
+ end_time += editor->get_marker_moving_selection_offset();
+ }
+
+ if (start_time < animation->get_length() && end_time >= 0) {
+ float start_ofs = MAX(0, start_time) - timeline->get_value();
+ float end_ofs = MIN(animation->get_length(), end_time) - timeline->get_value();
+ start_ofs = start_ofs * scale + limit;
+ end_ofs = end_ofs * scale + limit;
+ start_ofs = MAX(start_ofs, limit);
+ end_ofs = MIN(end_ofs, limit_end);
+ Rect2 rect;
+ rect.set_position(Vector2(start_ofs, 0));
+ rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));
+
+ draw_rect(rect, Color(1, 0.1, 0.1, 0.2));
+ }
+ }
+ }
+
+ // Marker overlays.
+
+ {
+ float scale = timeline->get_zoom_scale();
+ PackedStringArray markers = animation->get_marker_names();
+ for (const StringName marker : markers) {
+ double time = animation->get_marker_time(marker);
+ if (editor->is_marker_selected(marker) && editor->is_marker_moving_selection()) {
+ time += editor->get_marker_moving_selection_offset();
+ }
+ if (time >= 0) {
+ float offset = time - timeline->get_value();
+ offset = offset * scale + limit;
+ Color marker_color = animation->get_marker_color(marker);
+ marker_color.a = 0.2;
+ draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color);
+ }
+ }
+ }
+
// Keyframes.
draw_bg(limit, get_size().width - timeline->get_buttons_width() - outer_margin);
@@ -2352,7 +2425,7 @@ void AnimationTrackEdit::_notification(int p_what) {
}
int AnimationTrackEdit::get_key_height() const {
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return 0;
}
@@ -2360,7 +2433,7 @@ int AnimationTrackEdit::get_key_height() const {
}
Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) {
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return Rect2();
}
Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height);
@@ -2399,7 +2472,7 @@ void AnimationTrackEdit::draw_key_link(int p_index, float p_pixels_sec, int p_x,
}
void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return;
}
@@ -2573,7 +2646,7 @@ void AnimationTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
}
void AnimationTrackEdit::_play_position_draw() {
- if (!animation.is_valid() || play_position_pos < 0) {
+ if (animation.is_null() || play_position_pos < 0) {
return;
}
@@ -3522,6 +3595,64 @@ void AnimationTrackEditGroup::_notification(int p_what) {
draw_style_box(stylebox_header, Rect2(Point2(), get_size()));
+ int limit = timeline->get_name_limit();
+
+ // Section preview.
+
+ {
+ float scale = timeline->get_zoom_scale();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+
+ PackedStringArray section = editor->get_selected_section();
+ if (section.size() == 2) {
+ StringName start_marker = section[0];
+ StringName end_marker = section[1];
+ double start_time = editor->get_current_animation()->get_marker_time(start_marker);
+ double end_time = editor->get_current_animation()->get_marker_time(end_marker);
+
+ // When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (editor->is_marker_moving_selection() && !(player && player->is_playing())) {
+ start_time += editor->get_marker_moving_selection_offset();
+ end_time += editor->get_marker_moving_selection_offset();
+ }
+
+ if (start_time < editor->get_current_animation()->get_length() && end_time >= 0) {
+ float start_ofs = MAX(0, start_time) - timeline->get_value();
+ float end_ofs = MIN(editor->get_current_animation()->get_length(), end_time) - timeline->get_value();
+ start_ofs = start_ofs * scale + limit;
+ end_ofs = end_ofs * scale + limit;
+ start_ofs = MAX(start_ofs, limit);
+ end_ofs = MIN(end_ofs, limit_end);
+ Rect2 rect;
+ rect.set_position(Vector2(start_ofs, 0));
+ rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));
+
+ draw_rect(rect, Color(1, 0.1, 0.1, 0.2));
+ }
+ }
+ }
+
+ // Marker overlays.
+
+ {
+ float scale = timeline->get_zoom_scale();
+ PackedStringArray markers = editor->get_current_animation()->get_marker_names();
+ for (const StringName marker : markers) {
+ double time = editor->get_current_animation()->get_marker_time(marker);
+ if (editor->is_marker_selected(marker) && editor->is_marker_moving_selection()) {
+ time += editor->get_marker_moving_selection_offset();
+ }
+ if (time >= 0) {
+ float offset = time - timeline->get_value();
+ offset = offset * scale + limit;
+ Color marker_color = editor->get_current_animation()->get_marker_color(marker);
+ marker_color.a = 0.2;
+ draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color);
+ }
+ }
+ }
+
draw_line(Point2(), Point2(get_size().width, 0), h_line_color, Math::round(EDSCALE));
draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), v_line_color, Math::round(EDSCALE));
draw_line(Point2(get_size().width - timeline->get_buttons_width() - outer_margin, 0), Point2(get_size().width - timeline->get_buttons_width() - outer_margin, get_size().height), v_line_color, Math::round(EDSCALE));
@@ -3590,6 +3721,10 @@ void AnimationTrackEditGroup::set_root(Node *p_root) {
queue_redraw();
}
+void AnimationTrackEditGroup::set_editor(AnimationTrackEditor *p_editor) {
+ editor = p_editor;
+}
+
void AnimationTrackEditGroup::_zoom_changed() {
queue_redraw();
}
@@ -3623,6 +3758,9 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
read_only = p_read_only;
timeline->set_animation(p_anim, read_only);
+ marker_edit->set_animation(p_anim, read_only);
+ marker_edit->set_play_position(timeline->get_play_position());
+
_cancel_bezier_edit();
_update_tracks();
@@ -3873,6 +4011,7 @@ void AnimationTrackEditor::_track_grab_focus(int p_track) {
void AnimationTrackEditor::set_anim_pos(float p_pos) {
timeline->set_play_position(p_pos);
+ marker_edit->set_play_position(p_pos);
for (int i = 0; i < track_edits.size(); i++) {
track_edits[i]->set_play_position(p_pos);
}
@@ -4043,7 +4182,7 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_
if (!keying) {
return;
}
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return;
}
@@ -4083,7 +4222,7 @@ bool AnimationTrackEditor::has_track(Node3D *p_node, const String &p_sub, const
if (!keying) {
return false;
}
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return false;
}
@@ -4140,7 +4279,18 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
// Let's build a node path.
String path = root->get_path_to(p_node, true);
- Variant value = p_node->get(p_property);
+ // Get the value from the subpath.
+ Variant value = p_node;
+ Vector<String> property_path = p_property.split(":");
+ for (const String &E : property_path) {
+ if (value.get_type() == Variant::OBJECT) {
+ Object *obj = value;
+ value = obj->get(E);
+ } else {
+ value = Variant();
+ break;
+ }
+ }
if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") {
if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) {
@@ -4230,6 +4380,22 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
_query_insert(id);
}
+PackedStringArray AnimationTrackEditor::get_selected_section() const {
+ return marker_edit->get_selected_section();
+}
+
+bool AnimationTrackEditor::is_marker_selected(const StringName &p_marker) const {
+ return marker_edit->is_marker_selected(p_marker);
+}
+
+bool AnimationTrackEditor::is_marker_moving_selection() const {
+ return marker_edit->is_moving_selection();
+}
+
+float AnimationTrackEditor::get_marker_moving_selection_offset() const {
+ return marker_edit->get_moving_selection_offset();
+}
+
void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) {
EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
@@ -4316,7 +4482,7 @@ void AnimationTrackEditor::_confirm_insert_list() {
PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val) {
r_base_path = NodePath();
- ERR_FAIL_COND_V(!animation.is_valid(), PropertyInfo());
+ ERR_FAIL_COND_V(animation.is_null(), PropertyInfo());
ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo());
if (!root) {
@@ -4769,6 +4935,7 @@ void AnimationTrackEditor::_update_tracks() {
g->set_root(root);
g->set_tooltip_text(tooltip);
g->set_timeline(timeline);
+ g->set_editor(this);
groups.push_back(g);
VBoxContainer *vb = memnew(VBoxContainer);
vb->add_theme_constant_override("separation", 0);
@@ -4860,23 +5027,23 @@ void AnimationTrackEditor::_snap_mode_changed(int p_mode) {
if (key_edit) {
key_edit->set_use_fps(use_fps);
}
- step->set_step(use_fps ? FPS_DECIMAL : SECOND_DECIMAL);
+ marker_edit->set_use_fps(use_fps);
+ // To ensure that the conversion results are consistent between serialization and load, the value is snapped with 0.0625 to be a rational number when FPS mode is used.
+ step->set_step(use_fps ? FPS_STEP_FRACTION : SECOND_DECIMAL);
_update_step_spinbox();
}
void AnimationTrackEditor::_update_step_spinbox() {
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return;
}
step->set_block_signals(true);
if (timeline->is_using_fps()) {
- if (animation->get_step() == 0) {
- step->set_value(0);
+ if (animation->get_step() == 0.0) {
+ step->set_value(0.0);
} else {
- // The value stored within tscn cannot restored the original FPS due to lack of precision,
- // so the value should be limited to integer.
- step->set_value(Math::round(1.0 / animation->get_step()));
+ step->set_value(1.0 / animation->get_step());
}
} else {
@@ -4978,6 +5145,7 @@ void AnimationTrackEditor::_notification(int p_what) {
void AnimationTrackEditor::_update_scroll(double) {
_redraw_tracks();
_redraw_groups();
+ marker_edit->queue_redraw();
}
void AnimationTrackEditor::_update_step(double p_new_step) {
@@ -4989,10 +5157,12 @@ void AnimationTrackEditor::_update_step(double p_new_step) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Change Animation Step"));
- float step_value = p_new_step;
+ double step_value = p_new_step;
if (timeline->is_using_fps()) {
if (step_value != 0.0) {
- step_value = 1.0 / step_value;
+ // step_value must also be less than or equal to 1000 to ensure that no error accumulates due to interactions with retrieving values from inner range.
+ step_value = 1.0 / MIN(1000.0, p_new_step);
+ ;
}
timeline->queue_redraw();
}
@@ -5253,6 +5423,8 @@ void AnimationTrackEditor::_timeline_value_changed(double) {
bezier_edit->queue_redraw();
bezier_edit->update_play_position();
+
+ marker_edit->update_play_position();
}
int AnimationTrackEditor::_get_track_selected() {
@@ -5445,6 +5617,8 @@ void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track)
_redraw_tracks();
_update_key_edit();
+
+ marker_edit->_clear_selection(marker_edit->is_selection_active());
}
void AnimationTrackEditor::_key_deselected(int p_key, int p_track) {
@@ -5513,7 +5687,7 @@ void AnimationTrackEditor::_clear_selection(bool p_update) {
void AnimationTrackEditor::_update_key_edit() {
_clear_key_edit();
- if (!animation.is_valid()) {
+ if (animation.is_null()) {
return;
}
@@ -5600,6 +5774,8 @@ void AnimationTrackEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_t
selection.insert(sk, ki);
_update_key_edit();
+
+ marker_edit->_clear_selection(marker_edit->is_selection_active());
}
void AnimationTrackEditor::_move_selection_commit() {
@@ -6200,8 +6376,8 @@ void AnimationTrackEditor::goto_prev_step(bool p_from_mouse_event) {
return;
}
float anim_step = animation->get_step();
- if (anim_step == 0) {
- anim_step = 1;
+ if (anim_step == 0.0) {
+ anim_step = 1.0;
}
if (p_from_mouse_event && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
// Use more precise snapping when holding Shift.
@@ -6211,8 +6387,8 @@ void AnimationTrackEditor::goto_prev_step(bool p_from_mouse_event) {
float pos = timeline->get_play_position();
pos = Math::snapped(pos - anim_step, anim_step);
- if (pos < 0) {
- pos = 0;
+ if (pos < 0.0) {
+ pos = 0.0;
}
set_anim_pos(pos);
_timeline_changed(pos, false);
@@ -6223,8 +6399,8 @@ void AnimationTrackEditor::goto_next_step(bool p_from_mouse_event, bool p_timeli
return;
}
float anim_step = animation->get_step();
- if (anim_step == 0) {
- anim_step = 1;
+ if (anim_step == 0.0) {
+ anim_step = 1.0;
}
if (p_from_mouse_event && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
// Use more precise snapping when holding Shift.
@@ -7311,6 +7487,15 @@ AnimationTrackEditor::AnimationTrackEditor() {
box_selection_container->set_clip_contents(true);
timeline_vbox->add_child(box_selection_container);
+ marker_edit = memnew(AnimationMarkerEdit);
+ timeline->get_child(0)->add_child(marker_edit);
+ marker_edit->set_editor(this);
+ marker_edit->set_timeline(timeline);
+ marker_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ marker_edit->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT);
+ marker_edit->connect(SceneStringName(draw), callable_mp(this, &AnimationTrackEditor::_redraw_groups));
+ marker_edit->connect(SceneStringName(draw), callable_mp(this, &AnimationTrackEditor::_redraw_tracks));
+
scroll = memnew(ScrollContainer);
box_selection_container->add_child(scroll);
scroll->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
@@ -7826,3 +8011,1203 @@ AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animat
AnimationTrackKeyEditEditor::~AnimationTrackKeyEditEditor() {
}
+
+void AnimationMarkerEdit::_zoom_changed() {
+ queue_redraw();
+ play_position->queue_redraw();
+}
+
+void AnimationMarkerEdit::_menu_selected(int p_index) {
+ switch (p_index) {
+ case MENU_KEY_INSERT: {
+ _insert_marker(insert_at_pos);
+ } break;
+ case MENU_KEY_RENAME: {
+ if (selection.size() > 0) {
+ _rename_marker(*selection.last());
+ }
+ } break;
+ case MENU_KEY_DELETE: {
+ _delete_selected_markers();
+ } break;
+ case MENU_KEY_TOGGLE_MARKER_NAMES: {
+ should_show_all_marker_names = !should_show_all_marker_names;
+ queue_redraw();
+ } break;
+ }
+}
+
+void AnimationMarkerEdit::_play_position_draw() {
+ if (animation.is_null() || play_position_pos < 0) {
+ return;
+ }
+
+ float scale = timeline->get_zoom_scale();
+ int h = get_size().height;
+
+ int px = (play_position_pos - timeline->get_value()) * scale + timeline->get_name_limit();
+
+ if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) {
+ Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
+ play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE));
+ }
+}
+
+bool AnimationMarkerEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {
+ int limit = timeline->get_name_limit();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+ // Left Border including space occupied by keyframes on t=0.
+ int limit_start_hitbox = limit - type_icon->get_width();
+
+ if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
+ int key_idx = -1;
+ float key_distance = 1e20;
+ PackedStringArray names = animation->get_marker_names();
+ for (int i = 0; i < names.size(); i++) {
+ Rect2 rect = const_cast<AnimationMarkerEdit *>(this)->get_key_rect(timeline->get_zoom_scale());
+ float offset = animation->get_marker_time(names[i]) - timeline->get_value();
+ offset = offset * timeline->get_zoom_scale() + limit;
+ rect.position.x += offset;
+ if (rect.has_point(p_pos)) {
+ if (const_cast<AnimationMarkerEdit *>(this)->is_key_selectable_by_distance()) {
+ float distance = Math::abs(offset - p_pos.x);
+ if (key_idx == -1 || distance < key_distance) {
+ key_idx = i;
+ key_distance = distance;
+ }
+ } else {
+ // First one does it.
+ break;
+ }
+ }
+ }
+
+ if (key_idx != -1) {
+ if (p_aggregate) {
+ StringName name = names[key_idx];
+ if (selection.has(name)) {
+ if (p_deselectable) {
+ call_deferred("_deselect_key", name);
+ moving_selection_pivot = 0.0f;
+ moving_selection_mouse_begin_x = 0.0f;
+ }
+ } else {
+ call_deferred("_select_key", name, false);
+ moving_selection_attempt = true;
+ moving_selection_effective = false;
+ select_single_attempt = StringName();
+ moving_selection_pivot = animation->get_marker_time(name);
+ moving_selection_mouse_begin_x = p_pos.x;
+ }
+
+ } else {
+ StringName name = names[key_idx];
+ if (!selection.has(name)) {
+ call_deferred("_select_key", name, true);
+ select_single_attempt = StringName();
+ } else {
+ select_single_attempt = name;
+ }
+
+ moving_selection_attempt = true;
+ moving_selection_effective = false;
+ moving_selection_pivot = animation->get_marker_time(name);
+ moving_selection_mouse_begin_x = p_pos.x;
+ }
+
+ if (read_only) {
+ moving_selection_attempt = false;
+ moving_selection_pivot = 0.0f;
+ moving_selection_mouse_begin_x = 0.0f;
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool AnimationMarkerEdit::_is_ui_pos_in_current_section(const Point2 &p_pos) {
+ int limit = timeline->get_name_limit();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+
+ if (p_pos.x >= limit && p_pos.x <= limit_end) {
+ PackedStringArray section = get_selected_section();
+ if (!section.is_empty()) {
+ StringName start_marker = section[0];
+ StringName end_marker = section[1];
+ float start_offset = (animation->get_marker_time(start_marker) - timeline->get_value()) * timeline->get_zoom_scale() + limit;
+ float end_offset = (animation->get_marker_time(end_marker) - timeline->get_value()) * timeline->get_zoom_scale() + limit;
+ return p_pos.x >= start_offset && p_pos.x <= end_offset;
+ }
+ }
+
+ return false;
+}
+
+HBoxContainer *AnimationMarkerEdit::_create_hbox_labeled_control(const String &p_text, Control *p_control) const {
+ HBoxContainer *hbox = memnew(HBoxContainer);
+ Label *label = memnew(Label);
+ label->set_text(p_text);
+ hbox->add_child(label);
+ hbox->add_child(p_control);
+ hbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ label->set_h_size_flags(SIZE_EXPAND_FILL);
+ label->set_stretch_ratio(1.0);
+ p_control->set_h_size_flags(SIZE_EXPAND_FILL);
+ p_control->set_stretch_ratio(1.0);
+ return hbox;
+}
+
+void AnimationMarkerEdit::_update_key_edit() {
+ _clear_key_edit();
+ if (animation.is_null()) {
+ return;
+ }
+
+ if (selection.size() == 1) {
+ key_edit = memnew(AnimationMarkerKeyEdit);
+ key_edit->animation = animation;
+ key_edit->animation_read_only = read_only;
+ key_edit->marker_name = *selection.begin();
+ key_edit->use_fps = timeline->is_using_fps();
+ key_edit->marker_edit = this;
+
+ EditorNode::get_singleton()->push_item(key_edit);
+
+ InspectorDock::get_singleton()->set_info(TTR("Marker name is read-only in the inspector."), TTR("A marker's name can only be changed by right-clicking it in the animation editor and selecting \"Rename Marker\", in order to make sure that marker names are all unique."), true);
+ } else if (selection.size() > 1) {
+ multi_key_edit = memnew(AnimationMultiMarkerKeyEdit);
+ multi_key_edit->animation = animation;
+ multi_key_edit->animation_read_only = read_only;
+ multi_key_edit->marker_edit = this;
+ for (const StringName &name : selection) {
+ multi_key_edit->marker_names.push_back(name);
+ }
+
+ EditorNode::get_singleton()->push_item(multi_key_edit);
+ }
+}
+
+void AnimationMarkerEdit::_clear_key_edit() {
+ if (key_edit) {
+ // If key edit is the object being inspected, remove it first.
+ if (InspectorDock::get_inspector_singleton()->get_edited_object() == key_edit) {
+ EditorNode::get_singleton()->push_item(nullptr);
+ }
+
+ // Then actually delete it.
+ memdelete(key_edit);
+ key_edit = nullptr;
+ }
+
+ if (multi_key_edit) {
+ if (InspectorDock::get_inspector_singleton()->get_edited_object() == multi_key_edit) {
+ EditorNode::get_singleton()->push_item(nullptr);
+ }
+
+ memdelete(multi_key_edit);
+ multi_key_edit = nullptr;
+ }
+}
+
+void AnimationMarkerEdit::_bind_methods() {
+ ClassDB::bind_method("_clear_selection_for_anim", &AnimationMarkerEdit::_clear_selection_for_anim);
+ ClassDB::bind_method("_select_key", &AnimationMarkerEdit::_select_key);
+ ClassDB::bind_method("_deselect_key", &AnimationMarkerEdit::_deselect_key);
+}
+
+void AnimationMarkerEdit::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ if (animation.is_null()) {
+ return;
+ }
+
+ type_icon = get_editor_theme_icon(SNAME("Marker"));
+ selected_icon = get_editor_theme_icon(SNAME("MarkerSelected"));
+ } break;
+
+ case NOTIFICATION_DRAW: {
+ if (animation.is_null()) {
+ return;
+ }
+
+ int limit = timeline->get_name_limit();
+
+ Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
+ Color color = get_theme_color(SceneStringName(font_color), SNAME("Label"));
+ int hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
+ Color linecolor = color;
+ linecolor.a = 0.2;
+
+ // SECTION PREVIEW //
+
+ {
+ float scale = timeline->get_zoom_scale();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+
+ PackedStringArray section = get_selected_section();
+ if (section.size() == 2) {
+ StringName start_marker = section[0];
+ StringName end_marker = section[1];
+ double start_time = animation->get_marker_time(start_marker);
+ double end_time = animation->get_marker_time(end_marker);
+
+ // When AnimationPlayer is playing, don't move the preview rect, so it still indicates the playback section.
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (moving_selection && !(player && player->is_playing())) {
+ start_time += moving_selection_offset;
+ end_time += moving_selection_offset;
+ }
+
+ if (start_time < animation->get_length() && end_time >= 0) {
+ float start_ofs = MAX(0, start_time) - timeline->get_value();
+ float end_ofs = MIN(animation->get_length(), end_time) - timeline->get_value();
+ start_ofs = start_ofs * scale + limit;
+ end_ofs = end_ofs * scale + limit;
+ start_ofs = MAX(start_ofs, limit);
+ end_ofs = MIN(end_ofs, limit_end);
+ Rect2 rect;
+ rect.set_position(Vector2(start_ofs, 0));
+ rect.set_size(Vector2(end_ofs - start_ofs, get_size().height));
+
+ draw_rect(rect, Color(1, 0.1, 0.1, 0.2));
+ }
+ }
+ }
+
+ // KEYFRAMES //
+
+ draw_bg(limit, get_size().width - timeline->get_buttons_width());
+
+ {
+ float scale = timeline->get_zoom_scale();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+
+ PackedStringArray names = animation->get_marker_names();
+ for (int i = 0; i < names.size(); i++) {
+ StringName name = names[i];
+ bool is_selected = selection.has(name);
+ float offset = animation->get_marker_time(name) - timeline->get_value();
+ if (is_selected && moving_selection) {
+ offset += moving_selection_offset;
+ }
+
+ offset = offset * scale + limit;
+
+ draw_key(name, scale, int(offset), is_selected, limit, limit_end);
+
+ const int font_size = 16;
+ Size2 string_size = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size);
+ if (int(offset) <= limit_end && int(offset) >= limit && should_show_all_marker_names) {
+ float bottom = get_size().height + string_size.y - font->get_descent(font_size);
+ float extrusion = MAX(0, offset + string_size.x - limit_end); // How much the string would extrude outside limit_end if unadjusted.
+ Color marker_color = animation->get_marker_color(name);
+ draw_string(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color);
+ draw_string_outline(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color);
+ }
+ }
+ }
+
+ draw_fg(limit, get_size().width - timeline->get_buttons_width());
+
+ // BUTTONS //
+
+ {
+ int ofs = get_size().width - timeline->get_buttons_width();
+
+ draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE));
+
+ ofs += hsep;
+ }
+
+ draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE));
+ } break;
+
+ case NOTIFICATION_MOUSE_ENTER:
+ hovered = true;
+ queue_redraw();
+ break;
+ case NOTIFICATION_MOUSE_EXIT:
+ hovered = false;
+ // When the mouse cursor exits the track, we're no longer hovering any keyframe.
+ hovering_marker = StringName();
+ queue_redraw();
+ break;
+ }
+}
+
+void AnimationMarkerEdit::gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
+ if (animation.is_null()) {
+ return;
+ }
+
+ if (p_event->is_pressed()) {
+ if (ED_IS_SHORTCUT("animation_marker_edit/rename_marker", p_event)) {
+ if (!read_only) {
+ _menu_selected(MENU_KEY_RENAME);
+ }
+ }
+
+ if (ED_IS_SHORTCUT("animation_marker_edit/delete_selection", p_event)) {
+ if (!read_only) {
+ _menu_selected(MENU_KEY_DELETE);
+ }
+ }
+
+ if (ED_IS_SHORTCUT("animation_marker_edit/toggle_marker_names", p_event)) {
+ if (!read_only) {
+ _menu_selected(MENU_KEY_TOGGLE_MARKER_NAMES);
+ }
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ Point2 pos = mb->get_position();
+ if (_try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), true)) {
+ accept_event();
+ } else if (!_is_ui_pos_in_current_section(pos)) {
+ _clear_selection_for_anim(animation);
+ }
+ }
+
+ if (mb.is_valid() && moving_selection_attempt) {
+ if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ moving_selection_attempt = false;
+ if (moving_selection && moving_selection_effective) {
+ if (Math::abs(moving_selection_offset) > CMP_EPSILON) {
+ _move_selection_commit();
+ accept_event(); // So play position doesn't snap to the end of move selection.
+ }
+ } else if (select_single_attempt) {
+ call_deferred("_select_key", select_single_attempt, true);
+
+ // First select click should not affect play position.
+ if (!selection.has(select_single_attempt)) {
+ accept_event();
+ } else {
+ // Second click and onwards should snap to marker time.
+ double ofs = animation->get_marker_time(select_single_attempt);
+ timeline->set_play_position(ofs);
+ timeline->emit_signal(SNAME("timeline_changed"), ofs, mb->is_alt_pressed());
+ accept_event();
+ }
+ } else {
+ // First select click should not affect play position.
+ if (!selection.has(select_single_attempt)) {
+ accept_event();
+ }
+ }
+
+ moving_selection = false;
+ select_single_attempt = StringName();
+ }
+
+ if (moving_selection && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
+ moving_selection_attempt = false;
+ moving_selection = false;
+ _move_selection_cancel();
+ }
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
+ Point2 pos = mb->get_position();
+ if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
+ // Can do something with menu too! show insert key.
+ float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
+ if (!read_only) {
+ bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false);
+
+ menu->clear();
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Marker..."), MENU_KEY_INSERT);
+
+ if (selected || selection.size() > 0) {
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Edit")), TTR("Rename Marker"), MENU_KEY_RENAME);
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Marker(s)"), MENU_KEY_DELETE);
+ }
+
+ menu->add_icon_item(get_editor_theme_icon(should_show_all_marker_names ? SNAME("GuiChecked") : SNAME("GuiUnchecked")), TTR("Show All Marker Names"), MENU_KEY_TOGGLE_MARKER_NAMES);
+ menu->reset_size();
+
+ moving_selection_attempt = false;
+ moving_selection = false;
+
+ menu->set_position(get_screen_position() + get_local_mouse_position());
+ menu->popup();
+
+ insert_at_pos = offset + timeline->get_value();
+ accept_event();
+ }
+ }
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid()) {
+ const StringName previous_hovering_marker = hovering_marker;
+
+ // Hovering compressed keyframes for editing is not possible.
+ const float scale = timeline->get_zoom_scale();
+ const int limit = timeline->get_name_limit();
+ const int limit_end = get_size().width - timeline->get_buttons_width();
+ // Left Border including space occupied by keyframes on t=0.
+ const int limit_start_hitbox = limit - type_icon->get_width();
+ const Point2 pos = mm->get_position();
+
+ if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
+ // Use the same logic as key selection to ensure that hovering accurately represents
+ // which key will be selected when clicking.
+ int key_idx = -1;
+ float key_distance = 1e20;
+
+ hovering_marker = StringName();
+
+ PackedStringArray names = animation->get_marker_names();
+
+ // Hovering should happen in the opposite order of drawing for more accurate overlap hovering.
+ for (int i = names.size() - 1; i >= 0; i--) {
+ StringName name = names[i];
+ Rect2 rect = get_key_rect(scale);
+ float offset = animation->get_marker_time(name) - timeline->get_value();
+ offset = offset * scale + limit;
+ rect.position.x += offset;
+
+ if (rect.has_point(pos)) {
+ if (is_key_selectable_by_distance()) {
+ const float distance = Math::abs(offset - pos.x);
+ if (key_idx == -1 || distance < key_distance) {
+ key_idx = i;
+ key_distance = distance;
+ hovering_marker = name;
+ }
+ } else {
+ // First one does it.
+ hovering_marker = name;
+ break;
+ }
+ }
+ }
+
+ if (hovering_marker != previous_hovering_marker) {
+ // Required to draw keyframe hover feedback on the correct keyframe.
+ queue_redraw();
+ }
+ }
+ }
+
+ if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && moving_selection_attempt) {
+ if (!moving_selection) {
+ moving_selection = true;
+ _move_selection_begin();
+ }
+
+ float moving_begin_time = ((moving_selection_mouse_begin_x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+ float new_time = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value();
+ float delta = new_time - moving_begin_time;
+ float snapped_time = editor->snap_time(moving_selection_pivot + delta);
+
+ float offset = 0.0;
+ if (Math::abs(editor->get_moving_selection_offset()) > CMP_EPSILON || (snapped_time > moving_selection_pivot && delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && delta < -CMP_EPSILON)) {
+ offset = snapped_time - moving_selection_pivot;
+ moving_selection_effective = true;
+ }
+
+ _move_selection(offset);
+ }
+}
+
+String AnimationMarkerEdit::get_tooltip(const Point2 &p_pos) const {
+ if (animation.is_null()) {
+ return Control::get_tooltip(p_pos);
+ }
+
+ int limit = timeline->get_name_limit();
+ int limit_end = get_size().width - timeline->get_buttons_width();
+ // Left Border including space occupied by keyframes on t=0.
+ int limit_start_hitbox = limit - type_icon->get_width();
+
+ if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
+ int key_idx = -1;
+ float key_distance = 1e20;
+
+ PackedStringArray names = animation->get_marker_names();
+
+ // Select should happen in the opposite order of drawing for more accurate overlap select.
+ for (int i = names.size() - 1; i >= 0; i--) {
+ StringName name = names[i];
+ Rect2 rect = const_cast<AnimationMarkerEdit *>(this)->get_key_rect(timeline->get_zoom_scale());
+ float offset = animation->get_marker_time(name) - timeline->get_value();
+ offset = offset * timeline->get_zoom_scale() + limit;
+ rect.position.x += offset;
+
+ if (rect.has_point(p_pos)) {
+ if (const_cast<AnimationMarkerEdit *>(this)->is_key_selectable_by_distance()) {
+ float distance = ABS(offset - p_pos.x);
+ if (key_idx == -1 || distance < key_distance) {
+ key_idx = i;
+ key_distance = distance;
+ }
+ } else {
+ // First one does it.
+ break;
+ }
+ }
+ }
+
+ if (key_idx != -1) {
+ String name = names[key_idx];
+ String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->get_marker_time(name), 0.0001))) + "\n";
+ text += TTR("Marker:") + " " + name + "\n";
+ return text;
+ }
+ }
+
+ return Control::get_tooltip(p_pos);
+}
+
+int AnimationMarkerEdit::get_key_height() const {
+ if (animation.is_null()) {
+ return 0;
+ }
+
+ return type_icon->get_height();
+}
+
+Rect2 AnimationMarkerEdit::get_key_rect(float p_pixels_sec) const {
+ if (animation.is_null()) {
+ return Rect2();
+ }
+
+ Rect2 rect = Rect2(-type_icon->get_width() / 2, get_size().height - type_icon->get_size().height, type_icon->get_width(), type_icon->get_size().height);
+
+ // Make it a big easier to click.
+ rect.position.x -= rect.size.x * 0.5;
+ rect.size.x *= 2;
+ return rect;
+}
+
+PackedStringArray AnimationMarkerEdit::get_selected_section() const {
+ if (selection.size() >= 2) {
+ PackedStringArray arr;
+ arr.push_back(""); // Marker with smallest time.
+ arr.push_back(""); // Marker with largest time.
+ double min_time = INFINITY;
+ double max_time = -INFINITY;
+ for (const StringName &marker_name : selection) {
+ double time = animation->get_marker_time(marker_name);
+ if (time < min_time) {
+ arr.set(0, marker_name);
+ min_time = time;
+ }
+ if (time > max_time) {
+ arr.set(1, marker_name);
+ max_time = time;
+ }
+ }
+ return arr;
+ }
+
+ return PackedStringArray();
+}
+
+bool AnimationMarkerEdit::is_marker_selected(const StringName &p_marker) const {
+ return selection.has(p_marker);
+}
+
+bool AnimationMarkerEdit::is_key_selectable_by_distance() const {
+ return true;
+}
+
+void AnimationMarkerEdit::draw_key(const StringName &p_name, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) {
+ if (animation.is_null()) {
+ return;
+ }
+
+ if (p_x < p_clip_left || p_x > p_clip_right) {
+ return;
+ }
+
+ Ref<Texture2D> icon_to_draw = p_selected ? selected_icon : type_icon;
+
+ Vector2 ofs(p_x - icon_to_draw->get_width() / 2, int(get_size().height - icon_to_draw->get_height()));
+
+ // Don't apply custom marker color when the key is selected.
+ Color marker_color = p_selected ? Color(1, 1, 1) : animation->get_marker_color(p_name);
+
+ // Use a different color for the currently hovered key.
+ // The color multiplier is chosen to work with both dark and light editor themes,
+ // and on both unselected and selected key icons.
+ draw_texture(
+ icon_to_draw,
+ ofs,
+ p_name == hovering_marker ? get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")) : marker_color);
+}
+
+void AnimationMarkerEdit::draw_bg(int p_clip_left, int p_clip_right) {
+}
+
+void AnimationMarkerEdit::draw_fg(int p_clip_left, int p_clip_right) {
+}
+
+Ref<Animation> AnimationMarkerEdit::get_animation() const {
+ return animation;
+}
+
+void AnimationMarkerEdit::set_animation(const Ref<Animation> &p_animation, bool p_read_only) {
+ if (animation.is_valid()) {
+ _clear_selection_for_anim(animation);
+ }
+ animation = p_animation;
+ read_only = p_read_only;
+ type_icon = get_editor_theme_icon(SNAME("Marker"));
+ selected_icon = get_editor_theme_icon(SNAME("MarkerSelected"));
+
+ queue_redraw();
+}
+
+Size2 AnimationMarkerEdit::get_minimum_size() const {
+ Ref<Texture2D> texture = get_editor_theme_icon(SNAME("Object"));
+ Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
+ int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
+ int separation = get_theme_constant(SNAME("v_separation"), SNAME("ItemList"));
+
+ int max_h = MAX(texture->get_height(), font->get_height(font_size));
+ max_h = MAX(max_h, get_key_height());
+
+ return Vector2(1, max_h + separation);
+}
+
+void AnimationMarkerEdit::set_timeline(AnimationTimelineEdit *p_timeline) {
+ timeline = p_timeline;
+ timeline->connect("zoom_changed", callable_mp(this, &AnimationMarkerEdit::_zoom_changed));
+ timeline->connect("name_limit_changed", callable_mp(this, &AnimationMarkerEdit::_zoom_changed));
+}
+
+void AnimationMarkerEdit::set_editor(AnimationTrackEditor *p_editor) {
+ editor = p_editor;
+}
+
+void AnimationMarkerEdit::set_play_position(float p_pos) {
+ play_position_pos = p_pos;
+ play_position->queue_redraw();
+}
+
+void AnimationMarkerEdit::update_play_position() {
+ play_position->queue_redraw();
+}
+
+void AnimationMarkerEdit::set_use_fps(bool p_use_fps) {
+ if (key_edit) {
+ key_edit->use_fps = p_use_fps;
+ key_edit->notify_property_list_changed();
+ }
+}
+
+void AnimationMarkerEdit::_move_selection_begin() {
+ moving_selection = true;
+ moving_selection_offset = 0;
+}
+
+void AnimationMarkerEdit::_move_selection(float p_offset) {
+ moving_selection_offset = p_offset;
+ queue_redraw();
+}
+
+void AnimationMarkerEdit::_move_selection_commit() {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Animation Move Markers"));
+
+ for (HashSet<StringName>::Iterator E = selection.last(); E; --E) {
+ StringName name = *E;
+ double time = animation->get_marker_time(name);
+ float newpos = time + moving_selection_offset;
+ undo_redo->add_do_method(animation.ptr(), "remove_marker", name);
+ undo_redo->add_do_method(animation.ptr(), "add_marker", name, newpos);
+ undo_redo->add_do_method(animation.ptr(), "set_marker_color", name, animation->get_marker_color(name));
+ undo_redo->add_undo_method(animation.ptr(), "remove_marker", name);
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", name, time);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", name, animation->get_marker_color(name));
+
+ // add_marker will overwrite the overlapped key on the redo pass, so we add it back on the undo pass.
+ if (StringName overlap = animation->get_marker_at_time(newpos)) {
+ if (select_single_attempt == overlap) {
+ select_single_attempt = "";
+ }
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", overlap, newpos);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", overlap, animation->get_marker_color(overlap));
+ }
+ }
+
+ moving_selection = false;
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (player) {
+ PackedStringArray selected_section = get_selected_section();
+ if (selected_section.size() >= 2) {
+ undo_redo->add_do_method(player, "set_section_with_markers", selected_section[0], selected_section[1]);
+ undo_redo->add_undo_method(player, "set_section_with_markers", selected_section[0], selected_section[1]);
+ }
+ }
+ undo_redo->add_do_method(timeline, "queue_redraw");
+ undo_redo->add_undo_method(timeline, "queue_redraw");
+ undo_redo->add_do_method(this, "queue_redraw");
+ undo_redo->add_undo_method(this, "queue_redraw");
+ undo_redo->commit_action();
+ _update_key_edit();
+}
+
+void AnimationMarkerEdit::_delete_selected_markers() {
+ if (selection.size()) {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Animation Delete Keys"));
+ for (const StringName &name : selection) {
+ double time = animation->get_marker_time(name);
+ undo_redo->add_do_method(animation.ptr(), "remove_marker", name);
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", name, time);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", name, animation->get_marker_color(name));
+ }
+ _clear_selection_for_anim(animation);
+
+ undo_redo->add_do_method(this, "queue_redraw");
+ undo_redo->add_undo_method(this, "queue_redraw");
+ undo_redo->commit_action();
+ _update_key_edit();
+ }
+}
+
+void AnimationMarkerEdit::_move_selection_cancel() {
+ moving_selection = false;
+ queue_redraw();
+}
+
+void AnimationMarkerEdit::_clear_selection(bool p_update) {
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (player) {
+ player->reset_section();
+ }
+
+ selection.clear();
+
+ if (p_update) {
+ queue_redraw();
+ }
+
+ _clear_key_edit();
+}
+
+void AnimationMarkerEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
+ if (animation != p_anim) {
+ return;
+ }
+
+ _clear_selection(true);
+}
+
+void AnimationMarkerEdit::_select_key(const StringName &p_name, bool is_single) {
+ if (is_single) {
+ _clear_selection(false);
+ }
+
+ selection.insert(p_name);
+
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (player) {
+ if (selection.size() >= 2) {
+ PackedStringArray selected_section = get_selected_section();
+ double start_time = animation->get_marker_time(selected_section[0]);
+ double end_time = animation->get_marker_time(selected_section[1]);
+ player->set_section(start_time, end_time);
+ } else {
+ player->reset_section();
+ }
+ }
+
+ queue_redraw();
+ _update_key_edit();
+
+ editor->_clear_selection(editor->is_selection_active());
+}
+
+void AnimationMarkerEdit::_deselect_key(const StringName &p_name) {
+ selection.erase(p_name);
+
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+ if (player) {
+ if (selection.size() >= 2) {
+ PackedStringArray selected_section = get_selected_section();
+ double start_time = animation->get_marker_time(selected_section[0]);
+ double end_time = animation->get_marker_time(selected_section[1]);
+ player->set_section(start_time, end_time);
+ } else {
+ player->reset_section();
+ }
+ }
+
+ queue_redraw();
+ _update_key_edit();
+}
+
+void AnimationMarkerEdit::_insert_marker(float p_ofs) {
+ if (editor->is_snap_timeline_enabled()) {
+ p_ofs = editor->snap_time(p_ofs);
+ }
+
+ marker_insert_confirm->popup_centered(Size2(200, 100) * EDSCALE);
+ marker_insert_color->set_pick_color(Color(1, 1, 1));
+
+ String base = "new_marker";
+ int count = 1;
+ while (true) {
+ String attempt = base;
+ if (count > 1) {
+ attempt += vformat("_%d", count);
+ }
+ if (animation->has_marker(attempt)) {
+ count++;
+ continue;
+ }
+ base = attempt;
+ break;
+ }
+
+ marker_insert_new_name->set_text(base);
+ _marker_insert_new_name_changed(base);
+ marker_insert_ofs = p_ofs;
+}
+
+void AnimationMarkerEdit::_rename_marker(const StringName &p_name) {
+ marker_rename_confirm->popup_centered(Size2i(200, 0) * EDSCALE);
+ marker_rename_prev_name = p_name;
+ marker_rename_new_name->set_text(p_name);
+}
+
+void AnimationMarkerEdit::_marker_insert_confirmed() {
+ StringName name = marker_insert_new_name->get_text();
+
+ if (animation->has_marker(name)) {
+ marker_insert_error_dialog->set_text(vformat(TTR("Marker '%s' already exists!"), name));
+ marker_insert_error_dialog->popup_centered();
+ return;
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+ undo_redo->create_action(TTR("Add Marker Key"));
+ undo_redo->add_do_method(animation.ptr(), "add_marker", name, marker_insert_ofs);
+ undo_redo->add_undo_method(animation.ptr(), "remove_marker", name);
+ StringName existing_marker = animation->get_marker_at_time(marker_insert_ofs);
+ if (existing_marker) {
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", existing_marker, marker_insert_ofs);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", existing_marker, animation->get_marker_color(existing_marker));
+ }
+ undo_redo->add_do_method(animation.ptr(), "set_marker_color", name, marker_insert_color->get_pick_color());
+
+ undo_redo->add_do_method(this, "queue_redraw");
+ undo_redo->add_undo_method(this, "queue_redraw");
+
+ undo_redo->commit_action();
+
+ marker_insert_confirm->hide();
+}
+
+void AnimationMarkerEdit::_marker_insert_new_name_changed(const String &p_text) {
+ marker_insert_confirm->get_ok_button()->set_disabled(p_text.is_empty());
+}
+
+void AnimationMarkerEdit::_marker_rename_confirmed() {
+ StringName new_name = marker_rename_new_name->get_text();
+ StringName prev_name = marker_rename_prev_name;
+
+ if (new_name == StringName()) {
+ marker_rename_error_dialog->set_text(TTR("Empty marker names are not allowed."));
+ marker_rename_error_dialog->popup_centered();
+ return;
+ }
+
+ if (new_name != prev_name && animation->has_marker(new_name)) {
+ marker_rename_error_dialog->set_text(vformat(TTR("Marker '%s' already exists!"), new_name));
+ marker_rename_error_dialog->popup_centered();
+ return;
+ }
+
+ if (prev_name != new_name) {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Rename Marker"));
+ undo_redo->add_do_method(animation.ptr(), "remove_marker", prev_name);
+ undo_redo->add_do_method(animation.ptr(), "add_marker", new_name, animation->get_marker_time(prev_name));
+ undo_redo->add_do_method(animation.ptr(), "set_marker_color", new_name, animation->get_marker_color(prev_name));
+ undo_redo->add_undo_method(animation.ptr(), "remove_marker", new_name);
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", prev_name, animation->get_marker_time(prev_name));
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", prev_name, animation->get_marker_color(prev_name));
+ undo_redo->add_do_method(this, "_select_key", new_name, true);
+ undo_redo->add_undo_method(this, "_select_key", prev_name, true);
+ undo_redo->commit_action();
+ select_single_attempt = StringName();
+ }
+ marker_rename_confirm->hide();
+}
+
+void AnimationMarkerEdit::_marker_rename_new_name_changed(const String &p_text) {
+ marker_rename_confirm->get_ok_button()->set_disabled(p_text.is_empty());
+}
+
+AnimationMarkerEdit::AnimationMarkerEdit() {
+ play_position = memnew(Control);
+ play_position->set_mouse_filter(MOUSE_FILTER_PASS);
+ add_child(play_position);
+ play_position->connect(SceneStringName(draw), callable_mp(this, &AnimationMarkerEdit::_play_position_draw));
+ set_focus_mode(FOCUS_CLICK);
+ set_mouse_filter(MOUSE_FILTER_PASS); // Scroll has to work too for selection.
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationMarkerEdit::_menu_selected));
+ menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/rename_marker", TTR("Rename Marker"), Key::R), MENU_KEY_RENAME);
+ menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/delete_selection", TTR("Delete Markers (s)"), Key::KEY_DELETE), MENU_KEY_DELETE);
+ menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/toggle_marker_names", TTR("Show All Marker Names"), Key::M), MENU_KEY_TOGGLE_MARKER_NAMES);
+
+ marker_insert_confirm = memnew(ConfirmationDialog);
+ marker_insert_confirm->set_title(TTR("Insert Marker"));
+ marker_insert_confirm->set_hide_on_ok(false);
+ marker_insert_confirm->connect(SceneStringName(confirmed), callable_mp(this, &AnimationMarkerEdit::_marker_insert_confirmed));
+ add_child(marker_insert_confirm);
+ VBoxContainer *marker_insert_vbox = memnew(VBoxContainer);
+ marker_insert_vbox->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT);
+ marker_insert_confirm->add_child(marker_insert_vbox);
+ marker_insert_new_name = memnew(LineEdit);
+ marker_insert_new_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationMarkerEdit::_marker_insert_new_name_changed));
+ marker_insert_confirm->register_text_enter(marker_insert_new_name);
+ marker_insert_vbox->add_child(_create_hbox_labeled_control(TTR("Marker Name"), marker_insert_new_name));
+ marker_insert_color = memnew(ColorPickerButton);
+ marker_insert_color->set_edit_alpha(false);
+ marker_insert_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(marker_insert_color->get_picker()));
+ marker_insert_vbox->add_child(_create_hbox_labeled_control(TTR("Marker Color"), marker_insert_color));
+ marker_insert_error_dialog = memnew(AcceptDialog);
+ marker_insert_error_dialog->set_ok_button_text(TTR("Close"));
+ marker_insert_error_dialog->set_title(TTR("Error!"));
+ marker_insert_confirm->add_child(marker_insert_error_dialog);
+
+ marker_rename_confirm = memnew(ConfirmationDialog);
+ marker_rename_confirm->set_title(TTR("Rename Marker"));
+ marker_rename_confirm->set_hide_on_ok(false);
+ marker_rename_confirm->connect(SceneStringName(confirmed), callable_mp(this, &AnimationMarkerEdit::_marker_rename_confirmed));
+ add_child(marker_rename_confirm);
+ VBoxContainer *marker_rename_vbox = memnew(VBoxContainer);
+ marker_rename_vbox->set_anchors_and_offsets_preset(Control::LayoutPreset::PRESET_FULL_RECT);
+ marker_rename_confirm->add_child(marker_rename_vbox);
+ Label *marker_rename_new_name_label = memnew(Label);
+ marker_rename_new_name_label->set_text(TTR("Change Marker Name:"));
+ marker_rename_vbox->add_child(marker_rename_new_name_label);
+ marker_rename_new_name = memnew(LineEdit);
+ marker_rename_new_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationMarkerEdit::_marker_rename_new_name_changed));
+ marker_rename_confirm->register_text_enter(marker_rename_new_name);
+ marker_rename_vbox->add_child(marker_rename_new_name);
+
+ marker_rename_error_dialog = memnew(AcceptDialog);
+ marker_rename_error_dialog->set_ok_button_text(TTR("Close"));
+ marker_rename_error_dialog->set_title(TTR("Error!"));
+ marker_rename_confirm->add_child(marker_rename_error_dialog);
+}
+
+AnimationMarkerEdit::~AnimationMarkerEdit() {
+}
+
+float AnimationMarkerKeyEdit::get_time() const {
+ return animation->get_marker_time(marker_name);
+}
+
+void AnimationMarkerKeyEdit::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMarkerKeyEdit::_hide_script_from_inspector);
+ ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMarkerKeyEdit::_hide_metadata_from_inspector);
+ ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMarkerKeyEdit::_dont_undo_redo);
+ ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMarkerKeyEdit::_is_read_only);
+ ClassDB::bind_method(D_METHOD("_set_marker_name"), &AnimationMarkerKeyEdit::_set_marker_name);
+}
+
+void AnimationMarkerKeyEdit::_set_marker_name(const StringName &p_name) {
+ marker_name = p_name;
+}
+
+bool AnimationMarkerKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+
+ if (p_name == "color") {
+ Color color = p_value;
+ Color prev_color = animation->get_marker_color(marker_name);
+ if (color != prev_color) {
+ undo_redo->create_action(TTR("Edit Marker Color"), UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(animation.ptr(), "set_marker_color", marker_name, color);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", marker_name, prev_color);
+ undo_redo->add_do_method(marker_edit, "queue_redraw");
+ undo_redo->add_undo_method(marker_edit, "queue_redraw");
+ undo_redo->commit_action();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationMarkerKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "name") {
+ r_ret = marker_name;
+ return true;
+ }
+
+ if (p_name == "color") {
+ r_ret = animation->get_marker_color(marker_name);
+ return true;
+ }
+
+ return false;
+}
+
+void AnimationMarkerKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (animation.is_null()) {
+ return;
+ }
+
+ p_list->push_back(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_EDITOR));
+ p_list->push_back(PropertyInfo(Variant::COLOR, "color", PROPERTY_HINT_COLOR_NO_ALPHA));
+}
+
+void AnimationMultiMarkerKeyEdit::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiMarkerKeyEdit::_hide_script_from_inspector);
+ ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMultiMarkerKeyEdit::_hide_metadata_from_inspector);
+ ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiMarkerKeyEdit::_dont_undo_redo);
+ ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMultiMarkerKeyEdit::_is_read_only);
+}
+
+bool AnimationMultiMarkerKeyEdit::_set(const StringName &p_name, const Variant &p_value) {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ if (p_name == "color") {
+ Color color = p_value;
+
+ undo_redo->create_action(TTR("Multi Edit Marker Color"), UndoRedo::MERGE_ENDS);
+
+ for (const StringName &marker_name : marker_names) {
+ undo_redo->add_do_method(animation.ptr(), "set_marker_color", marker_name, color);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", marker_name, animation->get_marker_color(marker_name));
+ }
+
+ undo_redo->add_do_method(marker_edit, "queue_redraw");
+ undo_redo->add_undo_method(marker_edit, "queue_redraw");
+ undo_redo->commit_action();
+
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationMultiMarkerKeyEdit::_get(const StringName &p_name, Variant &r_ret) const {
+ if (p_name == "color") {
+ r_ret = animation->get_marker_color(marker_names[0]);
+ return true;
+ }
+
+ return false;
+}
+
+void AnimationMultiMarkerKeyEdit::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (animation.is_null()) {
+ return;
+ }
+
+ p_list->push_back(PropertyInfo(Variant::COLOR, "color", PROPERTY_HINT_COLOR_NO_ALPHA));
+}
+
+// AnimationMarkerKeyEditEditorPlugin
+
+void AnimationMarkerKeyEditEditor::_time_edit_entered() {
+}
+
+void AnimationMarkerKeyEditEditor::_time_edit_exited() {
+ real_t new_time = spinner->get_value();
+
+ if (use_fps) {
+ real_t fps = animation->get_step();
+ if (fps > 0) {
+ fps = 1.0 / fps;
+ }
+ new_time /= fps;
+ }
+
+ real_t prev_time = animation->get_marker_time(marker_name);
+
+ if (Math::is_equal_approx(new_time, prev_time)) {
+ return; // No change.
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Animation Change Marker Time"), UndoRedo::MERGE_ENDS);
+
+ Color color = animation->get_marker_color(marker_name);
+ undo_redo->add_do_method(animation.ptr(), "add_marker", marker_name, new_time);
+ undo_redo->add_do_method(animation.ptr(), "set_marker_color", marker_name, color);
+ undo_redo->add_undo_method(animation.ptr(), "remove_marker", marker_name);
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", marker_name, prev_time);
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", marker_name, color);
+ StringName existing_marker = animation->get_marker_at_time(new_time);
+ if (existing_marker) {
+ undo_redo->add_undo_method(animation.ptr(), "add_marker", existing_marker, animation->get_marker_time(existing_marker));
+ undo_redo->add_undo_method(animation.ptr(), "set_marker_color", existing_marker, animation->get_marker_color(existing_marker));
+ }
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ AnimationTrackEditor *ate = ape->get_track_editor();
+ if (ate) {
+ AnimationMarkerEdit *ame = ate->marker_edit;
+ undo_redo->add_do_method(ame, "queue_redraw");
+ undo_redo->add_undo_method(ame, "queue_redraw");
+ }
+ }
+ undo_redo->commit_action();
+}
+
+AnimationMarkerKeyEditEditor::AnimationMarkerKeyEditEditor(Ref<Animation> p_animation, const StringName &p_name, bool p_use_fps) {
+ if (p_animation.is_null()) {
+ return;
+ }
+
+ animation = p_animation;
+ use_fps = p_use_fps;
+ marker_name = p_name;
+
+ set_label("Time");
+
+ spinner = memnew(EditorSpinSlider);
+ spinner->set_focus_mode(Control::FOCUS_CLICK);
+ spinner->set_min(0);
+ spinner->set_allow_greater(true);
+ spinner->set_allow_lesser(true);
+
+ float time = animation->get_marker_time(marker_name);
+
+ if (use_fps) {
+ spinner->set_step(FPS_DECIMAL);
+ real_t fps = animation->get_step();
+ if (fps > 0) {
+ fps = 1.0 / fps;
+ }
+ spinner->set_value(time * fps);
+ } else {
+ spinner->set_step(SECOND_DECIMAL);
+ spinner->set_value(time);
+ spinner->set_max(animation->get_length());
+ }
+
+ add_child(spinner);
+
+ spinner->connect("grabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED);
+ spinner->connect("ungrabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
+ spinner->connect("value_focus_entered", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED);
+ spinner->connect("value_focus_exited", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED);
+}
+
+AnimationMarkerKeyEditEditor::~AnimationMarkerKeyEditEditor() {
+}
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 6b9140ddaa..0da474afd4 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -41,9 +41,11 @@
#include "scene/gui/tree.h"
#include "scene/resources/animation.h"
+class AnimationMarkerEdit;
class AnimationTrackEditor;
class AnimationTrackEdit;
class CheckBox;
+class ColorPickerButton;
class EditorSpinSlider;
class HSlider;
class OptionButton;
@@ -52,6 +54,7 @@ class SceneTreeDialog;
class SpinBox;
class TextureRect;
class ViewPanner;
+class EditorValidationPanel;
class AnimationTrackKeyEdit : public Object {
GDCLASS(AnimationTrackKeyEdit, Object);
@@ -128,6 +131,58 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
};
+class AnimationMarkerKeyEdit : public Object {
+ GDCLASS(AnimationMarkerKeyEdit, Object);
+
+public:
+ bool animation_read_only = false;
+
+ Ref<Animation> animation;
+ StringName marker_name;
+ bool use_fps = false;
+
+ AnimationMarkerEdit *marker_edit = nullptr;
+
+ bool _hide_script_from_inspector() { return true; }
+ bool _hide_metadata_from_inspector() { return true; }
+ bool _dont_undo_redo() { return true; }
+
+ bool _is_read_only() { return animation_read_only; }
+
+ float get_time() const;
+
+protected:
+ static void _bind_methods();
+ void _set_marker_name(const StringName &p_name);
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+};
+
+class AnimationMultiMarkerKeyEdit : public Object {
+ GDCLASS(AnimationMultiMarkerKeyEdit, Object);
+
+public:
+ bool animation_read_only = false;
+
+ Ref<Animation> animation;
+ Vector<StringName> marker_names;
+
+ AnimationMarkerEdit *marker_edit = nullptr;
+
+ bool _hide_script_from_inspector() { return true; }
+ bool _hide_metadata_from_inspector() { return true; }
+ bool _dont_undo_redo() { return true; }
+
+ bool _is_read_only() { return animation_read_only; }
+
+protected:
+ static void _bind_methods();
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+};
+
class AnimationTimelineEdit : public Range {
GDCLASS(AnimationTimelineEdit, Range);
@@ -218,6 +273,140 @@ public:
AnimationTimelineEdit();
};
+class AnimationMarkerEdit : public Control {
+ GDCLASS(AnimationMarkerEdit, Control);
+ friend class AnimationTimelineEdit;
+
+ enum {
+ MENU_KEY_INSERT,
+ MENU_KEY_RENAME,
+ MENU_KEY_DELETE,
+ MENU_KEY_TOGGLE_MARKER_NAMES,
+ };
+
+ AnimationTimelineEdit *timeline = nullptr;
+ Control *play_position = nullptr; // Separate control used to draw so updates for only position changed are much faster.
+ float play_position_pos = 0.0f;
+
+ HashSet<StringName> selection;
+
+ Ref<Animation> animation;
+ bool read_only = false;
+
+ Ref<Texture2D> type_icon;
+ Ref<Texture2D> selected_icon;
+
+ PopupMenu *menu = nullptr;
+
+ bool hovered = false;
+ StringName hovering_marker;
+
+ void _zoom_changed();
+
+ Ref<Texture2D> icon_cache;
+
+ void _menu_selected(int p_index);
+
+ void _play_position_draw();
+ bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
+ bool _is_ui_pos_in_current_section(const Point2 &p_pos);
+
+ float insert_at_pos = 0.0f;
+ bool moving_selection_attempt = false;
+ bool moving_selection_effective = false;
+ float moving_selection_offset = 0.0f;
+ float moving_selection_pivot = 0.0f;
+ float moving_selection_mouse_begin_x = 0.0f;
+ float moving_selection_mouse_begin_y = 0.0f;
+ StringName select_single_attempt;
+ bool moving_selection = false;
+ void _move_selection_begin();
+ void _move_selection(float p_offset);
+ void _move_selection_commit();
+ void _move_selection_cancel();
+
+ void _clear_selection_for_anim(const Ref<Animation> &p_anim);
+ void _select_key(const StringName &p_name, bool is_single = false);
+ void _deselect_key(const StringName &p_name);
+
+ void _insert_marker(float p_ofs);
+ void _rename_marker(const StringName &p_name);
+ void _delete_selected_markers();
+
+ ConfirmationDialog *marker_insert_confirm = nullptr;
+ LineEdit *marker_insert_new_name = nullptr;
+ ColorPickerButton *marker_insert_color = nullptr;
+ AcceptDialog *marker_insert_error_dialog = nullptr;
+ float marker_insert_ofs = 0;
+
+ ConfirmationDialog *marker_rename_confirm = nullptr;
+ LineEdit *marker_rename_new_name = nullptr;
+ StringName marker_rename_prev_name;
+
+ AcceptDialog *marker_rename_error_dialog = nullptr;
+
+ bool should_show_all_marker_names = false;
+
+ ////////////// edit menu stuff
+
+ void _marker_insert_confirmed();
+ void _marker_insert_new_name_changed(const String &p_text);
+ void _marker_rename_confirmed();
+ void _marker_rename_new_name_changed(const String &p_text);
+
+ AnimationTrackEditor *editor = nullptr;
+
+ HBoxContainer *_create_hbox_labeled_control(const String &p_text, Control *p_control) const;
+
+ void _update_key_edit();
+ void _clear_key_edit();
+
+ AnimationMarkerKeyEdit *key_edit = nullptr;
+ AnimationMultiMarkerKeyEdit *multi_key_edit = nullptr;
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+ virtual void gui_input(const Ref<InputEvent> &p_event) override;
+
+public:
+ virtual String get_tooltip(const Point2 &p_pos) const override;
+
+ virtual int get_key_height() const;
+ virtual Rect2 get_key_rect(float p_pixels_sec) const;
+ virtual bool is_key_selectable_by_distance() const;
+ virtual void draw_key(const StringName &p_name, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right);
+ virtual void draw_bg(int p_clip_left, int p_clip_right);
+ virtual void draw_fg(int p_clip_left, int p_clip_right);
+
+ Ref<Animation> get_animation() const;
+ AnimationTimelineEdit *get_timeline() const { return timeline; }
+ AnimationTrackEditor *get_editor() const { return editor; }
+ bool is_selection_active() const { return !selection.is_empty(); }
+ bool is_moving_selection() const { return moving_selection; }
+ float get_moving_selection_offset() const { return moving_selection_offset; }
+ void set_animation(const Ref<Animation> &p_animation, bool p_read_only);
+ virtual Size2 get_minimum_size() const override;
+
+ void set_timeline(AnimationTimelineEdit *p_timeline);
+ void set_editor(AnimationTrackEditor *p_editor);
+
+ void set_play_position(float p_pos);
+ void update_play_position();
+
+ void set_use_fps(bool p_use_fps);
+
+ PackedStringArray get_selected_section() const;
+ bool is_marker_selected(const StringName &p_marker) const;
+
+ // For use by AnimationTrackEditor.
+ void _clear_selection(bool p_update);
+
+ AnimationMarkerEdit();
+ ~AnimationMarkerEdit();
+};
+
class AnimationTrackEdit : public Control {
GDCLASS(AnimationTrackEdit, Control);
friend class AnimationTimelineEdit;
@@ -367,6 +556,7 @@ class AnimationTrackEditGroup : public Control {
NodePath node;
Node *root = nullptr;
AnimationTimelineEdit *timeline = nullptr;
+ AnimationTrackEditor *editor = nullptr;
void _zoom_changed();
@@ -380,6 +570,7 @@ public:
virtual Size2 get_minimum_size() const override;
void set_timeline(AnimationTimelineEdit *p_timeline);
void set_root(Node *p_root);
+ void set_editor(AnimationTrackEditor *p_editor);
AnimationTrackEditGroup();
};
@@ -388,6 +579,7 @@ class AnimationTrackEditor : public VBoxContainer {
GDCLASS(AnimationTrackEditor, VBoxContainer);
friend class AnimationTimelineEdit;
friend class AnimationBezierTrackEdit;
+ friend class AnimationMarkerKeyEditEditor;
Ref<Animation> animation;
bool read_only = false;
@@ -405,6 +597,7 @@ class AnimationTrackEditor : public VBoxContainer {
Label *info_message = nullptr;
AnimationTimelineEdit *timeline = nullptr;
+ AnimationMarkerEdit *marker_edit = nullptr;
HSlider *zoom = nullptr;
EditorSpinSlider *step = nullptr;
TextureRect *zoom_icon = nullptr;
@@ -743,6 +936,10 @@ public:
float get_moving_selection_offset() const;
float snap_time(float p_value, bool p_relative = false);
bool is_grouping_tracks();
+ PackedStringArray get_selected_section() const;
+ bool is_marker_selected(const StringName &p_marker) const;
+ bool is_marker_moving_selection() const;
+ float get_marker_moving_selection_offset() const;
/** If `p_from_mouse_event` is `true`, handle Shift key presses for precise snapping. */
void goto_prev_step(bool p_from_mouse_event);
@@ -781,4 +978,23 @@ public:
~AnimationTrackKeyEditEditor();
};
+// AnimationMarkerKeyEditEditorPlugin
+
+class AnimationMarkerKeyEditEditor : public EditorProperty {
+ GDCLASS(AnimationMarkerKeyEditEditor, EditorProperty);
+
+ Ref<Animation> animation;
+ StringName marker_name;
+ bool use_fps = false;
+
+ EditorSpinSlider *spinner = nullptr;
+
+ void _time_edit_entered();
+ void _time_edit_exited();
+
+public:
+ AnimationMarkerKeyEditEditor(Ref<Animation> p_animation, const StringName &p_name, bool p_use_fps);
+ ~AnimationMarkerKeyEditEditor();
+};
+
#endif // ANIMATION_TRACK_EDITOR_H
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 40b58d2c62..88a32b1a6d 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -66,6 +66,8 @@ void GotoLineDialog::ok_pressed() {
text_editor->remove_secondary_carets();
text_editor->unfold_line(line_number);
text_editor->set_caret_line(line_number);
+ text_editor->set_code_hint("");
+ text_editor->cancel_code_completion();
hide();
}
@@ -176,6 +178,8 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col)
text_editor->unfold_line(pos.y);
text_editor->select(pos.y, pos.x, pos.y, pos.x + text.length());
text_editor->center_viewport_to_caret(0);
+ text_editor->set_code_hint("");
+ text_editor->cancel_code_completion();
line_col_changed_for_result = true;
}
@@ -640,6 +644,8 @@ void FindReplaceBar::_search_text_submitted(const String &p_text) {
} else {
search_next();
}
+
+ callable_mp(search_text, &LineEdit::edit).call_deferred();
}
void FindReplaceBar::_replace_text_submitted(const String &p_text) {
@@ -1330,6 +1336,8 @@ void CodeTextEditor::goto_line(int p_line, int p_column) {
text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
text_editor->set_caret_line(p_line, false);
text_editor->set_caret_column(p_column, false);
+ text_editor->set_code_hint("");
+ text_editor->cancel_code_completion();
// Defer in case the CodeEdit was just created and needs to be resized.
callable_mp((TextEdit *)text_editor, &TextEdit::adjust_viewport_to_caret).call_deferred(0);
}
@@ -1338,6 +1346,8 @@ void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
text_editor->remove_secondary_carets();
text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
text_editor->select(p_line, p_begin, p_line, p_end);
+ text_editor->set_code_hint("");
+ text_editor->cancel_code_completion();
callable_mp((TextEdit *)text_editor, &TextEdit::adjust_viewport_to_caret).call_deferred(0);
}
@@ -1347,6 +1357,8 @@ void CodeTextEditor::goto_line_centered(int p_line, int p_column) {
text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
text_editor->set_caret_line(p_line, false);
text_editor->set_caret_column(p_column, false);
+ text_editor->set_code_hint("");
+ text_editor->cancel_code_completion();
callable_mp((TextEdit *)text_editor, &TextEdit::center_viewport_to_caret).call_deferred(0);
}
diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
index 4210baeed2..2af629676a 100644
--- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp
@@ -30,6 +30,7 @@
#include "debug_adapter_parser.h"
+#include "editor/debugger/debug_adapter/debug_adapter_types.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/export/editor_export_platform.h"
@@ -442,26 +443,34 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
return Dictionary();
}
- Dictionary response = prepare_success_response(p_params), body;
- response["body"] = body;
-
Dictionary args = p_params["arguments"];
int variable_id = args["variablesReference"];
- HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
+ if (HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); E) {
+ Dictionary response = prepare_success_response(p_params);
+ Dictionary body;
+ response["body"] = body;
- if (E) {
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
for (int i = 0; i < E->value.size(); i++) {
Dictionary variable = E->value[i];
variable.erase("type");
}
}
+
body["variables"] = E ? E->value : Array();
return response;
} else {
- return Dictionary();
+ // If the requested variable is an object, it needs to be requested from the debuggee.
+ ObjectID object_id = DebugAdapterProtocol::get_singleton()->search_object_id(variable_id);
+
+ if (object_id.is_null()) {
+ return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
+ }
+
+ DebugAdapterProtocol::get_singleton()->request_remote_object(object_id);
}
+ return Dictionary();
}
Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const {
@@ -479,16 +488,27 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
}
Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
- Dictionary response = prepare_success_response(p_params), body;
- response["body"] = body;
-
Dictionary args = p_params["arguments"];
+ String expression = args["expression"];
+ int frame_id = args.has("frameId") ? static_cast<int>(args["frameId"]) : DebugAdapterProtocol::get_singleton()->_current_frame;
- String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
- body["result"] = value;
- body["variablesReference"] = 0;
+ if (HashMap<String, DAP::Variable>::Iterator E = DebugAdapterProtocol::get_singleton()->eval_list.find(expression); E) {
+ Dictionary response = prepare_success_response(p_params);
+ Dictionary body;
+ response["body"] = body;
- return response;
+ DAP::Variable var = E->value;
+
+ body["result"] = var.value;
+ body["variablesReference"] = var.variablesReference;
+
+ // Since an evaluation can alter the state of the debuggee, they are volatile, and should only be used once
+ DebugAdapterProtocol::get_singleton()->eval_list.erase(E->key);
+ return response;
+ } else {
+ DebugAdapterProtocol::get_singleton()->request_remote_evaluate(expression, frame_id);
+ }
+ return Dictionary();
}
Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
index 4febb8bf04..066cf63301 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.cpp
@@ -33,8 +33,8 @@
#include "core/config/project_settings.h"
#include "core/debugger/debugger_marshalls.h"
#include "core/io/json.h"
+#include "core/io/marshalls.h"
#include "editor/debugger/script_editor_debugger.h"
-#include "editor/doc_tools.h"
#include "editor/editor_log.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -186,6 +186,8 @@ void DebugAdapterProtocol::reset_stack_info() {
stackframe_list.clear();
variable_list.clear();
+ object_list.clear();
+ object_pending_set.clear();
}
int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
@@ -671,12 +673,194 @@ int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
variable_list.insert(id, arr);
return id;
}
+ case Variant::OBJECT: {
+ // Objects have to be requested from the debuggee. This has do be done
+ // in a lazy way, as retrieving object properties takes time.
+ EncodedObjectAsID *encoded_obj = Object::cast_to<EncodedObjectAsID>(p_var);
+
+ // Object may be null; in that case, return early.
+ if (!encoded_obj) {
+ return 0;
+ }
+
+ // Object may have been already requested.
+ ObjectID object_id = encoded_obj->get_object_id();
+ if (object_list.has(object_id)) {
+ return object_list[object_id];
+ }
+
+ // Queue requesting the object.
+ int id = variable_id++;
+ object_list.insert(object_id, id);
+ return id;
+ }
default:
// Simple atomic stuff, or too complex to be manipulated
return 0;
}
}
+void DebugAdapterProtocol::parse_object(SceneDebuggerObject &p_obj) {
+ // If the object is not on the pending list, we weren't expecting it. Ignore it.
+ ObjectID object_id = p_obj.id;
+ if (!object_pending_set.erase(object_id)) {
+ return;
+ }
+
+ // Populate DAP::Variable's with the object's properties. These properties will be divided by categories.
+ Array properties;
+ Array script_members;
+ Array script_constants;
+ Array script_node;
+ DAP::Variable node_type;
+ Array node_properties;
+
+ for (SceneDebuggerObject::SceneDebuggerProperty &property : p_obj.properties) {
+ PropertyInfo &info = property.first;
+
+ // Script members ("Members/" prefix)
+ if (info.name.begins_with("Members/")) {
+ info.name = info.name.trim_prefix("Members/");
+ script_members.push_back(parse_object_variable(property));
+ }
+
+ // Script constants ("Constants/" prefix)
+ else if (info.name.begins_with("Constants/")) {
+ info.name = info.name.trim_prefix("Constants/");
+ script_constants.push_back(parse_object_variable(property));
+ }
+
+ // Script node ("Node/" prefix)
+ else if (info.name.begins_with("Node/")) {
+ info.name = info.name.trim_prefix("Node/");
+ script_node.push_back(parse_object_variable(property));
+ }
+
+ // Regular categories (with type Variant::NIL)
+ else if (info.type == Variant::NIL) {
+ if (!node_properties.is_empty()) {
+ node_type.value = itos(node_properties.size());
+ variable_list.insert(node_type.variablesReference, node_properties.duplicate());
+ properties.push_back(node_type.to_json());
+ }
+
+ node_type.name = info.name;
+ node_type.type = "Category";
+ node_type.variablesReference = variable_id++;
+ node_properties.clear();
+ }
+
+ // Regular properties.
+ else {
+ node_properties.push_back(parse_object_variable(property));
+ }
+ }
+
+ // Add the last category.
+ if (!node_properties.is_empty()) {
+ node_type.value = itos(node_properties.size());
+ variable_list.insert(node_type.variablesReference, node_properties.duplicate());
+ properties.push_back(node_type.to_json());
+ }
+
+ // Add the script categories, in reverse order to be at the front of the array:
+ // ( [members; constants; node; category1; category2; ...] )
+ if (!script_node.is_empty()) {
+ DAP::Variable node;
+ node.name = "Node";
+ node.type = "Category";
+ node.value = itos(script_node.size());
+ node.variablesReference = variable_id++;
+ variable_list.insert(node.variablesReference, script_node);
+ properties.push_front(node.to_json());
+ }
+
+ if (!script_constants.is_empty()) {
+ DAP::Variable constants;
+ constants.name = "Constants";
+ constants.type = "Category";
+ constants.value = itos(script_constants.size());
+ constants.variablesReference = variable_id++;
+ variable_list.insert(constants.variablesReference, script_constants);
+ properties.push_front(constants.to_json());
+ }
+
+ if (!script_members.is_empty()) {
+ DAP::Variable members;
+ members.name = "Members";
+ members.type = "Category";
+ members.value = itos(script_members.size());
+ members.variablesReference = variable_id++;
+ variable_list.insert(members.variablesReference, script_members);
+ properties.push_front(members.to_json());
+ }
+
+ ERR_FAIL_COND(!object_list.has(object_id));
+ variable_list.insert(object_list[object_id], properties);
+}
+
+void DebugAdapterProtocol::parse_evaluation(DebuggerMarshalls::ScriptStackVariable &p_var) {
+ // If the eval is not on the pending list, we weren't expecting it. Ignore it.
+ String eval = p_var.name;
+ if (!eval_pending_list.erase(eval)) {
+ return;
+ }
+
+ DAP::Variable variable;
+ variable.name = p_var.name;
+ variable.value = p_var.value;
+ variable.type = Variant::get_type_name(p_var.value.get_type());
+ variable.variablesReference = parse_variant(p_var.value);
+
+ eval_list.insert(variable.name, variable);
+}
+
+const Variant DebugAdapterProtocol::parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property) {
+ const PropertyInfo &info = p_property.first;
+ const Variant &value = p_property.second;
+
+ DAP::Variable var;
+ var.name = info.name;
+ var.type = Variant::get_type_name(info.type);
+ var.value = value;
+ var.variablesReference = parse_variant(value);
+
+ return var.to_json();
+}
+
+ObjectID DebugAdapterProtocol::search_object_id(DAPVarID p_var_id) {
+ for (const KeyValue<ObjectID, DAPVarID> &E : object_list) {
+ if (E.value == p_var_id) {
+ return E.key;
+ }
+ }
+ return ObjectID();
+}
+
+bool DebugAdapterProtocol::request_remote_object(const ObjectID &p_object_id) {
+ // If the object is already on the pending list, we don't need to request it again.
+ if (object_pending_set.has(p_object_id)) {
+ return false;
+ }
+
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_object(p_object_id);
+ object_pending_set.insert(p_object_id);
+
+ return true;
+}
+
+bool DebugAdapterProtocol::request_remote_evaluate(const String &p_eval, int p_stack_frame) {
+ // If the eval is already on the pending list, we don't need to request it again
+ if (eval_pending_list.has(p_eval)) {
+ return false;
+ }
+
+ EditorDebuggerNode::get_singleton()->get_default_debugger()->request_remote_evaluate(p_eval, p_stack_frame);
+ eval_pending_list.insert(p_eval);
+
+ return true;
+}
+
bool DebugAdapterProtocol::process_message(const String &p_text) {
JSON json;
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Malformed message!");
@@ -966,7 +1150,7 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
List<int> scope_ids = stackframe_list.find(frame)->value;
ERR_FAIL_COND(scope_ids.size() != 3);
- ERR_FAIL_INDEX(stack_var.type, 3);
+ ERR_FAIL_INDEX(stack_var.type, 4);
int var_id = scope_ids.get(stack_var.type);
DAP::Variable variable;
@@ -986,6 +1170,20 @@ void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_dat
return;
}
+ if (p_msg == "scene:inspect_object") {
+ // An object was requested from the debuggee; parse it.
+ SceneDebuggerObject remote_obj;
+ remote_obj.deserialize(p_data);
+
+ parse_object(remote_obj);
+ } else if (p_msg == "evaluation_return") {
+ // An evaluation was requested from the debuggee; parse it.
+ DebuggerMarshalls::ScriptStackVariable remote_evaluation;
+ remote_evaluation.deserialize(p_data);
+
+ parse_evaluation(remote_evaluation);
+ }
+
notify_custom_data(p_msg, p_data);
}
diff --git a/editor/debugger/debug_adapter/debug_adapter_protocol.h b/editor/debugger/debug_adapter/debug_adapter_protocol.h
index caff0f9c7f..1d1e183850 100644
--- a/editor/debugger/debug_adapter/debug_adapter_protocol.h
+++ b/editor/debugger/debug_adapter/debug_adapter_protocol.h
@@ -31,12 +31,13 @@
#ifndef DEBUG_ADAPTER_PROTOCOL_H
#define DEBUG_ADAPTER_PROTOCOL_H
-#include "core/io/stream_peer.h"
+#include "core/debugger/debugger_marshalls.h"
#include "core/io/stream_peer_tcp.h"
#include "core/io/tcp_server.h"
#include "debug_adapter_parser.h"
#include "debug_adapter_types.h"
+#include "scene/debugger/scene_debugger.h"
#define DAP_MAX_BUFFER_SIZE 4194304 // 4MB
#define DAP_MAX_CLIENTS 8
@@ -75,6 +76,8 @@ class DebugAdapterProtocol : public Object {
friend class DebugAdapterParser;
+ using DAPVarID = int;
+
private:
static DebugAdapterProtocol *singleton;
DebugAdapterParser *parser = nullptr;
@@ -99,6 +102,13 @@ private:
void reset_stack_info();
int parse_variant(const Variant &p_var);
+ void parse_object(SceneDebuggerObject &p_obj);
+ const Variant parse_object_variable(const SceneDebuggerObject::SceneDebuggerProperty &p_property);
+ void parse_evaluation(DebuggerMarshalls::ScriptStackVariable &p_var);
+
+ ObjectID search_object_id(DAPVarID p_var_id);
+ bool request_remote_object(const ObjectID &p_object_id);
+ bool request_remote_evaluate(const String &p_eval, int p_stack_frame);
bool _initialized = false;
bool _processing_breakpoint = false;
@@ -106,7 +116,7 @@ private:
bool _processing_stackdump = false;
int _remaining_vars = 0;
int _current_frame = 0;
- uint64_t _request_timeout = 1000;
+ uint64_t _request_timeout = 5000;
bool _sync_breakpoints = false;
String _current_request;
@@ -114,10 +124,16 @@ private:
int breakpoint_id = 0;
int stackframe_id = 0;
- int variable_id = 0;
+ DAPVarID variable_id = 0;
List<DAP::Breakpoint> breakpoint_list;
HashMap<DAP::StackFrame, List<int>, DAP::StackFrame> stackframe_list;
- HashMap<int, Array> variable_list;
+ HashMap<DAPVarID, Array> variable_list;
+
+ HashMap<ObjectID, DAPVarID> object_list;
+ HashSet<ObjectID> object_pending_set;
+
+ HashMap<String, DAP::Variable> eval_list;
+ HashSet<String> eval_pending_list;
public:
friend class DebugAdapterServer;
diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp
index cb5e4375a6..e085e2e448 100644
--- a/editor/debugger/editor_debugger_inspector.cpp
+++ b/editor/debugger/editor_debugger_inspector.cpp
@@ -223,7 +223,7 @@ Object *EditorDebuggerInspector::get_object(ObjectID p_id) {
return nullptr;
}
-void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
+void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_offset) {
DebuggerMarshalls::ScriptStackVariable var;
var.deserialize(p_array);
String n = var.name;
@@ -248,6 +248,9 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
case 2:
type = "Globals/";
break;
+ case 3:
+ type = "Evaluated/";
+ break;
default:
type = "Unknown/";
}
@@ -258,7 +261,15 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
pinfo.hint = h;
pinfo.hint_string = hs;
- variables->prop_list.push_back(pinfo);
+ if ((p_offset == -1) || variables->prop_list.is_empty()) {
+ variables->prop_list.push_back(pinfo);
+ } else {
+ List<PropertyInfo>::Element *current = variables->prop_list.front();
+ for (int i = 0; i < p_offset; i++) {
+ current = current->next();
+ }
+ variables->prop_list.insert_before(current, pinfo);
+ }
variables->prop_values[type + n] = v;
variables->update();
edit(variables);
diff --git a/editor/debugger/editor_debugger_inspector.h b/editor/debugger/editor_debugger_inspector.h
index 73dd773750..fac9525943 100644
--- a/editor/debugger/editor_debugger_inspector.h
+++ b/editor/debugger/editor_debugger_inspector.h
@@ -90,7 +90,7 @@ public:
// Stack Dump variables
String get_stack_variable(const String &p_var);
- void add_stack_variable(const Array &p_arr);
+ void add_stack_variable(const Array &p_arr, int p_offset = -1);
void clear_stack_variables();
};
diff --git a/editor/debugger/editor_debugger_server.cpp b/editor/debugger/editor_debugger_server.cpp
index c0efc6a1fc..9ec6058132 100644
--- a/editor/debugger/editor_debugger_server.cpp
+++ b/editor/debugger/editor_debugger_server.cpp
@@ -77,8 +77,8 @@ Error EditorDebuggerServerTCP::start(const String &p_uri) {
// Optionally override
if (!p_uri.is_empty() && p_uri != "tcp://") {
- String scheme, path;
- Error err = p_uri.parse_url(scheme, bind_host, bind_port, path);
+ String scheme, path, fragment;
+ Error err = p_uri.parse_url(scheme, bind_host, bind_port, path, fragment);
ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
}
diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp
index c4d7899b2d..a900842651 100644
--- a/editor/debugger/editor_debugger_tree.cpp
+++ b/editor/debugger/editor_debugger_tree.cpp
@@ -277,11 +277,14 @@ Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {
}
String path = selected->get_text(0);
+ const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
HBoxContainer *hb = memnew(HBoxContainer);
TextureRect *tf = memnew(TextureRect);
tf->set_texture(selected->get_icon(0));
- tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
+ tf->set_custom_minimum_size(Size2(icon_size, icon_size));
+ tf->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
+ tf->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
hb->add_child(tf);
Label *label = memnew(Label(path));
hb->add_child(label);
diff --git a/editor/debugger/editor_expression_evaluator.cpp b/editor/debugger/editor_expression_evaluator.cpp
new file mode 100644
index 0000000000..25e9c9eac3
--- /dev/null
+++ b/editor/debugger/editor_expression_evaluator.cpp
@@ -0,0 +1,145 @@
+/**************************************************************************/
+/* editor_expression_evaluator.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor_expression_evaluator.h"
+
+#include "editor/debugger/editor_debugger_inspector.h"
+#include "editor/debugger/script_editor_debugger.h"
+#include "scene/gui/button.h"
+#include "scene/gui/check_box.h"
+
+void EditorExpressionEvaluator::on_start() {
+ expression_input->set_editable(false);
+ evaluate_btn->set_disabled(true);
+
+ if (clear_on_run_checkbox->is_pressed()) {
+ inspector->clear_stack_variables();
+ }
+}
+
+void EditorExpressionEvaluator::set_editor_debugger(ScriptEditorDebugger *p_editor_debugger) {
+ editor_debugger = p_editor_debugger;
+}
+
+void EditorExpressionEvaluator::add_value(const Array &p_array) {
+ inspector->add_stack_variable(p_array, 0);
+ inspector->set_v_scroll(0);
+ inspector->set_h_scroll(0);
+}
+
+void EditorExpressionEvaluator::_evaluate() {
+ const String &expression = expression_input->get_text();
+ if (expression.is_empty()) {
+ return;
+ }
+
+ if (!editor_debugger->is_session_active()) {
+ return;
+ }
+
+ editor_debugger->request_remote_evaluate(expression, editor_debugger->get_stack_script_frame());
+
+ expression_input->clear();
+}
+
+void EditorExpressionEvaluator::_clear() {
+ inspector->clear_stack_variables();
+}
+
+void EditorExpressionEvaluator::_remote_object_selected(ObjectID p_id) {
+ editor_debugger->emit_signal(SNAME("remote_object_requested"), p_id);
+}
+
+void EditorExpressionEvaluator::_on_expression_input_changed(const String &p_expression) {
+ evaluate_btn->set_disabled(p_expression.is_empty());
+}
+
+void EditorExpressionEvaluator::_on_debugger_breaked(bool p_breaked, bool p_can_debug) {
+ expression_input->set_editable(p_breaked);
+ evaluate_btn->set_disabled(!p_breaked);
+}
+
+void EditorExpressionEvaluator::_on_debugger_clear_execution(Ref<Script> p_stack_script) {
+ expression_input->set_editable(false);
+ evaluate_btn->set_disabled(true);
+}
+
+void EditorExpressionEvaluator::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_READY: {
+ EditorDebuggerNode::get_singleton()->connect("breaked", callable_mp(this, &EditorExpressionEvaluator::_on_debugger_breaked));
+ EditorDebuggerNode::get_singleton()->connect("clear_execution", callable_mp(this, &EditorExpressionEvaluator::_on_debugger_clear_execution));
+ } break;
+ }
+}
+
+EditorExpressionEvaluator::EditorExpressionEvaluator() {
+ set_h_size_flags(SIZE_EXPAND_FILL);
+
+ HBoxContainer *hb = memnew(HBoxContainer);
+ add_child(hb);
+
+ expression_input = memnew(LineEdit);
+ expression_input->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ expression_input->set_placeholder(TTR("Expression to evaluate"));
+ expression_input->set_clear_button_enabled(true);
+ expression_input->connect("text_submitted", callable_mp(this, &EditorExpressionEvaluator::_evaluate).unbind(1));
+ expression_input->connect(SceneStringName(text_changed), callable_mp(this, &EditorExpressionEvaluator::_on_expression_input_changed));
+ hb->add_child(expression_input);
+
+ clear_on_run_checkbox = memnew(CheckBox);
+ clear_on_run_checkbox->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ clear_on_run_checkbox->set_text(TTR("Clear on Run"));
+ clear_on_run_checkbox->set_pressed(true);
+ hb->add_child(clear_on_run_checkbox);
+
+ evaluate_btn = memnew(Button);
+ evaluate_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ evaluate_btn->set_text(TTR("Evaluate"));
+ evaluate_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorExpressionEvaluator::_evaluate));
+ hb->add_child(evaluate_btn);
+
+ clear_btn = memnew(Button);
+ clear_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ clear_btn->set_text(TTR("Clear"));
+ clear_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorExpressionEvaluator::_clear));
+ hb->add_child(clear_btn);
+
+ inspector = memnew(EditorDebuggerInspector);
+ inspector->set_v_size_flags(SIZE_EXPAND_FILL);
+ inspector->set_property_name_style(EditorPropertyNameProcessor::STYLE_RAW);
+ inspector->set_read_only(true);
+ inspector->connect("object_selected", callable_mp(this, &EditorExpressionEvaluator::_remote_object_selected));
+ inspector->set_use_filter(true);
+ add_child(inspector);
+
+ expression_input->set_editable(false);
+ evaluate_btn->set_disabled(true);
+}
diff --git a/editor/editor_quick_open.h b/editor/debugger/editor_expression_evaluator.h
index 815cc0c8fe..0548784695 100644
--- a/editor/editor_quick_open.h
+++ b/editor/debugger/editor_expression_evaluator.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* editor_quick_open.h */
+/* editor_expression_evaluator.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,62 +28,50 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef EDITOR_QUICK_OPEN_H
-#define EDITOR_QUICK_OPEN_H
+#ifndef EDITOR_EXPRESSION_EVALUATOR_H
+#define EDITOR_EXPRESSION_EVALUATOR_H
-#include "core/templates/oa_hash_map.h"
-#include "editor/editor_file_system.h"
-#include "scene/gui/dialogs.h"
-#include "scene/gui/tree.h"
+#include "scene/gui/box_container.h"
-class EditorQuickOpen : public ConfirmationDialog {
- GDCLASS(EditorQuickOpen, ConfirmationDialog);
+class Button;
+class CheckBox;
+class EditorDebuggerInspector;
+class LineEdit;
+class RemoteDebuggerPeer;
+class ScriptEditorDebugger;
- static Rect2i prev_rect;
- static bool was_showed;
+class EditorExpressionEvaluator : public VBoxContainer {
+ GDCLASS(EditorExpressionEvaluator, VBoxContainer)
- LineEdit *search_box = nullptr;
- Tree *search_options = nullptr;
- String base_type;
- bool allow_multi_select = false;
+private:
+ Ref<RemoteDebuggerPeer> peer;
- Vector<String> files;
- OAHashMap<String, Ref<Texture2D>> icons;
+ LineEdit *expression_input = nullptr;
+ CheckBox *clear_on_run_checkbox = nullptr;
+ Button *evaluate_btn = nullptr;
+ Button *clear_btn = nullptr;
- struct Entry {
- String path;
- float score = 0;
- };
+ EditorDebuggerInspector *inspector = nullptr;
- struct EntryComparator {
- _FORCE_INLINE_ bool operator()(const Entry &A, const Entry &B) const {
- return A.score > B.score;
- }
- };
+ void _evaluate();
+ void _clear();
- void _update_search();
- void _build_search_cache(EditorFileSystemDirectory *p_efsd);
- float _score_search_result(const PackedStringArray &p_search_tokens, const String &p_path);
-
- void _confirmed();
- virtual void cancel_pressed() override;
- void _cleanup();
-
- void _sbox_input(const Ref<InputEvent> &p_event);
- void _text_changed(const String &p_newtext);
+ void _remote_object_selected(ObjectID p_id);
+ void _on_expression_input_changed(const String &p_expression);
+ void _on_debugger_breaked(bool p_breaked, bool p_can_debug);
+ void _on_debugger_clear_execution(Ref<Script> p_stack_script);
protected:
+ ScriptEditorDebugger *editor_debugger = nullptr;
+
void _notification(int p_what);
- static void _bind_methods();
public:
- String get_base_type() const;
-
- String get_selected() const;
- Vector<String> get_selected_files() const;
+ void on_start();
+ void set_editor_debugger(ScriptEditorDebugger *p_editor_debugger);
+ void add_value(const Array &p_array);
- void popup_dialog(const String &p_base, bool p_enable_multi = false, bool p_dontclear = false);
- EditorQuickOpen();
+ EditorExpressionEvaluator();
};
-#endif // EDITOR_QUICK_OPEN_H
+#endif // EDITOR_EXPRESSION_EVALUATOR_H
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index b798bdf9c1..cbe7910518 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -37,6 +37,7 @@
#include "core/string/ustring.h"
#include "core/version.h"
#include "editor/debugger/debug_adapter/debug_adapter_protocol.h"
+#include "editor/debugger/editor_expression_evaluator.h"
#include "editor/debugger/editor_performance_profiler.h"
#include "editor/debugger/editor_profiler.h"
#include "editor/debugger/editor_visual_profiler.h"
@@ -252,6 +253,13 @@ const SceneDebuggerTree *ScriptEditorDebugger::get_remote_tree() {
return scene_tree;
}
+void ScriptEditorDebugger::request_remote_evaluate(const String &p_expression, int p_stack_frame) {
+ Array msg;
+ msg.push_back(p_expression);
+ msg.push_back(p_stack_frame);
+ _put_msg("evaluate", msg);
+}
+
void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value) {
Array msg;
msg.push_back(p_obj_id);
@@ -811,6 +819,8 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
if (EditorFileSystem::get_singleton()) {
EditorFileSystem::get_singleton()->update_file(p_data[0]);
}
+ } else if (p_msg == "evaluation_return") {
+ expression_evaluator->add_value(p_data);
} else {
int colon_index = p_msg.find_char(':');
ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
@@ -854,8 +864,9 @@ void ScriptEditorDebugger::_notification(int p_what) {
error_tree->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditorDebugger::_error_selected));
error_tree->connect("item_activated", callable_mp(this, &ScriptEditorDebugger::_error_activated));
breakpoints_tree->connect("item_activated", callable_mp(this, &ScriptEditorDebugger::_breakpoint_tree_clicked));
- [[fallthrough]];
- }
+ connect("started", callable_mp(expression_evaluator, &EditorExpressionEvaluator::on_start));
+ } break;
+
case NOTIFICATION_THEME_CHANGED: {
tabs->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DebuggerPanel"), EditorStringName(EditorStyles)));
@@ -2010,6 +2021,13 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
add_child(file_dialog);
}
+ { // Expression evaluator
+ expression_evaluator = memnew(EditorExpressionEvaluator);
+ expression_evaluator->set_name(TTR("Evaluator"));
+ expression_evaluator->set_editor_debugger(this);
+ tabs->add_child(expression_evaluator);
+ }
+
{ //profiler
profiler = memnew(EditorProfiler);
profiler->set_name(TTR("Profiler"));
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index bd0b0c7d85..1908b1e5a7 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -56,6 +56,7 @@ class SceneDebuggerTree;
class EditorDebuggerPlugin;
class DebugAdapterProtocol;
class DebugAdapterParser;
+class EditorExpressionEvaluator;
class ScriptEditorDebugger : public MarginContainer {
GDCLASS(ScriptEditorDebugger, MarginContainer);
@@ -152,6 +153,7 @@ private:
EditorProfiler *profiler = nullptr;
EditorVisualProfiler *visual_profiler = nullptr;
EditorPerformanceProfiler *performance_profiler = nullptr;
+ EditorExpressionEvaluator *expression_evaluator = nullptr;
OS::ProcessID remote_pid = 0;
bool move_to_foreground = true;
@@ -252,6 +254,8 @@ public:
void request_remote_tree();
const SceneDebuggerTree *get_remote_tree();
+ void request_remote_evaluate(const String &p_expression, int p_stack_frame);
+
void start(Ref<RemoteDebuggerPeer> p_peer);
void stop();
diff --git a/editor/directory_create_dialog.cpp b/editor/directory_create_dialog.cpp
index 46baa2c6e1..ee03d5a7f6 100644
--- a/editor/directory_create_dialog.cpp
+++ b/editor/directory_create_dialog.cpp
@@ -39,33 +39,48 @@
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
-static String sanitize_input(const String &p_path) {
+String DirectoryCreateDialog::_sanitize_input(const String &p_path) const {
String path = p_path.strip_edges();
- if (path.ends_with("/")) {
- path = path.left(path.length() - 1);
+ if (mode == MODE_DIRECTORY) {
+ path = path.trim_suffix("/");
}
return path;
}
String DirectoryCreateDialog::_validate_path(const String &p_path) const {
if (p_path.is_empty()) {
- return TTR("Folder name cannot be empty.");
+ return TTR("Name cannot be empty.");
}
-
- if (p_path.contains("\\") || p_path.contains(":") || p_path.contains("*") ||
- p_path.contains("|") || p_path.contains(">")) {
- return TTR("Folder name contains invalid characters.");
+ if (mode == MODE_FILE && p_path.ends_with("/")) {
+ return TTR("File name can't end with /.");
}
- for (const String &part : p_path.split("/")) {
+ const PackedStringArray splits = p_path.split("/");
+ for (int i = 0; i < splits.size(); i++) {
+ const String &part = splits[i];
+ bool is_file = mode == MODE_FILE && i == splits.size() - 1;
+
if (part.is_empty()) {
- return TTR("Folder name cannot be empty.");
+ if (is_file) {
+ return TTR("File name cannot be empty.");
+ } else {
+ return TTR("Folder name cannot be empty.");
+ }
}
- if (part.ends_with(" ") || part[0] == ' ') {
- return TTR("Folder name cannot begin or end with a space.");
+ if (part.contains("\\") || part.contains(":") || part.contains("*") ||
+ part.contains("|") || part.contains(">") || part.ends_with(".") || part.ends_with(" ")) {
+ if (is_file) {
+ return TTR("File name contains invalid characters.");
+ } else {
+ return TTR("Folder name contains invalid characters.");
+ }
}
if (part[0] == '.') {
- return TTR("Folder name cannot begin with a dot.");
+ if (is_file) {
+ return TTR("File name begins with a dot.");
+ } else {
+ return TTR("Folder name begins with a dot.");
+ }
}
}
@@ -82,12 +97,18 @@ String DirectoryCreateDialog::_validate_path(const String &p_path) const {
}
void DirectoryCreateDialog::_on_dir_path_changed() {
- const String path = sanitize_input(dir_path->get_text());
+ const String path = _sanitize_input(dir_path->get_text());
const String error = _validate_path(path);
if (error.is_empty()) {
if (path.contains("/")) {
- validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK);
+ if (mode == MODE_DIRECTORY) {
+ validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK);
+ } else {
+ validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in path will create the file in subfolder, creating new subfolders if necessary."), EditorValidationPanel::MSG_OK);
+ }
+ } else if (mode == MODE_FILE) {
+ validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("File name is valid."), EditorValidationPanel::MSG_OK);
}
} else {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR);
@@ -95,18 +116,13 @@ void DirectoryCreateDialog::_on_dir_path_changed() {
}
void DirectoryCreateDialog::ok_pressed() {
- const String path = sanitize_input(dir_path->get_text());
+ const String path = _sanitize_input(dir_path->get_text());
// The OK button should be disabled if the path is invalid, but just in case.
const String error = _validate_path(path);
ERR_FAIL_COND_MSG(!error.is_empty(), error);
- Error err = EditorFileSystem::get_singleton()->make_dir_recursive(path, base_dir);
- if (err == OK) {
- emit_signal(SNAME("dir_created"), base_dir.path_join(path));
- } else {
- EditorNode::get_singleton()->show_warning(TTR("Could not create folder."));
- }
+ accept_callback.call(base_dir.path_join(path));
hide();
}
@@ -115,28 +131,40 @@ void DirectoryCreateDialog::_post_popup() {
dir_path->grab_focus();
}
-void DirectoryCreateDialog::config(const String &p_base_dir) {
+void DirectoryCreateDialog::config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name) {
+ set_title(p_title);
base_dir = p_base_dir;
- label->set_text(vformat(TTR("Create new folder in %s:"), base_dir));
- dir_path->set_text("new folder");
- dir_path->select_all();
+ base_path_label->set_text(vformat(TTR("Base path: %s"), base_dir));
+ accept_callback = p_accept_callback;
+ mode = p_mode;
+
+ dir_path->set_text(p_default_name);
validation_panel->update();
-}
-void DirectoryCreateDialog::_bind_methods() {
- ADD_SIGNAL(MethodInfo("dir_created", PropertyInfo(Variant::STRING, "path")));
+ if (p_mode == MODE_FILE) {
+ int extension_pos = p_default_name.rfind(".");
+ if (extension_pos > -1) {
+ dir_path->select(0, extension_pos);
+ return;
+ }
+ }
+ dir_path->select_all();
}
DirectoryCreateDialog::DirectoryCreateDialog() {
- set_title(TTR("Create Folder"));
set_min_size(Size2i(480, 0) * EDSCALE);
VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb);
- label = memnew(Label);
- label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS);
- vb->add_child(label);
+ base_path_label = memnew(Label);
+ base_path_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS);
+ vb->add_child(base_path_label);
+
+ Label *name_label = memnew(Label);
+ name_label->set_text(TTR("Name:"));
+ name_label->set_theme_type_variation("HeaderSmall");
+ vb->add_child(name_label);
dir_path = memnew(LineEdit);
vb->add_child(dir_path);
diff --git a/editor/directory_create_dialog.h b/editor/directory_create_dialog.h
index 82e2e98ae5..2292a6e5c9 100644
--- a/editor/directory_create_dialog.h
+++ b/editor/directory_create_dialog.h
@@ -40,23 +40,31 @@ class LineEdit;
class DirectoryCreateDialog : public ConfirmationDialog {
GDCLASS(DirectoryCreateDialog, ConfirmationDialog);
+public:
+ enum Mode {
+ MODE_FILE,
+ MODE_DIRECTORY,
+ };
+
+private:
String base_dir;
+ Callable accept_callback;
+ int mode = MODE_FILE;
- Label *label = nullptr;
+ Label *base_path_label = nullptr;
LineEdit *dir_path = nullptr;
EditorValidationPanel *validation_panel = nullptr;
+ String _sanitize_input(const String &p_input) const;
String _validate_path(const String &p_path) const;
void _on_dir_path_changed();
protected:
- static void _bind_methods();
-
virtual void ok_pressed() override;
virtual void _post_popup() override;
public:
- void config(const String &p_base_dir);
+ void config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name = "");
DirectoryCreateDialog();
};
diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp
index dc943fc783..34f432aa7e 100644
--- a/editor/editor_about.cpp
+++ b/editor/editor_about.cpp
@@ -33,16 +33,12 @@
#include "core/authors.gen.h"
#include "core/donors.gen.h"
#include "core/license.gen.h"
-#include "core/os/time.h"
-#include "core/version.h"
#include "editor/editor_string_names.h"
+#include "editor/gui/editor_version_button.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/item_list.h"
#include "scene/resources/style_box.h"
-// The metadata key used to store and retrieve the version text to copy to the clipboard.
-const String EditorAbout::META_TEXT_TO_COPY = "text_to_copy";
-
void EditorAbout::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
@@ -81,10 +77,6 @@ void EditorAbout::_license_tree_selected() {
_tpl_text->set_text(selected->get_metadata(0));
}
-void EditorAbout::_version_button_pressed() {
- DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
-}
-
void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) {
const String website = p_il->get_item_metadata(p_id);
if (!website.is_empty()) {
@@ -198,25 +190,7 @@ EditorAbout::EditorAbout() {
Control *v_spacer = memnew(Control);
version_info_vbc->add_child(v_spacer);
- version_btn = memnew(LinkButton);
- String hash = String(VERSION_HASH);
- if (hash.length() != 0) {
- hash = " " + vformat("[%s]", hash.left(9));
- }
- version_btn->set_text(VERSION_FULL_NAME + hash);
- // Set the text to copy in metadata as it slightly differs from the button's text.
- version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash);
- version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
- String build_date;
- if (VERSION_TIMESTAMP > 0) {
- build_date = Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC";
- } else {
- build_date = TTR("(unknown)");
- }
- version_btn->set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version number."), build_date));
-
- version_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorAbout::_version_button_pressed));
- version_info_vbc->add_child(version_btn);
+ version_info_vbc->add_child(memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD)));
Label *about_text = memnew(Label);
about_text->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
diff --git a/editor/editor_about.h b/editor/editor_about.h
index fc3d6cedce..6f33d502d7 100644
--- a/editor/editor_about.h
+++ b/editor/editor_about.h
@@ -33,7 +33,6 @@
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
-#include "scene/gui/link_button.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/separator.h"
@@ -49,16 +48,12 @@
class EditorAbout : public AcceptDialog {
GDCLASS(EditorAbout, AcceptDialog);
- static const String META_TEXT_TO_COPY;
-
private:
void _license_tree_selected();
- void _version_button_pressed();
void _item_with_website_selected(int p_id, ItemList *p_il);
void _item_list_resized(ItemList *p_il);
ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false);
- LinkButton *version_btn = nullptr;
Tree *_tpl_tree = nullptr;
RichTextLabel *license_text_label = nullptr;
RichTextLabel *_tpl_text = nullptr;
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index afa0548044..2d1a914120 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -2089,12 +2089,11 @@ void EditorFileSystem::_update_script_documentation() {
// return the last loaded version of the script (without the modifications).
scr->reload_from_file();
}
- Vector<DocData::ClassDoc> docs = scr->get_documentation();
- for (int j = 0; j < docs.size(); j++) {
- EditorHelp::get_doc_data()->add_doc(docs[j]);
+ for (const DocData::ClassDoc &cd : scr->get_documentation()) {
+ EditorHelp::get_doc_data()->add_doc(cd);
if (!first_scan) {
// Update the documentation in the Script Editor if it is open.
- ScriptEditor::get_singleton()->update_doc(docs[j].name);
+ ScriptEditor::get_singleton()->update_doc(cd.name);
}
}
}
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index a215662f16..21f67772ea 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -2763,8 +2763,9 @@ void EditorInspector::update_tree() {
// TODO: Can be useful to store more context for the focusable, such as the caret position in LineEdit.
StringName current_selected = property_selected;
int current_focusable = -1;
- // Temporarily disable focus following to avoid jumping while the inspector is updating.
- set_follow_focus(false);
+
+ // Temporarily disable focus following on the root inspector to avoid jumping while the inspector is updating.
+ get_root_inspector()->set_follow_focus(false);
if (property_focusable != -1) {
// Check that focusable is actually focusable.
@@ -2792,6 +2793,7 @@ void EditorInspector::update_tree() {
_clear(!object);
if (!object) {
+ get_root_inspector()->set_follow_focus(true);
return;
}
@@ -3529,7 +3531,8 @@ void EditorInspector::update_tree() {
// Updating inspector might invalidate some editing owners.
EditorNode::get_singleton()->hide_unused_editors();
}
- set_follow_focus(true);
+
+ get_root_inspector()->set_follow_focus(true);
}
void EditorInspector::update_property(const String &p_prop) {
@@ -3774,11 +3777,10 @@ void EditorInspector::set_use_wide_editors(bool p_enable) {
wide_editors = p_enable;
}
-void EditorInspector::set_sub_inspector(bool p_enable) {
- sub_inspector = p_enable;
- if (!is_inside_tree()) {
- return;
- }
+void EditorInspector::set_root_inspector(EditorInspector *p_root_inspector) {
+ root_inspector = p_root_inspector;
+ // Only the root inspector should follow focus.
+ set_follow_focus(false);
}
void EditorInspector::set_use_deletable_properties(bool p_enabled) {
@@ -4096,13 +4098,13 @@ void EditorInspector::_notification(int p_what) {
EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &EditorInspector::_feature_profile_changed));
set_process(is_visible_in_tree());
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
- if (!sub_inspector) {
+ if (!is_sub_inspector()) {
get_tree()->connect("node_removed", callable_mp(this, &EditorInspector::_node_removed));
}
} break;
case NOTIFICATION_PREDELETE: {
- if (!sub_inspector && is_inside_tree()) {
+ if (!is_sub_inspector() && is_inside_tree()) {
get_tree()->disconnect("node_removed", callable_mp(this, &EditorInspector::_node_removed));
}
edit(nullptr);
@@ -4161,7 +4163,7 @@ void EditorInspector::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
bool needs_update = false;
- if (EditorThemeManager::is_generated_theme_outdated() && !sub_inspector) {
+ if (!is_sub_inspector() && EditorThemeManager::is_generated_theme_outdated()) {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
}
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 14b6ff0907..0309213b76 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -486,6 +486,7 @@ class EditorInspector : public ScrollContainer {
static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS];
static int inspector_plugin_count;
+ EditorInspector *root_inspector = nullptr;
VBoxContainer *main_vbox = nullptr;
// Map used to cache the instantiated editors.
@@ -514,7 +515,6 @@ class EditorInspector : public ScrollContainer {
bool update_all_pending = false;
bool read_only = false;
bool keying = false;
- bool sub_inspector = false;
bool wide_editors = false;
bool deletable_properties = false;
@@ -644,8 +644,9 @@ public:
String get_object_class() const;
void set_use_wide_editors(bool p_enable);
- void set_sub_inspector(bool p_enable);
- bool is_sub_inspector() const { return sub_inspector; }
+ void set_root_inspector(EditorInspector *p_root_inspector);
+ EditorInspector *get_root_inspector() { return is_sub_inspector() ? root_inspector : this; }
+ bool is_sub_inspector() const { return root_inspector != nullptr; }
void set_use_deletable_properties(bool p_enabled);
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index fa6198f695..264c80dcbf 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -40,6 +40,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/filesystem_dock.h"
+#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/gui/editor_scene_tabs.h"
#include "editor/gui/scene_tree_editor.h"
@@ -336,6 +337,24 @@ void EditorInterface::popup_property_selector(Object *p_object, const Callable &
property_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED);
}
+void EditorInterface::popup_quick_open(const Callable &p_callback, const TypedArray<StringName> &p_base_types) {
+ StringName required_type = SNAME("Resource");
+ Vector<StringName> base_types;
+ if (p_base_types.is_empty()) {
+ base_types.append(required_type);
+ } else {
+ for (int i = 0; i < p_base_types.size(); i++) {
+ StringName type = p_base_types[i];
+ ERR_FAIL_COND_MSG(!(ClassDB::is_parent_class(type, required_type) || EditorNode::get_editor_data().script_class_is_parent(type, required_type)), "Only types deriving from Resource are supported in the quick open dialog.");
+ base_types.append(type);
+ }
+ }
+
+ EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog();
+ quick_open->connect(SNAME("canceled"), callable_mp(this, &EditorInterface::_quick_open).bind(String(), p_callback));
+ quick_open->popup_dialog(base_types, callable_mp(this, &EditorInterface::_quick_open).bind(p_callback));
+}
+
void EditorInterface::_node_selected(const NodePath &p_node_path, const Callable &p_callback) {
const NodePath path = get_edited_scene_root()->get_path().rel_path_to(p_node_path);
_call_dialog_callback(p_callback, path, "node selected");
@@ -353,6 +372,12 @@ void EditorInterface::_property_selection_canceled(const Callable &p_callback) {
_call_dialog_callback(p_callback, NodePath(), "property selection canceled");
}
+void EditorInterface::_quick_open(const String &p_file_path, const Callable &p_callback) {
+ EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog();
+ quick_open->disconnect(SNAME("canceled"), callable_mp(this, &EditorInterface::_quick_open));
+ _call_dialog_callback(p_callback, p_file_path, "quick open");
+}
+
void EditorInterface::_call_dialog_callback(const Callable &p_callback, const Variant &p_selected, const String &p_context) {
Callable::CallError ce;
Variant ret;
@@ -568,6 +593,7 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types", "current_value"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter", "current_value"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()), DEFVAL(String()));
+ ClassDB::bind_method(D_METHOD("popup_quick_open", "callback", "base_types"), &EditorInterface::popup_quick_open, DEFVAL(TypedArray<StringName>()));
// Editor docks.
diff --git a/editor/editor_interface.h b/editor/editor_interface.h
index 20d66d71f5..4877444dac 100644
--- a/editor/editor_interface.h
+++ b/editor/editor_interface.h
@@ -72,6 +72,7 @@ class EditorInterface : public Object {
void _node_selection_canceled(const Callable &p_callback);
void _property_selected(const String &p_property_name, const Callable &p_callback);
void _property_selection_canceled(const Callable &p_callback);
+ void _quick_open(const String &p_file_path, const Callable &p_callback);
void _call_dialog_callback(const Callable &p_callback, const Variant &p_selected, const String &p_context);
// Editor tools.
@@ -138,6 +139,7 @@ public:
void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>(), Node *p_current_value = nullptr);
// Must use Vector<int> because exposing Vector<Variant::Type> is not supported.
void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array(), const String &p_current_value = String());
+ void popup_quick_open(const Callable &p_callback, const TypedArray<StringName> &p_base_types = TypedArray<StringName>());
// Editor docks.
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 665255b9b2..dd6c88ef25 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -94,7 +94,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_properties.h"
#include "editor/editor_property_name_processor.h"
-#include "editor/editor_quick_open.h"
+#include "editor/editor_resource_picker.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_run.h"
#include "editor/editor_run_native.h"
@@ -109,6 +109,7 @@
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/gui/editor_scene_tabs.h"
#include "editor/gui/editor_title_bar.h"
@@ -1982,7 +1983,7 @@ void EditorNode::try_autosave() {
editor_data.save_editor_external_data();
}
-void EditorNode::restart_editor() {
+void EditorNode::restart_editor(bool p_goto_project_manager) {
exiting = true;
if (project_run_bar->is_playing()) {
@@ -1990,22 +1991,25 @@ void EditorNode::restart_editor() {
}
String to_reopen;
- if (get_tree()->get_edited_scene_root()) {
+ if (!p_goto_project_manager && get_tree()->get_edited_scene_root()) {
to_reopen = get_tree()->get_edited_scene_root()->get_scene_file_path();
}
_exit_editor(EXIT_SUCCESS);
List<String> args;
-
for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
args.push_back(a);
}
- args.push_back("--path");
- args.push_back(ProjectSettings::get_singleton()->get_resource_path());
+ if (p_goto_project_manager) {
+ args.push_back("--project-manager");
+ } else {
+ args.push_back("--path");
+ args.push_back(ProjectSettings::get_singleton()->get_resource_path());
- args.push_back("-e");
+ args.push_back("-e");
+ }
if (!to_reopen.is_empty()) {
args.push_back(to_reopen);
@@ -2386,7 +2390,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
// This is to sweep properties that were removed from the inspector.
List<ObjectID> to_remove;
for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) {
- const Object *context = ObjectDB::get_instance(kv.key);
+ Object *context = ObjectDB::get_instance(kv.key);
if (context) {
// In case of self-owning plugins, they are disabled here if they can auto hide.
const EditorPlugin *self_owning = Object::cast_to<EditorPlugin>(context);
@@ -2395,7 +2399,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
}
}
- if (!context) {
+ if (!context || context->call(SNAME("_should_stop_editing"))) {
to_remove.push_back(kv.key);
for (EditorPlugin *plugin : kv.value) {
if (plugin->can_auto_hide()) {
@@ -2669,19 +2673,13 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
} break;
case FILE_QUICK_OPEN: {
- quick_open->popup_dialog("Resource", true);
- quick_open->set_title(TTR("Quick Open..."));
-
+ quick_open_dialog->popup_dialog({ "Resource" }, callable_mp(this, &EditorNode::_quick_opened));
} break;
case FILE_QUICK_OPEN_SCENE: {
- quick_open->popup_dialog("PackedScene", true);
- quick_open->set_title(TTR("Quick Open Scene..."));
-
+ quick_open_dialog->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorNode::_quick_opened));
} break;
case FILE_QUICK_OPEN_SCRIPT: {
- quick_open->popup_dialog("Script", true);
- quick_open->set_title(TTR("Quick Open Script..."));
-
+ quick_open_dialog->popup_dialog({ "Script" }, callable_mp(this, &EditorNode::_quick_opened));
} break;
case FILE_OPEN_PREV: {
if (previous_scenes.is_empty()) {
@@ -3407,23 +3405,7 @@ void EditorNode::_discard_changes(const String &p_str) {
} break;
case RUN_PROJECT_MANAGER: {
- project_run_bar->stop_playing();
- _exit_editor(EXIT_SUCCESS);
- String exec = OS::get_singleton()->get_executable_path();
-
- List<String> args;
- for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_TOOL)) {
- args.push_back(a);
- }
-
- String exec_base_dir = exec.get_base_dir();
- if (!exec_base_dir.is_empty()) {
- args.push_back("--path");
- args.push_back(exec_base_dir);
- }
- args.push_back("--project-manager");
-
- OS::get_singleton()->set_restart_on_exit(true, args);
+ restart_editor(true);
} break;
case RELOAD_CURRENT_PROJECT: {
restart_editor();
@@ -4599,17 +4581,11 @@ void EditorNode::_update_recent_scenes() {
recent_scenes->reset_size();
}
-void EditorNode::_quick_opened() {
- Vector<String> files = quick_open->get_selected_files();
-
- bool open_scene_dialog = quick_open->get_base_type() == "PackedScene";
- for (int i = 0; i < files.size(); i++) {
- const String &res_path = files[i];
- if (open_scene_dialog || ClassDB::is_parent_class(ResourceLoader::get_resource_type(res_path), "PackedScene")) {
- open_request(res_path);
- } else {
- load_resource(res_path);
- }
+void EditorNode::_quick_opened(const String &p_file_path) {
+ if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(p_file_path), "PackedScene")) {
+ open_request(p_file_path);
+ } else {
+ load_resource(p_file_path);
}
}
@@ -4795,7 +4771,13 @@ Ref<Texture2D> EditorNode::_get_class_or_script_icon(const String &p_class, cons
// Look for the native base type in the editor theme. This is relevant for
// scripts extending other scripts and for built-in classes.
String script_class_name = p_script->get_language()->get_global_class_name(p_script->get_path());
- String base_type = ScriptServer::get_global_class_native_base(script_class_name);
+ String base_type;
+ if (script_class_name.is_empty()) {
+ base_type = p_script->get_instance_base_type();
+ } else {
+ base_type = ScriptServer::get_global_class_native_base(script_class_name);
+ }
+
if (theme.is_valid() && theme->has_icon(base_type, EditorStringName(EditorIcons))) {
return theme->get_icon(base_type, EditorStringName(EditorIcons));
}
@@ -4860,6 +4842,8 @@ Ref<Texture2D> EditorNode::get_class_icon(const String &p_class, const String &p
Ref<Script> scr;
if (ScriptServer::is_global_class(p_class)) {
scr = EditorNode::get_editor_data().script_class_load_script(p_class);
+ } else if (ResourceLoader::exists(p_class)) { // If the script is not a class_name we check if the script resource exists.
+ scr = ResourceLoader::load(p_class);
}
return _get_class_or_script_icon(p_class, scr, p_fallback, true);
@@ -6761,10 +6745,6 @@ EditorNode::EditorNode() {
ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G);
ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G);
- // Used in the GPUParticles/CPUParticles 2D/3D editor plugins.
- // The shortcut is Ctrl + R even on macOS, as Cmd + R is used to run the current scene on macOS.
- ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R);
-
FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename"));
_update_vsync_mode();
@@ -7722,6 +7702,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(AnimationPlayerEditorPlugin));
add_editor_plugin(memnew(AnimationTrackKeyEditEditorPlugin));
+ add_editor_plugin(memnew(AnimationMarkerKeyEditEditorPlugin));
add_editor_plugin(memnew(CanvasItemEditorPlugin));
add_editor_plugin(memnew(Node3DEditorPlugin));
add_editor_plugin(memnew(ScriptEditorPlugin));
@@ -7847,9 +7828,8 @@ EditorNode::EditorNode() {
open_imported->connect("custom_action", callable_mp(this, &EditorNode::_inherit_imported));
gui_base->add_child(open_imported);
- quick_open = memnew(EditorQuickOpen);
- gui_base->add_child(quick_open);
- quick_open->connect("quick_open", callable_mp(this, &EditorNode::_quick_opened));
+ quick_open_dialog = memnew(EditorQuickOpenDialog);
+ gui_base->add_child(quick_open_dialog);
_update_recent_scenes();
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 36332e3d78..696caf857c 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -81,7 +81,6 @@ class EditorLog;
class EditorMainScreen;
class EditorNativeShaderSourceVisualizer;
class EditorPluginList;
-class EditorQuickOpen;
class EditorResourcePreview;
class EditorResourceConversionPlugin;
class EditorRunBar;
@@ -90,6 +89,7 @@ class EditorSelectionHistory;
class EditorSettingsDialog;
class EditorTitleBar;
class ExportTemplateManager;
+class EditorQuickOpenDialog;
class FBXImporterManager;
class FileSystemDock;
class HistoryDock;
@@ -257,13 +257,13 @@ private:
EditorSelectionHistory editor_history;
EditorCommandPalette *command_palette = nullptr;
+ EditorQuickOpenDialog *quick_open_dialog = nullptr;
EditorExport *editor_export = nullptr;
EditorLog *log = nullptr;
EditorNativeShaderSourceVisualizer *native_shader_source_visualizer = nullptr;
EditorPluginList *editor_plugins_force_input_forwarding = nullptr;
EditorPluginList *editor_plugins_force_over = nullptr;
EditorPluginList *editor_plugins_over = nullptr;
- EditorQuickOpen *quick_open = nullptr;
EditorResourcePreview *resource_preview = nullptr;
EditorSelection *editor_selection = nullptr;
EditorSettingsDialog *editor_settings_dialog = nullptr;
@@ -572,7 +572,7 @@ private:
void _inherit_request(String p_file);
void _instantiate_request(const Vector<String> &p_files);
- void _quick_opened();
+ void _quick_opened(const String &p_file_path);
void _open_command_palette();
void _project_run_started();
@@ -913,6 +913,8 @@ public:
Dictionary drag_resource(const Ref<Resource> &p_res, Control *p_from);
Dictionary drag_files_and_dirs(const Vector<String> &p_paths, Control *p_from);
+ EditorQuickOpenDialog *get_quick_open_dialog() { return quick_open_dialog; }
+
void add_tool_menu_item(const String &p_name, const Callable &p_callback);
void add_tool_submenu_item(const String &p_name, PopupMenu *p_submenu);
void remove_tool_menu_item(const String &p_name);
@@ -924,7 +926,7 @@ public:
void save_scene_list(const HashSet<String> &p_scene_paths);
void save_before_run();
void try_autosave();
- void restart_editor();
+ void restart_editor(bool p_goto_project_manager = false);
void unload_editor_addons();
void dim_editor(bool p_dimming);
diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp
index ff869f8a8a..883116bab6 100644
--- a/editor/editor_paths.cpp
+++ b/editor/editor_paths.cpp
@@ -257,22 +257,6 @@ EditorPaths::EditorPaths() {
}
}
- // Check that `.editorconfig` file exists.
- String project_editorconfig_path = "res://.editorconfig";
- if (!FileAccess::exists(project_editorconfig_path)) {
- Ref<FileAccess> f = FileAccess::open(project_editorconfig_path, FileAccess::WRITE);
- if (f.is_valid()) {
- f->store_line("root = true");
- f->store_line("");
- f->store_line("[*]");
- f->store_line("charset = utf-8");
- f->close();
- } else {
- ERR_PRINT("Failed to create file " + project_editorconfig_path.quote() + ".");
- }
- FileAccess::set_hidden_attribute(project_editorconfig_path, true);
- }
-
Engine::get_singleton()->set_shader_cache_path(project_data_dir);
// Editor metadata dir.
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 0fb57ce40e..c5a35e466c 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -3179,6 +3179,10 @@ void EditorPropertyResource::_update_preferred_shader() {
}
}
+bool EditorPropertyResource::_should_stop_editing() const {
+ return !resource_picker->is_toggle_pressed();
+}
+
void EditorPropertyResource::_viewport_selected(const NodePath &p_path) {
Node *to_node = get_node(p_path);
if (!Object::cast_to<Viewport>(to_node)) {
@@ -3246,7 +3250,10 @@ void EditorPropertyResource::update_property() {
sub_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
sub_inspector->set_use_doc_hints(true);
- sub_inspector->set_sub_inspector(true);
+ EditorInspector *parent_inspector = get_parent_inspector();
+ ERR_FAIL_NULL(parent_inspector);
+ sub_inspector->set_root_inspector(parent_inspector->get_root_inspector());
+
sub_inspector->set_property_name_style(InspectorDock::get_singleton()->get_property_name_style());
sub_inspector->connect("property_keyed", callable_mp(this, &EditorPropertyResource::_sub_inspector_property_keyed));
@@ -3350,13 +3357,18 @@ void EditorPropertyResource::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE: {
const EditorInspector *ei = get_parent_inspector();
- if (ei && !ei->is_main_editor_inspector()) {
+ const EditorInspector *main_ei = InspectorDock::get_inspector_singleton();
+ if (ei && main_ei && ei != main_ei && !main_ei->is_ancestor_of(ei)) {
fold_resource();
}
} break;
}
}
+void EditorPropertyResource::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_should_stop_editing"), &EditorPropertyResource::_should_stop_editing);
+}
+
EditorPropertyResource::EditorPropertyResource() {
use_sub_inspector = bool(EDITOR_GET("interface/inspector/open_resources_in_current_inspector"));
has_borders = true;
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index 2ec78cdb44..004630da3e 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -683,10 +683,12 @@ class EditorPropertyResource : public EditorProperty {
void _open_editor_pressed();
void _update_preferred_shader();
+ bool _should_stop_editing() const;
protected:
virtual void _set_read_only(bool p_read_only) override;
void _notification(int p_what);
+ static void _bind_methods();
public:
virtual void update_property() override;
diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp
deleted file mode 100644
index bd30fc28d1..0000000000
--- a/editor/editor_quick_open.cpp
+++ /dev/null
@@ -1,308 +0,0 @@
-/**************************************************************************/
-/* editor_quick_open.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "editor_quick_open.h"
-
-#include "core/os/keyboard.h"
-#include "editor/editor_node.h"
-#include "editor/editor_string_names.h"
-#include "editor/themes/editor_scale.h"
-
-Rect2i EditorQuickOpen::prev_rect = Rect2i();
-bool EditorQuickOpen::was_showed = false;
-
-void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dont_clear) {
- base_type = p_base;
- allow_multi_select = p_enable_multi;
- search_options->set_select_mode(allow_multi_select ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE);
-
- if (was_showed) {
- popup(prev_rect);
- } else {
- popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f);
- }
-
- EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem();
- _build_search_cache(efsd);
-
- if (p_dont_clear) {
- search_box->select_all();
- _update_search();
- } else {
- search_box->clear(); // This will emit text_changed.
- }
- search_box->grab_focus();
-}
-
-void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) {
- for (int i = 0; i < p_efsd->get_subdir_count(); i++) {
- _build_search_cache(p_efsd->get_subdir(i));
- }
-
- Vector<String> base_types = base_type.split(",");
- for (int i = 0; i < p_efsd->get_file_count(); i++) {
- String file = p_efsd->get_file_path(i);
- String engine_type = p_efsd->get_file_type(i);
- String script_type = p_efsd->get_file_resource_script_class(i);
- String actual_type = script_type.is_empty() ? engine_type : script_type;
-
- // Iterate all possible base types.
- for (String &parent_type : base_types) {
- if (ClassDB::is_parent_class(engine_type, parent_type) || EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type)) {
- files.push_back(file.substr(6, file.length()));
-
- // Store refs to used icons.
- String ext = file.get_extension();
- if (!icons.has(ext)) {
- icons.insert(ext, EditorNode::get_singleton()->get_class_icon(actual_type, "Object"));
- }
-
- // Stop testing base types as soon as we got a match.
- break;
- }
- }
- }
-}
-
-void EditorQuickOpen::_update_search() {
- const PackedStringArray search_tokens = search_box->get_text().to_lower().replace("/", " ").split(" ", false);
- const bool empty_search = search_tokens.is_empty();
-
- // Filter possible candidates.
- Vector<Entry> entries;
- for (int i = 0; i < files.size(); i++) {
- Entry r;
- r.path = files[i];
- if (empty_search) {
- entries.push_back(r);
- } else {
- r.score = _score_search_result(search_tokens, r.path.to_lower());
- if (r.score > 0) {
- entries.push_back(r);
- }
- }
- }
-
- // Display results
- TreeItem *root = search_options->get_root();
- root->clear_children();
-
- if (entries.size() > 0) {
- if (!empty_search) {
- SortArray<Entry, EntryComparator> sorter;
- sorter.sort(entries.ptrw(), entries.size());
- }
-
- const int class_icon_size = search_options->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
- const int entry_limit = MIN(entries.size(), 300);
- for (int i = 0; i < entry_limit; i++) {
- TreeItem *ti = search_options->create_item(root);
- ti->set_text(0, entries[i].path);
- ti->set_icon_max_width(0, class_icon_size);
- ti->set_icon(0, *icons.lookup_ptr(entries[i].path.get_extension()));
- }
-
- TreeItem *to_select = root->get_first_child();
- to_select->select(0);
- to_select->set_as_cursor(0);
- search_options->scroll_to_item(to_select);
-
- get_ok_button()->set_disabled(false);
- } else {
- search_options->deselect_all();
-
- get_ok_button()->set_disabled(true);
- }
-}
-
-float EditorQuickOpen::_score_search_result(const PackedStringArray &p_search_tokens, const String &p_path) {
- float score = 0.0f;
- int prev_min_match_idx = -1;
-
- for (const String &s : p_search_tokens) {
- int min_match_idx = p_path.find(s);
-
- if (min_match_idx == -1) {
- return 0.0f;
- }
-
- float token_score = s.length();
-
- int max_match_idx = p_path.rfind(s);
-
- // Prioritize the actual file name over folder.
- if (max_match_idx > p_path.rfind("/")) {
- token_score *= 2.0f;
- }
-
- // Prioritize matches at the front of the path token.
- if (min_match_idx == 0 || p_path.contains("/" + s)) {
- token_score += 1.0f;
- }
-
- score += token_score;
-
- // Prioritize tokens which appear in order.
- if (prev_min_match_idx != -1 && max_match_idx > prev_min_match_idx) {
- score += 1.0f;
- }
-
- prev_min_match_idx = min_match_idx;
- }
-
- return score;
-}
-
-void EditorQuickOpen::_confirmed() {
- if (!search_options->get_selected()) {
- return;
- }
- _cleanup();
- hide();
- emit_signal(SNAME("quick_open"));
-}
-
-void EditorQuickOpen::cancel_pressed() {
- _cleanup();
-}
-
-void EditorQuickOpen::_cleanup() {
- files.clear();
- icons.clear();
-}
-
-void EditorQuickOpen::_text_changed(const String &p_newtext) {
- _update_search();
-}
-
-void EditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_event) {
- // Redirect navigational key events to the tree.
- Ref<InputEventKey> key = p_event;
- if (key.is_valid()) {
- if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
- search_options->gui_input(key);
- search_box->accept_event();
-
- if (allow_multi_select) {
- TreeItem *root = search_options->get_root();
- if (!root->get_first_child()) {
- return;
- }
-
- TreeItem *current = search_options->get_selected();
- TreeItem *item = search_options->get_next_selected(root);
- while (item) {
- item->deselect(0);
- item = search_options->get_next_selected(item);
- }
-
- current->select(0);
- current->set_as_cursor(0);
- }
- }
- }
-}
-
-String EditorQuickOpen::get_selected() const {
- TreeItem *ti = search_options->get_selected();
- if (!ti) {
- return String();
- }
-
- return "res://" + ti->get_text(0);
-}
-
-Vector<String> EditorQuickOpen::get_selected_files() const {
- Vector<String> selected_files;
-
- TreeItem *item = search_options->get_next_selected(search_options->get_root());
- while (item) {
- selected_files.push_back("res://" + item->get_text(0));
- item = search_options->get_next_selected(item);
- }
-
- return selected_files;
-}
-
-String EditorQuickOpen::get_base_type() const {
- return base_type;
-}
-
-void EditorQuickOpen::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- connect(SceneStringName(confirmed), callable_mp(this, &EditorQuickOpen::_confirmed));
-
- search_box->set_clear_button_enabled(true);
- } break;
-
- case NOTIFICATION_VISIBILITY_CHANGED: {
- if (!is_visible()) {
- prev_rect = Rect2i(get_position(), get_size());
- was_showed = true;
- }
- } break;
-
- case NOTIFICATION_THEME_CHANGED: {
- search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
- } break;
-
- case NOTIFICATION_EXIT_TREE: {
- disconnect(SceneStringName(confirmed), callable_mp(this, &EditorQuickOpen::_confirmed));
- } break;
- }
-}
-
-void EditorQuickOpen::_bind_methods() {
- ADD_SIGNAL(MethodInfo("quick_open"));
-}
-
-EditorQuickOpen::EditorQuickOpen() {
- VBoxContainer *vbc = memnew(VBoxContainer);
- add_child(vbc);
-
- search_box = memnew(LineEdit);
- search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorQuickOpen::_text_changed));
- search_box->connect(SceneStringName(gui_input), callable_mp(this, &EditorQuickOpen::_sbox_input));
- vbc->add_margin_child(TTR("Search:"), search_box);
- register_text_enter(search_box);
-
- search_options = memnew(Tree);
- search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
- search_options->connect("item_activated", callable_mp(this, &EditorQuickOpen::_confirmed));
- search_options->create_item();
- search_options->set_hide_root(true);
- search_options->set_hide_folding(true);
- search_options->add_theme_constant_override("draw_guides", 1);
- vbc->add_margin_child(TTR("Matches:"), search_options, true);
-
- set_ok_button_text(TTR("Open"));
- set_hide_on_ok(false);
-}
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index a81db5fdaa..0f0287718c 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -33,12 +33,12 @@
#include "editor/audio_stream_preview.h"
#include "editor/editor_help.h"
#include "editor/editor_node.h"
-#include "editor/editor_quick_open.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/scene_tree_dock.h"
@@ -132,6 +132,11 @@ void EditorResourcePicker::_resource_selected() {
emit_signal(SNAME("resource_selected"), edited_resource, false);
}
+void EditorResourcePicker::_resource_changed() {
+ emit_signal(SNAME("resource_changed"), edited_resource);
+ _update_resource();
+}
+
void EditorResourcePicker::_file_selected(const String &p_path) {
Ref<Resource> loaded_resource = ResourceLoader::load(p_path);
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + p_path + "'.");
@@ -167,12 +172,7 @@ void EditorResourcePicker::_file_selected(const String &p_path) {
}
edited_resource = loaded_resource;
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
-}
-
-void EditorResourcePicker::_file_quick_selected() {
- _file_selected(quick_open->get_selected());
+ _resource_changed();
}
void EditorResourcePicker::_resource_saved(Object *p_resource) {
@@ -339,14 +339,14 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
} break;
case OBJ_MENU_QUICKLOAD: {
- if (!quick_open) {
- quick_open = memnew(EditorQuickOpen);
- add_child(quick_open);
- quick_open->connect("quick_open", callable_mp(this, &EditorResourcePicker::_file_quick_selected));
+ const Vector<String> &base_types_string = base_type.split(",");
+
+ Vector<StringName> base_types;
+ for (const String &type : base_types_string) {
+ base_types.push_back(type);
}
- quick_open->popup_dialog(base_type);
- quick_open->set_title(TTR("Resource"));
+ EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
} break;
case OBJ_MENU_INSPECT: {
@@ -357,8 +357,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
case OBJ_MENU_CLEAR: {
edited_resource = Ref<Resource>();
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
} break;
case OBJ_MENU_MAKE_UNIQUE: {
@@ -370,8 +369,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.
edited_resource = unique_resource;
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
} break;
case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: {
@@ -436,9 +434,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
return;
}
-
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
} break;
case OBJ_MENU_SHOW_IN_FILE_SYSTEM: {
@@ -457,8 +453,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
ERR_FAIL_INDEX(to_type, conversions.size());
edited_resource = conversions[to_type]->convert(edited_resource);
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
break;
}
@@ -485,8 +480,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
// Prevent freeing of the object until the end of the update of the resource (GH-88286).
Ref<Resource> old_edited_resource = edited_resource;
edited_resource = Ref<Resource>(resp);
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
} break;
}
}
@@ -782,8 +776,7 @@ void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_
}
edited_resource = dropped_resource;
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
}
}
@@ -956,6 +949,10 @@ void EditorResourcePicker::set_toggle_pressed(bool p_pressed) {
assign_button->set_pressed(p_pressed);
}
+bool EditorResourcePicker::is_toggle_pressed() const {
+ return assign_button->is_pressed();
+}
+
void EditorResourcePicker::set_editable(bool p_editable) {
editable = p_editable;
assign_button->set_disabled(!editable && !edited_resource.is_valid());
@@ -1050,8 +1047,7 @@ void EditorResourcePicker::_duplicate_selected_resources() {
if (meta.size() == 1) { // Root.
edited_resource = unique_resource;
- emit_signal(SNAME("resource_changed"), edited_resource);
- _update_resource();
+ _resource_changed();
} else {
Array parent_meta = item->get_parent()->get_metadata(0);
Ref<Resource> parent = parent_meta[0];
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 05e392da2c..0a32dea3ed 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -36,7 +36,6 @@
class Button;
class ConfirmationDialog;
class EditorFileDialog;
-class EditorQuickOpen;
class PopupMenu;
class TextureRect;
class Tree;
@@ -59,7 +58,6 @@ class EditorResourcePicker : public HBoxContainer {
TextureRect *preview_rect = nullptr;
Button *edit_button = nullptr;
EditorFileDialog *file_dialog = nullptr;
- EditorQuickOpen *quick_open = nullptr;
ConfirmationDialog *duplicate_resources_dialog = nullptr;
Tree *duplicate_resources_tree = nullptr;
@@ -88,7 +86,7 @@ class EditorResourcePicker : public HBoxContainer {
void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj);
void _resource_selected();
- void _file_quick_selected();
+ void _resource_changed();
void _file_selected(const String &p_path);
void _resource_saved(Object *p_resource);
@@ -137,6 +135,7 @@ public:
void set_toggle_mode(bool p_enable);
bool is_toggle_mode() const;
void set_toggle_pressed(bool p_pressed);
+ bool is_toggle_pressed() const;
void set_editable(bool p_editable);
bool is_editable() const;
diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp
index a50b2f3dcc..4dca3b33af 100644
--- a/editor/editor_resource_preview.cpp
+++ b/editor/editor_resource_preview.cpp
@@ -131,7 +131,7 @@ Variant EditorResourcePreviewGenerator::DrawRequester::_post_semaphore() const {
}
bool EditorResourcePreview::is_threaded() const {
- return RSG::texture_storage->can_create_resources_async();
+ return RSG::rasterizer->can_create_resources_async();
}
void EditorResourcePreview::_thread_func(void *ud) {
@@ -414,6 +414,24 @@ void EditorResourcePreview::_update_thumbnail_sizes() {
}
}
+EditorResourcePreview::PreviewItem EditorResourcePreview::get_resource_preview_if_available(const String &p_path) {
+ PreviewItem item;
+ {
+ MutexLock lock(preview_mutex);
+
+ HashMap<String, EditorResourcePreview::Item>::Iterator I = cache.find(p_path);
+ if (!I) {
+ return item;
+ }
+
+ EditorResourcePreview::Item &cached_item = I->value;
+ item.preview = cached_item.preview;
+ item.small_preview = cached_item.small_preview;
+ }
+ preview_sem.post();
+ return item;
+}
+
void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) {
ERR_FAIL_NULL(p_receiver);
ERR_FAIL_COND(!p_res.is_valid());
diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h
index 57b6e4cedb..88cd753a58 100644
--- a/editor/editor_resource_preview.h
+++ b/editor/editor_resource_preview.h
@@ -128,12 +128,19 @@ protected:
public:
static EditorResourcePreview *get_singleton();
+ struct PreviewItem {
+ Ref<Texture2D> preview;
+ Ref<Texture2D> small_preview;
+ };
+
// p_receiver_func callback has signature (String p_path, Ref<Texture2D> p_preview, Ref<Texture2D> p_preview_small, Variant p_userdata)
// p_preview will be null if there was an error
void queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata);
void queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata);
const Dictionary get_preview_metadata(const String &p_path) const;
+ PreviewItem get_resource_preview_if_available(const String &p_path);
+
void add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator);
void remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator);
void check_for_invalidation(const String &p_path);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index c972a6ab27..12a7c3a2ff 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -221,7 +221,6 @@ bool EditorSettings::_get(const StringName &p_name, Variant &r_ret) const {
const VariantContainer *v = props.getptr(p_name);
if (!v) {
- WARN_PRINT("EditorSettings::_get - Property not found: " + String(p_name));
return false;
}
r_ret = v->variant;
@@ -426,12 +425,17 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/display_scale", 0, display_scale_hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/custom_display_scale", 1.0, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
- String ed_screen_hints = "Screen With Mouse Pointer:-4,Screen With Keyboard Focus:-3,Primary Screen:-2"; // Note: Main Window Screen:-1 is not used for the main window.
+ String ed_screen_hints = "Auto (Remembers last position):-5,Screen With Mouse Pointer:-4,Screen With Keyboard Focus:-3,Primary Screen:-2";
for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {
ed_screen_hints += ",Screen " + itos(i + 1) + ":" + itos(i);
}
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/editor_screen", -2, ed_screen_hints)
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/project_manager_screen", -2, ed_screen_hints)
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/editor_screen", EditorSettings::InitialScreen::INITIAL_SCREEN_AUTO, ed_screen_hints)
+
+ String project_manager_screen_hints = "Screen With Mouse Pointer:-4,Screen With Keyboard Focus:-3,Primary Screen:-2";
+ for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) {
+ project_manager_screen_hints += ",Screen " + itos(i + 1) + ":" + itos(i);
+ }
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/project_manager_screen", EditorSettings::InitialScreen::INITIAL_SCREEN_PRIMARY, project_manager_screen_hints)
{
EngineUpdateLabel::UpdateMode default_update_mode = EngineUpdateLabel::UpdateMode::NEWEST_UNSTABLE;
@@ -466,7 +470,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("interface/editor/separate_distraction_mode", false, true);
_initial_set("interface/editor/automatically_open_screenshots", true, true);
EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/single_window_mode", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
- _initial_set("interface/editor/remember_window_size_and_position", true, true);
_initial_set("interface/editor/mouse_extra_buttons_navigate_history", true);
_initial_set("interface/editor/save_each_scene_on_quit", true, true); // Regression
EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/save_on_focus_loss", false, "")
@@ -598,6 +601,10 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "filesystem/file_dialog/display_mode", 0, "Thumbnails,List")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16")
+ // Quick Open dialog
+ _initial_set("filesystem/quick_open_dialog/include_addons", false);
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "filesystem/quick_open_dialog/default_display_mode", 0, "Adaptive,Last Used")
+
// Import (for glft module)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/blender/blender_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
@@ -1417,24 +1424,20 @@ Variant _EDITOR_GET(const String &p_setting) {
}
bool EditorSettings::_property_can_revert(const StringName &p_name) const {
- if (!props.has(p_name)) {
- return false;
- }
-
- if (!props[p_name].has_default_value) {
- return false;
+ const VariantContainer *property = props.getptr(p_name);
+ if (property) {
+ return property->has_default_value;
}
-
- return props[p_name].initial != props[p_name].variant;
+ return false;
}
bool EditorSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
- if (!props.has(p_name) || !props[p_name].has_default_value) {
- return false;
+ const VariantContainer *value = props.getptr(p_name);
+ if (value && value->has_default_value) {
+ r_property = value->initial;
+ return true;
}
-
- r_property = props[p_name].initial;
- return true;
+ return false;
}
void EditorSettings::add_property_hint(const PropertyInfo &p_hint) {
diff --git a/editor/editor_settings.h b/editor/editor_settings.h
index e850406839..d1ccedfe6c 100644
--- a/editor/editor_settings.h
+++ b/editor/editor_settings.h
@@ -62,6 +62,13 @@ public:
NETWORK_ONLINE,
};
+ enum InitialScreen {
+ INITIAL_SCREEN_AUTO = -5, // Remembers last screen position.
+ INITIAL_SCREEN_WITH_MOUSE_FOCUS = -4,
+ INITIAL_SCREEN_WITH_KEYBOARD_FOCUS = -3,
+ INITIAL_SCREEN_PRIMARY = -2,
+ };
+
private:
struct VariantContainer {
int order = 0;
diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp
index 31a5b60d77..52f7a0cee8 100644
--- a/editor/export/editor_export_platform_pc.cpp
+++ b/editor/export/editor_export_platform_pc.cpp
@@ -222,9 +222,15 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset>
err = da->make_dir_recursive(target_path);
if (err == OK) {
err = da->copy_dir(src_path, target_path, -1, true);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("GDExtension"), TTR(vformat("Failed to copy shared object \"%s\".", src_path)));
+ }
}
} else {
err = da->copy(src_path, target_path);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("GDExtension"), TTR(vformat("Failed to copy shared object \"%s\".", src_path)));
+ }
if (err == OK) {
err = sign_shared_object(p_preset, p_debug, target_path);
}
diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp
index 2f57010c2c..bd9c3503f7 100644
--- a/editor/export/editor_export_plugin.cpp
+++ b/editor/export/editor_export_plugin.cpp
@@ -295,6 +295,12 @@ bool EditorExportPlugin::_should_update_export_options(const Ref<EditorExportPla
return ret;
}
+bool EditorExportPlugin::_get_export_option_visibility(const Ref<EditorExportPlatform> &p_export_platform, const String &p_option_name) const {
+ bool ret = true;
+ GDVIRTUAL_CALL(_get_export_option_visibility, p_export_platform, p_option_name, ret);
+ return ret;
+}
+
String EditorExportPlugin::_get_export_option_warning(const Ref<EditorExportPlatform> &p_export_platform, const String &p_option_name) const {
String ret;
GDVIRTUAL_CALL(_get_export_option_warning, p_export_platform, p_option_name, ret);
@@ -354,6 +360,7 @@ void EditorExportPlugin::_bind_methods() {
GDVIRTUAL_BIND(_get_export_options, "platform");
GDVIRTUAL_BIND(_get_export_options_overrides, "platform");
GDVIRTUAL_BIND(_should_update_export_options, "platform");
+ GDVIRTUAL_BIND(_get_export_option_visibility, "platform", "option");
GDVIRTUAL_BIND(_get_export_option_warning, "platform", "option");
GDVIRTUAL_BIND(_get_export_features, "platform", "debug");
diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h
index 8c744b3108..79d3d0954d 100644
--- a/editor/export/editor_export_plugin.h
+++ b/editor/export/editor_export_plugin.h
@@ -132,6 +132,7 @@ protected:
GDVIRTUAL1RC(TypedArray<Dictionary>, _get_export_options, const Ref<EditorExportPlatform> &);
GDVIRTUAL1RC(Dictionary, _get_export_options_overrides, const Ref<EditorExportPlatform> &);
GDVIRTUAL1RC(bool, _should_update_export_options, const Ref<EditorExportPlatform> &);
+ GDVIRTUAL2RC(bool, _get_export_option_visibility, const Ref<EditorExportPlatform> &, String);
GDVIRTUAL2RC(String, _get_export_option_warning, const Ref<EditorExportPlatform> &, String);
GDVIRTUAL0RC_REQUIRED(String, _get_name)
@@ -160,6 +161,7 @@ protected:
virtual void _get_export_options(const Ref<EditorExportPlatform> &p_export_platform, List<EditorExportPlatform::ExportOption> *r_options) const;
virtual Dictionary _get_export_options_overrides(const Ref<EditorExportPlatform> &p_export_platform) const;
virtual bool _should_update_export_options(const Ref<EditorExportPlatform> &p_export_platform) const;
+ virtual bool _get_export_option_visibility(const Ref<EditorExportPlatform> &p_export_platform, const String &p_option_name) const;
virtual String _get_export_option_warning(const Ref<EditorExportPlatform> &p_export_platform, const String &p_option_name) const;
public:
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index 1ca72348e2..da7059b777 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -136,8 +136,29 @@ String EditorExportPreset::_get_property_warning(const StringName &p_name) const
void EditorExportPreset::_get_property_list(List<PropertyInfo> *p_list) const {
for (const KeyValue<StringName, PropertyInfo> &E : properties) {
- if (!value_overrides.has(E.key) && platform->get_export_option_visibility(this, E.key)) {
- p_list->push_back(E.value);
+ if (!value_overrides.has(E.key)) {
+ bool property_visible = platform->get_export_option_visibility(this, E.key);
+ if (!property_visible) {
+ continue;
+ }
+
+ // Get option visibility from editor export plugins.
+ Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
+ for (int i = 0; i < export_plugins.size(); i++) {
+ if (!export_plugins[i]->supports_platform(platform)) {
+ continue;
+ }
+
+ export_plugins.write[i]->set_export_preset(Ref<EditorExportPreset>(this));
+ property_visible = export_plugins[i]->_get_export_option_visibility(platform, E.key);
+ if (!property_visible) {
+ break;
+ }
+ }
+
+ if (property_visible) {
+ p_list->push_back(E.value);
+ }
}
}
}
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 5360b6fb60..2309319376 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -34,6 +34,7 @@
#include "core/io/json.h"
#include "core/io/zip_io.h"
#include "core/version.h"
+#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -876,7 +877,7 @@ Error ExportTemplateManager::install_android_template_from_file(const String &p_
ProgressDialog::get_singleton()->end_task("uncompress_src");
unzClose(pkg);
-
+ EditorFileSystem::get_singleton()->scan_changes();
return OK;
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index c9cc42d68d..53982b37b9 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1865,35 +1865,18 @@ void FileSystemDock::_rename_operation_confirm() {
_rescan();
}
-void FileSystemDock::_duplicate_operation_confirm() {
- String new_name = duplicate_dialog_text->get_text().strip_edges();
- if (new_name.length() == 0) {
- EditorNode::get_singleton()->show_warning(TTR("No name provided."));
- return;
- } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) {
- EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters."));
- return;
- } else if (new_name[0] == '.') {
- EditorNode::get_singleton()->show_warning(TTR("Name begins with a dot."));
- return;
- }
-
- String base_dir = to_duplicate.path.get_base_dir();
- // get_base_dir() returns "some/path" if the original path was "some/path/", so work it around.
- if (to_duplicate.path.ends_with("/")) {
- base_dir = base_dir.get_base_dir();
- }
-
- String new_path = base_dir.path_join(new_name);
-
- // Present a more user friendly warning for name conflict
+void FileSystemDock::_duplicate_operation_confirm(const String &p_path) {
+ String base_dir = p_path.trim_suffix("/").get_base_dir();
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- if (da->file_exists(new_path) || da->dir_exists(new_path)) {
- EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists."));
- return;
- }
+ if (!da->dir_exists(base_dir)) {
+ Error err = da->make_dir_recursive(base_dir);
- _try_duplicate_item(to_duplicate, new_path);
+ if (err != OK) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create base directory: %s"), error_names[err]));
+ return;
+ }
+ }
+ _try_duplicate_item(to_duplicate, p_path);
// Rescan everything.
print_verbose("FileSystem: calling rescan.");
@@ -2531,24 +2514,22 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
} break;
case FILE_DUPLICATE: {
- // Duplicate the selected files.
- for (int i = 0; i < p_selected.size(); i++) {
- to_duplicate.path = p_selected[i];
- to_duplicate.is_file = !to_duplicate.path.ends_with("/");
- if (to_duplicate.is_file) {
- String name = to_duplicate.path.get_file();
- duplicate_dialog->set_title(TTR("Duplicating file:") + " " + name);
- duplicate_dialog_text->set_text(name);
- duplicate_dialog_text->select(0, name.rfind("."));
- } else {
- String name = to_duplicate.path.substr(0, to_duplicate.path.length() - 1).get_file();
- duplicate_dialog->set_title(TTR("Duplicating folder:") + " " + name);
- duplicate_dialog_text->set_text(name);
- duplicate_dialog_text->select(0, name.length());
- }
- duplicate_dialog->popup_centered(Size2(250, 80) * EDSCALE);
- duplicate_dialog_text->grab_focus();
+ if (p_selected.size() != 1) {
+ return;
+ }
+
+ to_duplicate.path = p_selected[0];
+ to_duplicate.is_file = !to_duplicate.path.ends_with("/");
+ if (to_duplicate.is_file) {
+ String name = to_duplicate.path.get_file();
+ make_dir_dialog->config(to_duplicate.path.get_base_dir(), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm),
+ DirectoryCreateDialog::MODE_FILE, TTR("Duplicating file:") + " " + name, name);
+ } else {
+ String name = to_duplicate.path.trim_suffix("/").get_file();
+ make_dir_dialog->config(to_duplicate.path.trim_suffix("/").get_base_dir(), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm),
+ DirectoryCreateDialog::MODE_DIRECTORY, TTR("Duplicating folder:") + " " + name, name);
}
+ make_dir_dialog->popup_centered();
} break;
case FILE_INFO: {
@@ -2563,7 +2544,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
if (!directory.ends_with("/")) {
directory = directory.get_base_dir();
}
- make_dir_dialog->config(directory);
+ make_dir_dialog->config(directory, callable_mp(this, &FileSystemDock::create_directory).bind(directory),
+ DirectoryCreateDialog::MODE_DIRECTORY, TTR("Create Folder"), "new folder");
make_dir_dialog->popup_centered();
} break;
@@ -2793,6 +2775,13 @@ void FileSystemDock::focus_on_filter() {
}
}
+void FileSystemDock::create_directory(const String &p_path, const String &p_base_dir) {
+ Error err = EditorFileSystem::get_singleton()->make_dir_recursive(p_path, p_base_dir);
+ if (err != OK) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create folder: %s"), error_names[err]));
+ }
+}
+
ScriptCreateDialog *FileSystemDock::get_script_create_dialog() const {
return make_script_dialog;
}
@@ -4282,17 +4271,6 @@ FileSystemDock::FileSystemDock() {
overwrite_dialog_footer = memnew(Label);
overwrite_dialog_vb->add_child(overwrite_dialog_footer);
- duplicate_dialog = memnew(ConfirmationDialog);
- VBoxContainer *duplicate_dialog_vb = memnew(VBoxContainer);
- duplicate_dialog->add_child(duplicate_dialog_vb);
-
- duplicate_dialog_text = memnew(LineEdit);
- duplicate_dialog_vb->add_margin_child(TTR("Name:"), duplicate_dialog_text);
- duplicate_dialog->set_ok_button_text(TTR("Duplicate"));
- add_child(duplicate_dialog);
- duplicate_dialog->register_text_enter(duplicate_dialog_text);
- duplicate_dialog->connect(SceneStringName(confirmed), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm));
-
make_dir_dialog = memnew(DirectoryCreateDialog);
add_child(make_dir_dialog);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index ee68f44d4c..819abbd389 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -183,8 +183,6 @@ private:
DependencyRemoveDialog *remove_dialog = nullptr;
EditorDirDialog *move_dialog = nullptr;
- ConfirmationDialog *duplicate_dialog = nullptr;
- LineEdit *duplicate_dialog_text = nullptr;
DirectoryCreateDialog *make_dir_dialog = nullptr;
ConfirmationDialog *overwrite_dialog = nullptr;
@@ -291,7 +289,7 @@ private:
void _resource_created();
void _make_scene_confirm();
void _rename_operation_confirm();
- void _duplicate_operation_confirm();
+ void _duplicate_operation_confirm(const String &p_path);
void _overwrite_dialog_action(bool p_overwrite);
void _convert_dialog_action();
Vector<String> _check_existing();
@@ -384,6 +382,7 @@ public:
void navigate_to_path(const String &p_path);
void focus_on_path();
void focus_on_filter();
+ void create_directory(const String &p_path, const String &p_base_dir);
ScriptCreateDialog *get_script_create_dialog() const;
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index 4b2fd9cb2f..f6ba74fe95 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -30,8 +30,6 @@
#include "editor_bottom_panel.h"
-#include "core/os/time.h"
-#include "core/version.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_about.h"
#include "editor/editor_command_palette.h"
@@ -39,13 +37,10 @@
#include "editor/editor_string_names.h"
#include "editor/engine_update_label.h"
#include "editor/gui/editor_toaster.h"
+#include "editor/gui/editor_version_button.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
-#include "scene/gui/link_button.h"
-
-// The metadata key used to store and retrieve the version text to copy to the clipboard.
-static const String META_TEXT_TO_COPY = "text_to_copy";
void EditorBottomPanel::_notification(int p_what) {
switch (p_what) {
@@ -110,10 +105,6 @@ void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
EditorNode::get_top_split()->set_visible(!p_pressed);
}
-void EditorBottomPanel::_version_button_pressed() {
- DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
-}
-
bool EditorBottomPanel::_button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control) {
if (!p_button->is_pressed()) {
_switch_by_control(true, p_control);
@@ -262,25 +253,9 @@ EditorBottomPanel::EditorBottomPanel() {
editor_toaster = memnew(EditorToaster);
bottom_hbox->add_child(editor_toaster);
- version_btn = memnew(LinkButton);
- version_btn->set_text(VERSION_FULL_CONFIG);
- String hash = String(VERSION_HASH);
- if (hash.length() != 0) {
- hash = " " + vformat("[%s]", hash.left(9));
- }
- // Set the text to copy in metadata as it slightly differs from the button's text.
- version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash);
+ EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_BASIC));
// Fade out the version label to be less prominent, but still readable.
version_btn->set_self_modulate(Color(1, 1, 1, 0.65));
- version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
- String build_date;
- if (VERSION_TIMESTAMP > 0) {
- build_date = Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC";
- } else {
- build_date = TTR("(unknown)");
- }
- version_btn->set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version information."), build_date));
- version_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_version_button_pressed));
version_btn->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
bottom_hbox->add_child(version_btn);
diff --git a/editor/gui/editor_bottom_panel.h b/editor/gui/editor_bottom_panel.h
index 95c767dae5..3d44b3750a 100644
--- a/editor/gui/editor_bottom_panel.h
+++ b/editor/gui/editor_bottom_panel.h
@@ -37,7 +37,6 @@ class Button;
class ConfigFile;
class EditorToaster;
class HBoxContainer;
-class LinkButton;
class VBoxContainer;
class EditorBottomPanel : public PanelContainer {
@@ -55,14 +54,12 @@ class EditorBottomPanel : public PanelContainer {
HBoxContainer *bottom_hbox = nullptr;
HBoxContainer *button_hbox = nullptr;
EditorToaster *editor_toaster = nullptr;
- LinkButton *version_btn = nullptr;
Button *expand_button = nullptr;
Control *last_opened_control = nullptr;
void _switch_by_control(bool p_visible, Control *p_control);
void _switch_to_item(bool p_visible, int p_idx);
void _expand_button_toggled(bool p_pressed);
- void _version_button_pressed();
bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control);
diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp
index 23568a3c2a..481c8ed016 100644
--- a/editor/gui/editor_dir_dialog.cpp
+++ b/editor/gui/editor_dir_dialog.cpp
@@ -175,11 +175,14 @@ void EditorDirDialog::ok_pressed() {
void EditorDirDialog::_make_dir() {
TreeItem *ti = tree->get_selected();
ERR_FAIL_NULL(ti);
- makedialog->config(ti->get_metadata(0));
+ const String &directory = ti->get_metadata(0);
+ makedialog->config(directory, callable_mp(this, &EditorDirDialog::_make_dir_confirm).bind(directory), DirectoryCreateDialog::MODE_DIRECTORY, "new folder");
makedialog->popup_centered();
}
-void EditorDirDialog::_make_dir_confirm(const String &p_path) {
+void EditorDirDialog::_make_dir_confirm(const String &p_path, const String &p_base_dir) {
+ FileSystemDock::get_singleton()->create_directory(p_path, p_base_dir);
+
// Multiple level of directories can be created at once.
String base_dir = p_path.get_base_dir();
while (true) {
@@ -228,5 +231,4 @@ EditorDirDialog::EditorDirDialog() {
makedialog = memnew(DirectoryCreateDialog);
add_child(makedialog);
- makedialog->connect("dir_created", callable_mp(this, &EditorDirDialog::_make_dir_confirm));
}
diff --git a/editor/gui/editor_dir_dialog.h b/editor/gui/editor_dir_dialog.h
index b10cc8dd9f..491eb61749 100644
--- a/editor/gui/editor_dir_dialog.h
+++ b/editor/gui/editor_dir_dialog.h
@@ -56,7 +56,7 @@ class EditorDirDialog : public ConfirmationDialog {
void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String());
void _make_dir();
- void _make_dir_confirm(const String &p_path);
+ void _make_dir_confirm(const String &p_path, const String &p_base_dir);
void _copy_pressed();
void ok_pressed() override;
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index a63c3f7848..77d0ba7a60 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -1580,7 +1580,7 @@ void EditorFileDialog::_favorite_move_down() {
}
void EditorFileDialog::_update_favorites() {
- bool res = (access == ACCESS_RESOURCES);
+ bool access_resources = (access == ACCESS_RESOURCES);
String current = get_current_dir();
favorites->clear();
@@ -1596,8 +1596,11 @@ void EditorFileDialog::_update_favorites() {
for (int i = 0; i < favorited.size(); i++) {
String name = favorited[i];
- bool cres = name.begins_with("res://");
- if (cres != res || !name.ends_with("/")) {
+ if (access_resources != name.begins_with("res://")) {
+ continue;
+ }
+
+ if (!name.ends_with("/")) {
continue;
}
@@ -1609,7 +1612,7 @@ void EditorFileDialog::_update_favorites() {
}
// Compute favorite display text.
- if (res && name == "res://") {
+ if (access_resources && name == "res://") {
if (name == current) {
current_favorite = favorited_paths.size();
}
@@ -1620,7 +1623,7 @@ void EditorFileDialog::_update_favorites() {
if (name == current || name == current + "/") {
current_favorite = favorited_paths.size();
}
- name = name.substr(0, name.length() - 1);
+ name = name.trim_suffix("/");
name = name.get_file();
favorited_paths.append(favorited[i]);
favorited_names.append(name);
@@ -1647,7 +1650,7 @@ void EditorFileDialog::_update_favorites() {
}
void EditorFileDialog::_favorite_pressed() {
- bool res = (access == ACCESS_RESOURCES);
+ bool access_resources = (access == ACCESS_RESOURCES);
String cd = get_current_dir();
if (!cd.ends_with("/")) {
@@ -1657,13 +1660,12 @@ void EditorFileDialog::_favorite_pressed() {
Vector<String> favorited = EditorSettings::get_singleton()->get_favorites();
bool found = false;
- for (int i = 0; i < favorited.size(); i++) {
- bool cres = favorited[i].begins_with("res://");
- if (cres != res) {
+ for (const String &name : favorited) {
+ if (access_resources != name.begins_with("res://")) {
continue;
}
- if (favorited[i] == cd) {
+ if (name == cd) {
found = true;
break;
}
@@ -1683,31 +1685,30 @@ void EditorFileDialog::_favorite_pressed() {
void EditorFileDialog::_update_recent() {
recent->clear();
- bool res = (access == ACCESS_RESOURCES);
+ bool access_resources = (access == ACCESS_RESOURCES);
Vector<String> recentd = EditorSettings::get_singleton()->get_recent_dirs();
Vector<String> recentd_paths;
Vector<String> recentd_names;
+ bool modified = false;
for (int i = 0; i < recentd.size(); i++) {
- bool cres = recentd[i].begins_with("res://");
- if (cres != res) {
+ String name = recentd[i];
+ if (access_resources != name.begins_with("res://")) {
continue;
}
- if (!dir_access->dir_exists(recentd[i])) {
+ if (!dir_access->dir_exists(name)) {
// Remove invalid directory from the list of Recent directories.
recentd.remove_at(i--);
+ modified = true;
continue;
}
// Compute recent directory display text.
- String name = recentd[i];
- if (res && name == "res://") {
+ if (access_resources && name == "res://") {
name = "/";
} else {
- if (name.ends_with("/")) {
- name = name.substr(0, name.length() - 1);
- }
+ name = name.trim_suffix("/");
name = name.get_file();
}
recentd_paths.append(recentd[i]);
@@ -1721,7 +1722,10 @@ void EditorFileDialog::_update_recent() {
recent->set_item_metadata(-1, recentd_paths[i]);
recent->set_item_icon_modulate(-1, get_dir_icon_color(recentd_paths[i]));
}
- EditorSettings::get_singleton()->set_recent_dirs(recentd);
+
+ if (modified) {
+ EditorSettings::get_singleton()->set_recent_dirs(recentd);
+ }
}
void EditorFileDialog::_recent_selected(int p_idx) {
diff --git a/editor/gui/editor_object_selector.cpp b/editor/gui/editor_object_selector.cpp
index b73cd3b65c..ec5caa8c2a 100644
--- a/editor/gui/editor_object_selector.cpp
+++ b/editor/gui/editor_object_selector.cpp
@@ -30,6 +30,7 @@
#include "editor_object_selector.h"
+#include "editor/debugger/editor_debugger_inspector.h"
#include "editor/editor_data.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
@@ -131,6 +132,19 @@ void EditorObjectSelector::update_path() {
Ref<Texture2D> obj_icon;
if (Object::cast_to<MultiNodeEdit>(obj)) {
obj_icon = EditorNode::get_singleton()->get_class_icon(Object::cast_to<MultiNodeEdit>(obj)->get_edited_class_name());
+ } else if (Object::cast_to<EditorDebuggerRemoteObject>(obj)) {
+ String class_name;
+ Ref<Script> base_script = obj->get_script();
+ if (base_script.is_valid()) {
+ class_name = base_script->get_global_name();
+
+ if (class_name.is_empty()) {
+ // If there is no class_name in this script we just take the script path.
+ class_name = base_script->get_path();
+ }
+ }
+
+ obj_icon = EditorNode::get_singleton()->get_class_icon(class_name.is_empty() ? Object::cast_to<EditorDebuggerRemoteObject>(obj)->type_name : class_name);
} else {
obj_icon = EditorNode::get_singleton()->get_object_icon(obj);
}
diff --git a/editor/gui/editor_quick_open_dialog.cpp b/editor/gui/editor_quick_open_dialog.cpp
new file mode 100644
index 0000000000..94a5ff94a3
--- /dev/null
+++ b/editor/gui/editor_quick_open_dialog.cpp
@@ -0,0 +1,962 @@
+/**************************************************************************/
+/* editor_quick_open_dialog.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor_quick_open_dialog.h"
+
+#include "editor/editor_file_system.h"
+#include "editor/editor_node.h"
+#include "editor/editor_resource_preview.h"
+#include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
+#include "editor/themes/editor_scale.h"
+#include "scene/gui/center_container.h"
+#include "scene/gui/check_button.h"
+#include "scene/gui/flow_container.h"
+#include "scene/gui/margin_container.h"
+#include "scene/gui/panel_container.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/texture_rect.h"
+#include "scene/gui/tree.h"
+
+EditorQuickOpenDialog::EditorQuickOpenDialog() {
+ VBoxContainer *vbc = memnew(VBoxContainer);
+ vbc->add_theme_constant_override("separation", 0);
+ add_child(vbc);
+
+ {
+ // Search bar
+ MarginContainer *mc = memnew(MarginContainer);
+ mc->add_theme_constant_override("margin_top", 6);
+ mc->add_theme_constant_override("margin_bottom", 6);
+ mc->add_theme_constant_override("margin_left", 1);
+ mc->add_theme_constant_override("margin_right", 1);
+ vbc->add_child(mc);
+
+ search_box = memnew(LineEdit);
+ search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ search_box->set_placeholder(TTR("Search files..."));
+ search_box->set_clear_button_enabled(true);
+ mc->add_child(search_box);
+ }
+
+ {
+ container = memnew(QuickOpenResultContainer);
+ container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::ok_pressed));
+ vbc->add_child(container);
+ }
+
+ search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorQuickOpenDialog::_search_box_text_changed));
+ search_box->connect(SceneStringName(gui_input), callable_mp(container, &QuickOpenResultContainer::handle_search_box_input));
+ register_text_enter(search_box);
+ get_ok_button()->hide();
+}
+
+String EditorQuickOpenDialog::get_dialog_title(const Vector<StringName> &p_base_types) {
+ if (p_base_types.size() > 1) {
+ return TTR("Select Resource");
+ }
+
+ if (p_base_types[0] == SNAME("PackedScene")) {
+ return TTR("Select Scene");
+ }
+
+ return TTR("Select") + " " + p_base_types[0];
+}
+
+void EditorQuickOpenDialog::popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback) {
+ ERR_FAIL_COND(p_base_types.is_empty());
+ ERR_FAIL_COND(!p_item_selected_callback.is_valid());
+
+ item_selected_callback = p_item_selected_callback;
+
+ container->init(p_base_types);
+ get_ok_button()->set_disabled(container->has_nothing_selected());
+
+ set_title(get_dialog_title(p_base_types));
+ popup_centered_clamped(Size2(710, 650) * EDSCALE, 0.8f);
+ search_box->grab_focus();
+}
+
+void EditorQuickOpenDialog::ok_pressed() {
+ item_selected_callback.call(container->get_selected());
+
+ container->save_selected_item();
+ container->cleanup();
+ search_box->clear();
+ hide();
+}
+
+void EditorQuickOpenDialog::cancel_pressed() {
+ container->cleanup();
+ search_box->clear();
+}
+
+void EditorQuickOpenDialog::_search_box_text_changed(const String &p_query) {
+ container->update_results(p_query.to_lower());
+
+ get_ok_button()->set_disabled(container->has_nothing_selected());
+}
+
+//------------------------- Result Container
+
+QuickOpenResultContainer::QuickOpenResultContainer() {
+ set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ add_theme_constant_override("separation", 0);
+
+ {
+ // Results section
+ panel_container = memnew(PanelContainer);
+ panel_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ add_child(panel_container);
+
+ {
+ // No search results
+ no_results_container = memnew(CenterContainer);
+ no_results_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ no_results_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ panel_container->add_child(no_results_container);
+
+ no_results_label = memnew(Label);
+ no_results_label->add_theme_font_size_override(SceneStringName(font_size), 24 * EDSCALE);
+ no_results_container->add_child(no_results_label);
+ no_results_container->hide();
+ }
+
+ {
+ // Search results
+ scroll_container = memnew(ScrollContainer);
+ scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ scroll_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
+ scroll_container->hide();
+ panel_container->add_child(scroll_container);
+
+ list = memnew(VBoxContainer);
+ list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ list->hide();
+ scroll_container->add_child(list);
+
+ grid = memnew(HFlowContainer);
+ grid->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ grid->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ grid->add_theme_constant_override("v_separation", 18);
+ grid->add_theme_constant_override("h_separation", 4);
+ grid->hide();
+ scroll_container->add_child(grid);
+ }
+ }
+
+ {
+ // Bottom bar
+ HBoxContainer *bottom_bar = memnew(HBoxContainer);
+ add_child(bottom_bar);
+
+ file_details_path = memnew(Label);
+ file_details_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ file_details_path->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
+ file_details_path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
+ bottom_bar->add_child(file_details_path);
+
+ {
+ HBoxContainer *hbc = memnew(HBoxContainer);
+ hbc->add_theme_constant_override("separation", 3);
+ bottom_bar->add_child(hbc);
+
+ include_addons_toggle = memnew(CheckButton);
+ include_addons_toggle->set_flat(true);
+ include_addons_toggle->set_focus_mode(Control::FOCUS_NONE);
+ include_addons_toggle->set_default_cursor_shape(CURSOR_POINTING_HAND);
+ include_addons_toggle->set_tooltip_text(TTR("Include files from addons"));
+ include_addons_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_include_addons));
+ hbc->add_child(include_addons_toggle);
+
+ VSeparator *vsep = memnew(VSeparator);
+ vsep->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ vsep->set_custom_minimum_size(Size2i(0, 14 * EDSCALE));
+ hbc->add_child(vsep);
+
+ display_mode_toggle = memnew(Button);
+ display_mode_toggle->set_flat(true);
+ display_mode_toggle->set_focus_mode(Control::FOCUS_NONE);
+ display_mode_toggle->set_default_cursor_shape(CURSOR_POINTING_HAND);
+ display_mode_toggle->connect(SceneStringName(pressed), callable_mp(this, &QuickOpenResultContainer::_toggle_display_mode));
+ hbc->add_child(display_mode_toggle);
+ }
+ }
+
+ // Creating and deleting nodes while searching is slow, so we allocate
+ // a bunch of result nodes and fill in the content based on result ranking.
+ result_items.resize(TOTAL_ALLOCATED_RESULT_ITEMS);
+ for (int i = 0; i < TOTAL_ALLOCATED_RESULT_ITEMS; i++) {
+ QuickOpenResultItem *item = memnew(QuickOpenResultItem);
+ item->connect(SceneStringName(gui_input), callable_mp(this, &QuickOpenResultContainer::_item_input).bind(i));
+ result_items.write[i] = item;
+ }
+}
+
+QuickOpenResultContainer::~QuickOpenResultContainer() {
+ if (never_opened) {
+ for (QuickOpenResultItem *E : result_items) {
+ memdelete(E);
+ }
+ }
+}
+
+void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
+ base_types = p_base_types;
+ never_opened = false;
+
+ const int display_mode_behavior = EDITOR_GET("filesystem/quick_open_dialog/default_display_mode");
+ const bool adaptive_display_mode = (display_mode_behavior == 0);
+
+ if (adaptive_display_mode) {
+ _set_display_mode(get_adaptive_display_mode(p_base_types));
+ }
+
+ const bool include_addons = EDITOR_GET("filesystem/quick_open_dialog/include_addons");
+ include_addons_toggle->set_pressed_no_signal(include_addons);
+
+ _create_initial_results(include_addons);
+}
+
+void QuickOpenResultContainer::_create_initial_results(bool p_include_addons) {
+ file_type_icons.insert("__default_icon", get_editor_theme_icon(SNAME("Object")));
+ _find_candidates_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), p_include_addons);
+ max_total_results = MIN(candidates.size(), TOTAL_ALLOCATED_RESULT_ITEMS);
+ file_type_icons.clear();
+
+ update_results(query);
+}
+
+void QuickOpenResultContainer::_find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
+ for (int i = 0; i < p_directory->get_subdir_count(); i++) {
+ if (p_include_addons || p_directory->get_name() != "addons") {
+ _find_candidates_in_folder(p_directory->get_subdir(i), p_include_addons);
+ }
+ }
+
+ for (int i = 0; i < p_directory->get_file_count(); i++) {
+ String file_path = p_directory->get_file_path(i);
+
+ const StringName engine_type = p_directory->get_file_type(i);
+ const StringName script_type = p_directory->get_file_resource_script_class(i);
+
+ const bool is_engine_type = script_type == StringName();
+ const StringName &actual_type = is_engine_type ? engine_type : script_type;
+
+ for (const StringName &parent_type : base_types) {
+ bool is_valid = ClassDB::is_parent_class(engine_type, parent_type) || (!is_engine_type && EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type));
+
+ if (is_valid) {
+ Candidate c;
+ c.file_name = file_path.get_file();
+ c.file_directory = file_path.get_base_dir();
+
+ EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(file_path);
+ if (item.preview.is_valid()) {
+ c.thumbnail = item.preview;
+ } else if (file_type_icons.has(actual_type)) {
+ c.thumbnail = *file_type_icons.lookup_ptr(actual_type);
+ } else if (has_theme_icon(actual_type, EditorStringName(EditorIcons))) {
+ c.thumbnail = get_editor_theme_icon(actual_type);
+ file_type_icons.insert(actual_type, c.thumbnail);
+ } else {
+ c.thumbnail = *file_type_icons.lookup_ptr("__default_icon");
+ }
+
+ candidates.push_back(c);
+
+ break; // Stop testing base types as soon as we get a match.
+ }
+ }
+ }
+}
+
+void QuickOpenResultContainer::update_results(const String &p_query) {
+ query = p_query;
+
+ int relevant_candidates = _sort_candidates(p_query);
+ _update_result_items(MIN(relevant_candidates, max_total_results), 0);
+}
+
+int QuickOpenResultContainer::_sort_candidates(const String &p_query) {
+ if (p_query.is_empty()) {
+ return 0;
+ }
+
+ const PackedStringArray search_tokens = p_query.to_lower().replace("/", " ").split(" ", false);
+
+ if (search_tokens.is_empty()) {
+ return 0;
+ }
+
+ // First, we assign a score to each candidate.
+ int num_relevant_candidates = 0;
+ for (Candidate &c : candidates) {
+ c.score = 0;
+ int prev_token_match_pos = -1;
+
+ for (const String &token : search_tokens) {
+ const int file_pos = c.file_name.findn(token);
+ const int dir_pos = c.file_directory.findn(token);
+
+ const bool file_match = file_pos > -1;
+ const bool dir_match = dir_pos > -1;
+ if (!file_match && !dir_match) {
+ c.score = -1.0f;
+ break;
+ }
+
+ float token_score = file_match ? 0.6f : 0.1999f;
+
+ // Add bias for shorter filenames/paths: they resemble the query more.
+ const String &matched_string = file_match ? c.file_name : c.file_directory;
+ int matched_string_token_pos = file_match ? file_pos : dir_pos;
+ token_score += 0.1f * (1.0f - ((float)matched_string_token_pos / (float)matched_string.length()));
+
+ // Add bias if the match happened in the file name, not the extension.
+ if (file_match) {
+ int ext_pos = matched_string.rfind(".");
+ if (ext_pos == -1 || ext_pos > matched_string_token_pos) {
+ token_score += 0.1f;
+ }
+ }
+
+ // Add bias if token is in order.
+ {
+ int candidate_string_token_pos = file_match ? (c.file_directory.length() + file_pos) : dir_pos;
+
+ if (prev_token_match_pos != -1 && candidate_string_token_pos > prev_token_match_pos) {
+ token_score += 0.2f;
+ }
+
+ prev_token_match_pos = candidate_string_token_pos;
+ }
+
+ c.score += token_score;
+ }
+
+ if (c.score > 0.0f) {
+ num_relevant_candidates++;
+ }
+ }
+
+ // Now we will sort the candidates based on score, resolving ties by favoring:
+ // 1. Shorter file length.
+ // 2. Shorter directory length.
+ // 3. Lower alphabetic order.
+ struct CandidateComparator {
+ _FORCE_INLINE_ bool operator()(const Candidate &p_a, const Candidate &p_b) const {
+ if (!Math::is_equal_approx(p_a.score, p_b.score)) {
+ return p_a.score > p_b.score;
+ }
+
+ if (p_a.file_name.length() != p_b.file_name.length()) {
+ return p_a.file_name.length() < p_b.file_name.length();
+ }
+
+ if (p_a.file_directory.length() != p_b.file_directory.length()) {
+ return p_a.file_directory.length() < p_b.file_directory.length();
+ }
+
+ return p_a.file_name < p_b.file_name;
+ }
+ };
+ candidates.sort_custom<CandidateComparator>();
+
+ return num_relevant_candidates;
+}
+
+void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_count, int p_new_selection_index) {
+ List<Candidate> *type_history = nullptr;
+
+ showing_history = false;
+
+ if (query.is_empty()) {
+ if (candidates.size() <= SHOW_ALL_FILES_THRESHOLD) {
+ p_new_visible_results_count = candidates.size();
+ } else {
+ p_new_visible_results_count = 0;
+
+ if (base_types.size() == 1) {
+ type_history = selected_history.lookup_ptr(base_types[0]);
+ if (type_history) {
+ p_new_visible_results_count = type_history->size();
+ showing_history = true;
+ }
+ }
+ }
+ }
+
+ // Only need to update items that were not hidden in previous update.
+ int num_items_needing_updates = MAX(num_visible_results, p_new_visible_results_count);
+ num_visible_results = p_new_visible_results_count;
+
+ for (int i = 0; i < num_items_needing_updates; i++) {
+ QuickOpenResultItem *item = result_items[i];
+
+ if (i < num_visible_results) {
+ if (type_history) {
+ const Candidate &c = type_history->get(i);
+ item->set_content(c.thumbnail, c.file_name, c.file_directory);
+ } else {
+ const Candidate &c = candidates[i];
+ item->set_content(c.thumbnail, c.file_name, c.file_directory);
+ }
+ } else {
+ item->reset();
+ }
+ };
+
+ const bool any_results = num_visible_results > 0;
+ _select_item(any_results ? p_new_selection_index : -1);
+
+ scroll_container->set_visible(any_results);
+ no_results_container->set_visible(!any_results);
+
+ if (!any_results) {
+ if (candidates.is_empty()) {
+ no_results_label->set_text(TTR("No files found for this type"));
+ } else if (query.is_empty()) {
+ no_results_label->set_text(TTR("Start searching to find files..."));
+ } else {
+ no_results_label->set_text(TTR("No results found"));
+ }
+ }
+}
+
+void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_ie) {
+ if (num_visible_results < 0) {
+ return;
+ }
+
+ Ref<InputEventKey> key_event = p_ie;
+ if (key_event.is_valid() && key_event->is_pressed()) {
+ bool move_selection = false;
+
+ switch (key_event->get_keycode()) {
+ case Key::UP:
+ case Key::DOWN:
+ case Key::PAGEUP:
+ case Key::PAGEDOWN: {
+ move_selection = true;
+ } break;
+ case Key::LEFT:
+ case Key::RIGHT: {
+ // Both grid and the search box use left/right keys. By default, grid will take it.
+ // It would be nice if we could check for ALT to give the event to the searchbox cursor.
+ // However, if you press ALT, the searchbox also denies the input.
+ move_selection = (content_display_mode == QuickOpenDisplayMode::GRID);
+ } break;
+ default:
+ break; // Let the event through so it will reach the search box.
+ }
+
+ if (move_selection) {
+ _move_selection_index(key_event->get_keycode());
+ queue_redraw();
+ accept_event();
+ }
+ }
+}
+
+void QuickOpenResultContainer::_move_selection_index(Key p_key) {
+ const int max_index = num_visible_results - 1;
+
+ int idx = selection_index;
+ if (content_display_mode == QuickOpenDisplayMode::LIST) {
+ if (p_key == Key::UP) {
+ idx = (idx == 0) ? max_index : (idx - 1);
+ } else if (p_key == Key::DOWN) {
+ idx = (idx == max_index) ? 0 : (idx + 1);
+ } else if (p_key == Key::PAGEUP) {
+ idx = (idx == 0) ? idx : MAX(idx - 10, 0);
+ } else if (p_key == Key::PAGEDOWN) {
+ idx = (idx == max_index) ? idx : MIN(idx + 10, max_index);
+ }
+ } else {
+ int column_count = grid->get_line_max_child_count();
+
+ if (p_key == Key::LEFT) {
+ idx = (idx == 0) ? max_index : (idx - 1);
+ } else if (p_key == Key::RIGHT) {
+ idx = (idx == max_index) ? 0 : (idx + 1);
+ } else if (p_key == Key::UP) {
+ idx = (idx == 0) ? max_index : MAX(idx - column_count, 0);
+ } else if (p_key == Key::DOWN) {
+ idx = (idx == max_index) ? 0 : MIN(idx + column_count, max_index);
+ } else if (p_key == Key::PAGEUP) {
+ idx = (idx == 0) ? idx : MAX(idx - (3 * column_count), 0);
+ } else if (p_key == Key::PAGEDOWN) {
+ idx = (idx == max_index) ? idx : MIN(idx + (3 * column_count), max_index);
+ }
+ }
+
+ _select_item(idx);
+}
+
+void QuickOpenResultContainer::_select_item(int p_index) {
+ if (!has_nothing_selected()) {
+ result_items[selection_index]->highlight_item(false);
+ }
+
+ selection_index = p_index;
+
+ if (has_nothing_selected()) {
+ file_details_path->set_text("");
+ return;
+ }
+
+ result_items[selection_index]->highlight_item(true);
+ file_details_path->set_text(get_selected() + (showing_history ? TTR(" (recently opened)") : ""));
+
+ const QuickOpenResultItem *item = result_items[selection_index];
+
+ // Copied from Tree.
+ const int selected_position = item->get_position().y;
+ const int selected_size = item->get_size().y;
+ const int scroll_window_size = scroll_container->get_size().y;
+ const int scroll_position = scroll_container->get_v_scroll();
+
+ if (selected_position <= scroll_position) {
+ scroll_container->set_v_scroll(selected_position);
+ } else if (selected_position + selected_size > scroll_position + scroll_window_size) {
+ scroll_container->set_v_scroll(selected_position + selected_size - scroll_window_size);
+ }
+}
+
+void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_index) {
+ Ref<InputEventMouseButton> mb = p_ev;
+
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ _select_item(p_index);
+ emit_signal(SNAME("result_clicked"));
+ }
+}
+
+void QuickOpenResultContainer::_toggle_include_addons(bool p_pressed) {
+ EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/include_addons", p_pressed);
+
+ cleanup();
+ _create_initial_results(p_pressed);
+}
+
+void QuickOpenResultContainer::_toggle_display_mode() {
+ QuickOpenDisplayMode new_display_mode = (content_display_mode == QuickOpenDisplayMode::LIST) ? QuickOpenDisplayMode::GRID : QuickOpenDisplayMode::LIST;
+ _set_display_mode(new_display_mode);
+}
+
+void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
+ content_display_mode = p_display_mode;
+
+ const bool show_list = (content_display_mode == QuickOpenDisplayMode::LIST);
+ if ((show_list && list->is_visible()) || (!show_list && grid->is_visible())) {
+ return;
+ }
+
+ hide();
+
+ // Move result item nodes from one container to the other.
+ CanvasItem *prev_root;
+ CanvasItem *next_root;
+ if (content_display_mode == QuickOpenDisplayMode::LIST) {
+ prev_root = Object::cast_to<CanvasItem>(grid);
+ next_root = Object::cast_to<CanvasItem>(list);
+ } else {
+ prev_root = Object::cast_to<CanvasItem>(list);
+ next_root = Object::cast_to<CanvasItem>(grid);
+ }
+
+ const bool first_time = !list->is_visible() && !grid->is_visible();
+
+ prev_root->hide();
+ for (QuickOpenResultItem *item : result_items) {
+ item->set_display_mode(content_display_mode);
+
+ if (!first_time) {
+ prev_root->remove_child(item);
+ }
+
+ next_root->add_child(item);
+ }
+ next_root->show();
+ show();
+
+ _update_result_items(num_visible_results, selection_index);
+
+ if (content_display_mode == QuickOpenDisplayMode::LIST) {
+ display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileThumbnail")));
+ display_mode_toggle->set_tooltip_text(TTR("Grid view"));
+ } else {
+ display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileList")));
+ display_mode_toggle->set_tooltip_text(TTR("List view"));
+ }
+}
+
+bool QuickOpenResultContainer::has_nothing_selected() const {
+ return selection_index < 0;
+}
+
+String QuickOpenResultContainer::get_selected() const {
+ ERR_FAIL_COND_V_MSG(has_nothing_selected(), String(), "Tried to get selected file, but nothing was selected.");
+
+ if (showing_history) {
+ const List<Candidate> *type_history = selected_history.lookup_ptr(base_types[0]);
+
+ const Candidate &c = type_history->get(selection_index);
+ return c.file_directory.path_join(c.file_name);
+ } else {
+ const Candidate &c = candidates[selection_index];
+ return c.file_directory.path_join(c.file_name);
+ }
+}
+
+QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const Vector<StringName> &p_base_types) {
+ static const Vector<StringName> grid_preferred_types = {
+ "Font",
+ "Texture2D",
+ "Material",
+ "Mesh"
+ };
+
+ for (const StringName &type : grid_preferred_types) {
+ for (const StringName &base_type : p_base_types) {
+ if (base_type == type || ClassDB::is_parent_class(base_type, type))
+ return QuickOpenDisplayMode::GRID;
+ }
+ }
+
+ return QuickOpenDisplayMode::LIST;
+}
+
+void QuickOpenResultContainer::save_selected_item() {
+ if (base_types.size() > 1) {
+ // Getting the type of the file and checking which base type it belongs to should be possible.
+ // However, for now these are not supported, and we don't record this.
+ return;
+ }
+
+ if (showing_history) {
+ // Selecting from history, so already added.
+ return;
+ }
+
+ const StringName &base_type = base_types[0];
+
+ List<Candidate> *type_history = selected_history.lookup_ptr(base_type);
+ if (!type_history) {
+ selected_history.insert(base_type, List<Candidate>());
+ type_history = selected_history.lookup_ptr(base_type);
+ } else {
+ const Candidate &selected = candidates[selection_index];
+
+ for (const Candidate &candidate : *type_history) {
+ if (candidate.file_directory == selected.file_directory && candidate.file_name == selected.file_name) {
+ return;
+ }
+ }
+
+ if (type_history->size() > 8) {
+ type_history->pop_back();
+ }
+ }
+
+ type_history->push_front(candidates[selection_index]);
+}
+
+void QuickOpenResultContainer::cleanup() {
+ num_visible_results = 0;
+ candidates.clear();
+ _select_item(-1);
+
+ for (QuickOpenResultItem *item : result_items) {
+ item->reset();
+ }
+}
+
+void QuickOpenResultContainer::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ Color text_color = get_theme_color("font_readonly_color", EditorStringName(Editor));
+ file_details_path->add_theme_color_override(SceneStringName(font_color), text_color);
+ no_results_label->add_theme_color_override(SceneStringName(font_color), text_color);
+
+ panel_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
+
+ if (content_display_mode == QuickOpenDisplayMode::LIST) {
+ display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileThumbnail")));
+ } else {
+ display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileList")));
+ }
+ } break;
+ }
+}
+
+void QuickOpenResultContainer::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("result_clicked"));
+}
+
+//------------------------- Result Item
+
+QuickOpenResultItem::QuickOpenResultItem() {
+ set_focus_mode(FocusMode::FOCUS_ALL);
+ _set_enabled(false);
+ set_default_cursor_shape(CURSOR_POINTING_HAND);
+
+ list_item = memnew(QuickOpenResultListItem);
+ list_item->hide();
+ add_child(list_item);
+
+ grid_item = memnew(QuickOpenResultGridItem);
+ grid_item->hide();
+ add_child(grid_item);
+}
+
+void QuickOpenResultItem::set_display_mode(QuickOpenDisplayMode p_display_mode) {
+ if (p_display_mode == QuickOpenDisplayMode::LIST) {
+ grid_item->hide();
+ list_item->show();
+ } else {
+ list_item->hide();
+ grid_item->show();
+ }
+
+ queue_redraw();
+}
+
+void QuickOpenResultItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
+ _set_enabled(true);
+
+ if (list_item->is_visible()) {
+ list_item->set_content(p_thumbnail, p_file, p_file_directory);
+ } else {
+ grid_item->set_content(p_thumbnail, p_file);
+ }
+}
+
+void QuickOpenResultItem::reset() {
+ _set_enabled(false);
+
+ is_hovering = false;
+ is_selected = false;
+
+ if (list_item->is_visible()) {
+ list_item->reset();
+ } else {
+ grid_item->reset();
+ }
+}
+
+void QuickOpenResultItem::highlight_item(bool p_enabled) {
+ is_selected = p_enabled;
+
+ if (list_item->is_visible()) {
+ if (p_enabled) {
+ list_item->highlight_item(highlighted_font_color);
+ } else {
+ list_item->remove_highlight();
+ }
+ } else {
+ if (p_enabled) {
+ grid_item->highlight_item(highlighted_font_color);
+ } else {
+ grid_item->remove_highlight();
+ }
+ }
+
+ queue_redraw();
+}
+
+void QuickOpenResultItem::_set_enabled(bool p_enabled) {
+ set_visible(p_enabled);
+ set_process(p_enabled);
+ set_process_input(p_enabled);
+}
+
+void QuickOpenResultItem::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_MOUSE_ENTER:
+ case NOTIFICATION_MOUSE_EXIT: {
+ is_hovering = is_visible() && p_what == NOTIFICATION_MOUSE_ENTER;
+ queue_redraw();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ selected_stylebox = get_theme_stylebox("selected", "Tree");
+ hovering_stylebox = get_theme_stylebox("hover", "Tree");
+ highlighted_font_color = get_theme_color("font_focus_color", EditorStringName(Editor));
+ } break;
+ case NOTIFICATION_DRAW: {
+ if (is_selected) {
+ draw_style_box(selected_stylebox, Rect2(Point2(), get_size()));
+ } else if (is_hovering) {
+ draw_style_box(hovering_stylebox, Rect2(Point2(), get_size()));
+ }
+ } break;
+ }
+}
+
+//----------------- List item
+
+QuickOpenResultListItem::QuickOpenResultListItem() {
+ set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ add_theme_constant_override("separation", 4 * EDSCALE);
+
+ {
+ image_container = memnew(MarginContainer);
+ image_container->add_theme_constant_override("margin_top", 2 * EDSCALE);
+ image_container->add_theme_constant_override("margin_bottom", 2 * EDSCALE);
+ image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN * EDSCALE);
+ image_container->add_theme_constant_override("margin_right", 0);
+ add_child(image_container);
+
+ thumbnail = memnew(TextureRect);
+ thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ thumbnail->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
+ thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_SCALE);
+ image_container->add_child(thumbnail);
+ }
+
+ {
+ text_container = memnew(VBoxContainer);
+ text_container->add_theme_constant_override("separation", -6 * EDSCALE);
+ text_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ text_container->set_v_size_flags(Control::SIZE_FILL);
+ add_child(text_container);
+
+ name = memnew(Label);
+ name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
+ name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT);
+ text_container->add_child(name);
+
+ path = memnew(Label);
+ path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
+ path->add_theme_font_size_override(SceneStringName(font_size), 12 * EDSCALE);
+ text_container->add_child(path);
+ }
+}
+
+void QuickOpenResultListItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
+ thumbnail->set_texture(p_thumbnail);
+ name->set_text(p_file);
+ path->set_text(p_file_directory);
+
+ const int max_size = 32 * EDSCALE;
+ bool uses_icon = p_thumbnail->get_width() < max_size;
+
+ if (uses_icon) {
+ thumbnail->set_custom_minimum_size(p_thumbnail->get_size());
+
+ int margin_needed = (max_size - p_thumbnail->get_width()) / 2;
+ image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN + margin_needed);
+ image_container->add_theme_constant_override("margin_right", margin_needed);
+ } else {
+ thumbnail->set_custom_minimum_size(Size2i(max_size, max_size));
+ image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN);
+ image_container->add_theme_constant_override("margin_right", 0);
+ }
+}
+
+void QuickOpenResultListItem::reset() {
+ name->set_text("");
+ thumbnail->set_texture(nullptr);
+ path->set_text("");
+}
+
+void QuickOpenResultListItem::highlight_item(const Color &p_color) {
+ name->add_theme_color_override(SceneStringName(font_color), p_color);
+}
+
+void QuickOpenResultListItem::remove_highlight() {
+ name->remove_theme_color_override(SceneStringName(font_color));
+}
+
+void QuickOpenResultListItem::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ path->add_theme_color_override(SceneStringName(font_color), get_theme_color("font_disabled_color", EditorStringName(Editor)));
+ } break;
+ }
+}
+
+//--------------- Grid Item
+
+QuickOpenResultGridItem::QuickOpenResultGridItem() {
+ set_h_size_flags(Control::SIZE_FILL);
+ set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ add_theme_constant_override("separation", -2 * EDSCALE);
+
+ thumbnail = memnew(TextureRect);
+ thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ thumbnail->set_custom_minimum_size(Size2i(80 * EDSCALE, 64 * EDSCALE));
+ add_child(thumbnail);
+
+ name = memnew(Label);
+ name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
+ name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
+ name->add_theme_font_size_override(SceneStringName(font_size), 13 * EDSCALE);
+ add_child(name);
+}
+
+void QuickOpenResultGridItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file) {
+ thumbnail->set_texture(p_thumbnail);
+
+ const String &file_name = p_file.get_basename();
+ name->set_text(file_name);
+ name->set_tooltip_text(file_name);
+
+ bool uses_icon = p_thumbnail->get_width() < (32 * EDSCALE);
+
+ if (uses_icon || p_thumbnail->get_height() <= thumbnail->get_custom_minimum_size().y) {
+ thumbnail->set_expand_mode(TextureRect::EXPAND_KEEP_SIZE);
+ thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED);
+ } else {
+ thumbnail->set_expand_mode(TextureRect::EXPAND_FIT_WIDTH_PROPORTIONAL);
+ thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_SCALE);
+ }
+}
+
+void QuickOpenResultGridItem::reset() {
+ name->set_text("");
+ thumbnail->set_texture(nullptr);
+}
+
+void QuickOpenResultGridItem::highlight_item(const Color &p_color) {
+ name->add_theme_color_override(SceneStringName(font_color), p_color);
+}
+
+void QuickOpenResultGridItem::remove_highlight() {
+ name->remove_theme_color_override(SceneStringName(font_color));
+}
diff --git a/editor/gui/editor_quick_open_dialog.h b/editor/gui/editor_quick_open_dialog.h
new file mode 100644
index 0000000000..49257aed6b
--- /dev/null
+++ b/editor/gui/editor_quick_open_dialog.h
@@ -0,0 +1,232 @@
+/**************************************************************************/
+/* editor_quick_open_dialog.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef EDITOR_QUICK_OPEN_DIALOG_H
+#define EDITOR_QUICK_OPEN_DIALOG_H
+
+#include "core/templates/oa_hash_map.h"
+#include "scene/gui/dialogs.h"
+
+class Button;
+class CenterContainer;
+class CheckButton;
+class EditorFileSystemDirectory;
+class LineEdit;
+class HFlowContainer;
+class MarginContainer;
+class PanelContainer;
+class ScrollContainer;
+class StringName;
+class Texture2D;
+class TextureRect;
+class VBoxContainer;
+
+class QuickOpenResultItem;
+
+enum class QuickOpenDisplayMode {
+ GRID,
+ LIST,
+};
+
+class QuickOpenResultContainer : public VBoxContainer {
+ GDCLASS(QuickOpenResultContainer, VBoxContainer)
+
+public:
+ void init(const Vector<StringName> &p_base_types);
+ void handle_search_box_input(const Ref<InputEvent> &p_ie);
+ void update_results(const String &p_query);
+
+ bool has_nothing_selected() const;
+ String get_selected() const;
+
+ void save_selected_item();
+ void cleanup();
+
+ QuickOpenResultContainer();
+ ~QuickOpenResultContainer();
+
+protected:
+ void _notification(int p_what);
+
+private:
+ static const int TOTAL_ALLOCATED_RESULT_ITEMS = 100;
+ static const int SHOW_ALL_FILES_THRESHOLD = 30;
+
+ struct Candidate {
+ String file_name;
+ String file_directory;
+
+ Ref<Texture2D> thumbnail;
+ float score = 0;
+ };
+
+ Vector<StringName> base_types;
+ Vector<Candidate> candidates;
+
+ OAHashMap<StringName, List<Candidate>> selected_history;
+
+ String query;
+ int selection_index = -1;
+ int num_visible_results = 0;
+ int max_total_results = 0;
+
+ bool showing_history = false;
+ bool never_opened = true;
+
+ QuickOpenDisplayMode content_display_mode = QuickOpenDisplayMode::LIST;
+ Vector<QuickOpenResultItem *> result_items;
+
+ ScrollContainer *scroll_container = nullptr;
+ VBoxContainer *list = nullptr;
+ HFlowContainer *grid = nullptr;
+
+ PanelContainer *panel_container = nullptr;
+ CenterContainer *no_results_container = nullptr;
+ Label *no_results_label = nullptr;
+
+ Label *file_details_path = nullptr;
+ Button *display_mode_toggle = nullptr;
+ CheckButton *include_addons_toggle = nullptr;
+
+ OAHashMap<StringName, Ref<Texture2D>> file_type_icons;
+
+ static QuickOpenDisplayMode get_adaptive_display_mode(const Vector<StringName> &p_base_types);
+
+ void _create_initial_results(bool p_include_addons);
+ void _find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
+
+ int _sort_candidates(const String &p_query);
+ void _update_result_items(int p_new_visible_results_count, int p_new_selection_index);
+
+ void _move_selection_index(Key p_key);
+ void _select_item(int p_index);
+
+ void _item_input(const Ref<InputEvent> &p_ev, int p_index);
+
+ void _set_display_mode(QuickOpenDisplayMode p_display_mode);
+ void _toggle_display_mode();
+ void _toggle_include_addons(bool p_pressed);
+
+ static void _bind_methods();
+};
+
+class QuickOpenResultGridItem : public VBoxContainer {
+ GDCLASS(QuickOpenResultGridItem, VBoxContainer)
+
+public:
+ QuickOpenResultGridItem();
+
+ void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name);
+ void reset();
+ void highlight_item(const Color &p_color);
+ void remove_highlight();
+
+private:
+ TextureRect *thumbnail = nullptr;
+ Label *name = nullptr;
+};
+
+class QuickOpenResultListItem : public HBoxContainer {
+ GDCLASS(QuickOpenResultListItem, HBoxContainer)
+
+public:
+ QuickOpenResultListItem();
+
+ void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
+ void reset();
+ void highlight_item(const Color &p_color);
+ void remove_highlight();
+
+protected:
+ void _notification(int p_what);
+
+private:
+ static const int CONTAINER_MARGIN = 8;
+
+ MarginContainer *image_container = nullptr;
+ VBoxContainer *text_container = nullptr;
+
+ TextureRect *thumbnail = nullptr;
+ Label *name = nullptr;
+ Label *path = nullptr;
+};
+
+class QuickOpenResultItem : public HBoxContainer {
+ GDCLASS(QuickOpenResultItem, HBoxContainer)
+
+public:
+ QuickOpenResultItem();
+
+ void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
+ void set_display_mode(QuickOpenDisplayMode p_display_mode);
+ void reset();
+
+ void highlight_item(bool p_enabled);
+
+protected:
+ void _notification(int p_what);
+
+private:
+ QuickOpenResultListItem *list_item = nullptr;
+ QuickOpenResultGridItem *grid_item = nullptr;
+
+ Ref<StyleBox> selected_stylebox;
+ Ref<StyleBox> hovering_stylebox;
+ Color highlighted_font_color;
+
+ bool is_hovering = false;
+ bool is_selected = false;
+
+ void _set_enabled(bool p_enabled);
+};
+
+class EditorQuickOpenDialog : public AcceptDialog {
+ GDCLASS(EditorQuickOpenDialog, AcceptDialog);
+
+public:
+ void popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback);
+ EditorQuickOpenDialog();
+
+protected:
+ virtual void cancel_pressed() override;
+ virtual void ok_pressed() override;
+
+private:
+ static String get_dialog_title(const Vector<StringName> &p_base_types);
+
+ LineEdit *search_box = nullptr;
+ QuickOpenResultContainer *container = nullptr;
+
+ Callable item_selected_callback;
+
+ void _search_box_text_changed(const String &p_query);
+};
+
+#endif // EDITOR_QUICK_OPEN_DIALOG_H
diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp
index 9050ee0cd4..908b1e6719 100644
--- a/editor/gui/editor_run_bar.cpp
+++ b/editor/gui/editor_run_bar.cpp
@@ -34,10 +34,10 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
-#include "editor/editor_quick_open.h"
#include "editor/editor_run_native.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
+#include "editor/gui/editor_quick_open_dialog.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel_container.h"
@@ -121,16 +121,15 @@ void EditorRunBar::_write_movie_toggled(bool p_enabled) {
}
}
-void EditorRunBar::_quick_run_selected() {
- play_custom_scene(quick_run->get_selected());
+void EditorRunBar::_quick_run_selected(const String &p_file_path) {
+ play_custom_scene(p_file_path);
}
void EditorRunBar::_play_custom_pressed() {
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
stop_playing();
- quick_run->popup_dialog("PackedScene", true);
- quick_run->set_title(TTR("Quick Run Scene..."));
+ EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected));
play_custom_scene_button->set_pressed(false);
} else {
// Reload if already running a custom scene.
@@ -446,8 +445,4 @@ EditorRunBar::EditorRunBar() {
write_movie_button->set_focus_mode(Control::FOCUS_NONE);
write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
write_movie_button->connect(SceneStringName(toggled), callable_mp(this, &EditorRunBar::_write_movie_toggled));
-
- quick_run = memnew(EditorQuickOpen);
- add_child(quick_run);
- quick_run->connect("quick_open", callable_mp(this, &EditorRunBar::_quick_run_selected));
}
diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h
index 1cb999612a..d8238aa2c5 100644
--- a/editor/gui/editor_run_bar.h
+++ b/editor/gui/editor_run_bar.h
@@ -37,7 +37,6 @@
class Button;
class EditorRunNative;
-class EditorQuickOpen;
class PanelContainer;
class HBoxContainer;
@@ -68,8 +67,6 @@ class EditorRunBar : public MarginContainer {
PanelContainer *write_movie_panel = nullptr;
Button *write_movie_button = nullptr;
- EditorQuickOpen *quick_run = nullptr;
-
RunMode current_mode = RunMode::STOPPED;
String run_custom_filename;
String run_current_filename;
@@ -78,7 +75,7 @@ class EditorRunBar : public MarginContainer {
void _update_play_buttons();
void _write_movie_toggled(bool p_enabled);
- void _quick_run_selected();
+ void _quick_run_selected(const String &p_file_path);
void _play_current_pressed();
void _play_custom_pressed();
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/gui/editor_version_button.cpp
index 2a1ea93ac8..635d66f42a 100644
--- a/editor/plugins/cpu_particles_3d_editor_plugin.h
+++ b/editor/gui/editor_version_button.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* cpu_particles_3d_editor_plugin.h */
+/* editor_version_button.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,58 +28,58 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef CPU_PARTICLES_3D_EDITOR_PLUGIN_H
-#define CPU_PARTICLES_3D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/gpu_particles_3d_editor_plugin.h"
-#include "scene/3d/cpu_particles_3d.h"
-
-class CPUParticles3DEditor : public GPUParticles3DEditorBase {
- GDCLASS(CPUParticles3DEditor, GPUParticles3DEditorBase);
-
- enum Menu {
- MENU_OPTION_GENERATE_AABB,
- MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
- MENU_OPTION_CLEAR_EMISSION_VOLUME,
- MENU_OPTION_RESTART,
- MENU_OPTION_CONVERT_TO_GPU_PARTICLES,
- };
-
- ConfirmationDialog *generate_aabb = nullptr;
- SpinBox *generate_seconds = nullptr;
- CPUParticles3D *node = nullptr;
-
- void _generate_aabb();
-
- void _menu_option(int);
-
- friend class CPUParticles3DEditorPlugin;
-
- virtual void _generate_emission_points() override;
-
-protected:
- void _notification(int p_notification);
- void _node_removed(Node *p_node);
-
-public:
- void edit(CPUParticles3D *p_particles);
- CPUParticles3DEditor();
-};
-
-class CPUParticles3DEditorPlugin : public EditorPlugin {
- GDCLASS(CPUParticles3DEditorPlugin, EditorPlugin);
-
- CPUParticles3DEditor *particles_editor = nullptr;
-
-public:
- virtual String get_name() const override { return "CPUParticles3D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- CPUParticles3DEditorPlugin();
- ~CPUParticles3DEditorPlugin();
-};
-
-#endif // CPU_PARTICLES_3D_EDITOR_PLUGIN_H
+#include "editor_version_button.h"
+
+#include "core/os/time.h"
+#include "core/version.h"
+
+String _get_version_string(EditorVersionButton::VersionFormat p_format) {
+ String main;
+ switch (p_format) {
+ case EditorVersionButton::FORMAT_BASIC: {
+ return VERSION_FULL_CONFIG;
+ } break;
+ case EditorVersionButton::FORMAT_WITH_BUILD: {
+ main = "v" VERSION_FULL_BUILD;
+ } break;
+ case EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD: {
+ main = VERSION_FULL_NAME;
+ } break;
+ default: {
+ ERR_FAIL_V_MSG(VERSION_FULL_NAME, "Unexpected format: " + itos(p_format));
+ } break;
+ }
+
+ String hash = VERSION_HASH;
+ if (!hash.is_empty()) {
+ hash = vformat(" [%s]", hash.left(9));
+ }
+ return main + hash;
+}
+
+void EditorVersionButton::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE: {
+ // This can't be done in the constructor because theme cache is not ready yet.
+ set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
+ set_text(_get_version_string(format));
+ } break;
+ }
+}
+
+void EditorVersionButton::pressed() {
+ DisplayServer::get_singleton()->clipboard_set(_get_version_string(FORMAT_WITH_BUILD));
+}
+
+EditorVersionButton::EditorVersionButton(VersionFormat p_format) {
+ format = p_format;
+ set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
+
+ String build_date;
+ if (VERSION_TIMESTAMP > 0) {
+ build_date = Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC";
+ } else {
+ build_date = TTR("(unknown)");
+ }
+ set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version information."), build_date));
+}
diff --git a/editor/gui/editor_version_button.h b/editor/gui/editor_version_button.h
new file mode 100644
index 0000000000..591c3d483e
--- /dev/null
+++ b/editor/gui/editor_version_button.h
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* editor_version_button.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef EDITOR_VERSION_BUTTON_H
+#define EDITOR_VERSION_BUTTON_H
+
+#include "scene/gui/link_button.h"
+
+class EditorVersionButton : public LinkButton {
+ GDCLASS(EditorVersionButton, LinkButton);
+
+public:
+ enum VersionFormat {
+ // 4.3.2.stable
+ FORMAT_BASIC,
+ // v4.3.2.stable.mono [HASH]
+ FORMAT_WITH_BUILD,
+ // Godot Engine v4.3.2.stable.mono.official [HASH]
+ FORMAT_WITH_NAME_AND_BUILD,
+ };
+
+private:
+ VersionFormat format = FORMAT_WITH_NAME_AND_BUILD;
+
+protected:
+ void _notification(int p_what);
+
+ virtual void pressed() override;
+
+public:
+ EditorVersionButton(VersionFormat p_format);
+};
+
+#endif // EDITOR_VERSION_BUTTON_H
diff --git a/editor/icons/GuiToggleOffMirrored.svg b/editor/icons/GuiToggleOffMirrored.svg
index 4e7f63d573..138a856a1d 100644
--- a/editor/icons/GuiToggleOffMirrored.svg
+++ b/editor/icons/GuiToggleOffMirrored.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="38" height="16"><rect width="36" height="14" x="1" y="1" fill="gray" rx="7"/><circle cx="8" cy="8" r="5" fill="#b3b3b3"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="38" height="16"><g fill="#e0e0e0"><rect width="36" height="14" x="1" y="1" fill-opacity=".2" rx="7"/><circle cx="30" cy="8" r="5"/></g></svg> \ No newline at end of file
diff --git a/editor/icons/Marker.svg b/editor/icons/Marker.svg
new file mode 100644
index 0000000000..ff91a4a947
--- /dev/null
+++ b/editor/icons/Marker.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="-959.5 540.5 10 10"><path fill="#e0e0e0" d="m-954.5 550-3-3v-6h6v6z"/></svg> \ No newline at end of file
diff --git a/editor/icons/MarkerSelected.svg b/editor/icons/MarkerSelected.svg
new file mode 100644
index 0000000000..c581a3a651
--- /dev/null
+++ b/editor/icons/MarkerSelected.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="-959.5 540.5 10 10"><path fill="#5fb2ff" d="m-952 541.5v5.086l-2.5 2.5-2.5-2.5v-5.086zm1-1h-7v6.5l3.5 3.5 3.5-3.5z"/><path fill="#003e7a" d="m-957 546.586 2.5 2.5 2.5-2.5v-5.086h-5z"/></svg> \ No newline at end of file
diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp
index c526ca0b0c..e124697b20 100644
--- a/editor/import/dynamic_font_import_settings.cpp
+++ b/editor/import/dynamic_font_import_settings.cpp
@@ -96,7 +96,7 @@ struct UniRange {
};
// Unicode Character Blocks
-// Source: https://www.unicode.org/Public/14.0.0/ucd/Blocks.txt
+// Source: https://www.unicode.org/Public/16.0.0/ucd/Blocks.txt
static UniRange unicode_ranges[] = {
{ 0x0000, 0x007F, U"Basic Latin" },
{ 0x0080, 0x00FF, U"Latin-1 Supplement" },
@@ -283,6 +283,7 @@ static UniRange unicode_ranges[] = {
{ 0x10500, 0x1052F, U"Elbasan" },
{ 0x10530, 0x1056F, U"Caucasian Albanian" },
{ 0x10570, 0x105BF, U"Vithkuqi" },
+ { 0x105C0, 0x105FF, U"Todhri" },
{ 0x10600, 0x1077F, U"Linear A" },
{ 0x10780, 0x107BF, U"Latin Extended-F" },
{ 0x10800, 0x1083F, U"Cypriot Syllabary" },
@@ -305,6 +306,7 @@ static UniRange unicode_ranges[] = {
{ 0x10C00, 0x10C4F, U"Old Turkic" },
{ 0x10C80, 0x10CFF, U"Old Hungarian" },
{ 0x10D00, 0x10D3F, U"Hanifi Rohingya" },
+ { 0x10D40, 0x10D8F, U"Garay" },
{ 0x10E60, 0x10E7F, U"Rumi Numeral Symbols" },
{ 0x10E80, 0x10EBF, U"Yezidi" },
{ 0x10EC0, 0x10EFF, U"Arabic Extended-C" },
@@ -324,12 +326,14 @@ static UniRange unicode_ranges[] = {
{ 0x11280, 0x112AF, U"Multani" },
{ 0x112B0, 0x112FF, U"Khudawadi" },
{ 0x11300, 0x1137F, U"Grantha" },
+ { 0x11380, 0x113FF, U"Tulu-Tigalari" },
{ 0x11400, 0x1147F, U"Newa" },
{ 0x11480, 0x114DF, U"Tirhuta" },
{ 0x11580, 0x115FF, U"Siddham" },
{ 0x11600, 0x1165F, U"Modi" },
{ 0x11660, 0x1167F, U"Mongolian Supplement" },
{ 0x11680, 0x116CF, U"Takri" },
+ { 0x116D0, 0x116FF, U"Myanmar Extended-C" },
{ 0x11700, 0x1174F, U"Ahom" },
{ 0x11800, 0x1184F, U"Dogra" },
{ 0x118A0, 0x118FF, U"Warang Citi" },
@@ -340,6 +344,7 @@ static UniRange unicode_ranges[] = {
{ 0x11AB0, 0x11ABF, U"Unified Canadian Aboriginal Syllabics Extended-A" },
{ 0x11AC0, 0x11AFF, U"Pau Cin Hau" },
{ 0x11B00, 0x11B5F, U"Devanagari Extended-A" },
+ { 0x11BC0, 0x11BFF, U"Sunuwar" },
{ 0x11C00, 0x11C6F, U"Bhaiksuki" },
{ 0x11C70, 0x11CBF, U"Marchen" },
{ 0x11D00, 0x11D5F, U"Masaram Gondi" },
@@ -354,12 +359,15 @@ static UniRange unicode_ranges[] = {
{ 0x12F90, 0x12FFF, U"Cypro-Minoan" },
{ 0x13000, 0x1342F, U"Egyptian Hieroglyphs" },
{ 0x13430, 0x1343F, U"Egyptian Hieroglyph Format Controls" },
+ { 0x13460, 0x143FF, U"Egyptian Hieroglyphs Extended-A" },
{ 0x14400, 0x1467F, U"Anatolian Hieroglyphs" },
+ { 0x16100, 0x1613F, U"Gurung Khema" },
{ 0x16800, 0x16A3F, U"Bamum Supplement" },
{ 0x16A40, 0x16A6F, U"Mro" },
{ 0x16A70, 0x16ACF, U"Tangsa" },
{ 0x16AD0, 0x16AFF, U"Bassa Vah" },
{ 0x16B00, 0x16B8F, U"Pahawh Hmong" },
+ { 0x16D40, 0x16D7F, U"Kirat Rai" },
{ 0x16E40, 0x16E9F, U"Medefaidrin" },
{ 0x16F00, 0x16F9F, U"Miao" },
{ 0x16FE0, 0x16FFF, U"Ideographic Symbols and Punctuation" },
@@ -374,6 +382,7 @@ static UniRange unicode_ranges[] = {
{ 0x1B170, 0x1B2FF, U"Nushu" },
{ 0x1BC00, 0x1BC9F, U"Duployan" },
{ 0x1BCA0, 0x1BCAF, U"Shorthand Format Controls" },
+ { 0x1CC00, 0x1CEBF, U"Symbols for Legacy Computing Supplement" },
{ 0x1CF00, 0x1CFCF, U"Znamenny Musical Notation" },
{ 0x1D000, 0x1D0FF, U"Byzantine Musical Symbols" },
{ 0x1D100, 0x1D1FF, U"Musical Symbols" },
@@ -391,6 +400,7 @@ static UniRange unicode_ranges[] = {
{ 0x1E290, 0x1E2BF, U"Toto" },
{ 0x1E2C0, 0x1E2FF, U"Wancho" },
{ 0x1E4D0, 0x1E4FF, U"Nag Mundari" },
+ { 0x1E5D0, 0x1E5FF, U"Ol Onal" },
{ 0x1E7E0, 0x1E7FF, U"Ethiopic Extended-B" },
{ 0x1E800, 0x1E8DF, U"Mende Kikakui" },
{ 0x1E900, 0x1E95F, U"Adlam" },
@@ -418,6 +428,7 @@ static UniRange unicode_ranges[] = {
{ 0x2B740, 0x2B81F, U"CJK Unified Ideographs Extension D" },
{ 0x2B820, 0x2CEAF, U"CJK Unified Ideographs Extension E" },
{ 0x2CEB0, 0x2EBEF, U"CJK Unified Ideographs Extension F" },
+ { 0x2EBF0, 0x2EE5F, U"CJK Unified Ideographs Extension I" },
{ 0x2F800, 0x2FA1F, U"CJK Compatibility Ideographs Supplement" },
{ 0x30000, 0x3134F, U"CJK Unified Ideographs Extension G" },
{ 0x31350, 0x323AF, U"CJK Unified Ideographs Extension H" },
diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp
index 72d715ac2d..312195fcd7 100644
--- a/editor/import/resource_importer_layered_texture.cpp
+++ b/editor/import/resource_importer_layered_texture.cpp
@@ -276,6 +276,10 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
f->store_32(0);
f->store_32(0);
+ if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_images[0]->get_format() >= Image::FORMAT_RF) {
+ p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; // These can't go as lossy.
+ }
+
for (int i = 0; i < p_images.size(); i++) {
ResourceImporterTexture::save_to_ctex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
}
@@ -335,11 +339,6 @@ Error ResourceImporterLayeredTexture::import(const String &p_source_file, const
return err;
}
- if (compress_mode == COMPRESS_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) {
- //basis universal does not support float formats, fall back
- compress_mode = COMPRESS_VRAM_COMPRESSED;
- }
-
if (compress_mode == COMPRESS_VRAM_COMPRESSED) {
//if using video ram, optimize
if (channel_pack == 0) {
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index a205123df1..24a14c60ad 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -380,7 +380,7 @@ void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String
f->store_32(0);
f->store_32(0);
- if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_image->get_format() > Image::FORMAT_RGBA8) {
+ if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_image->get_format() >= Image::FORMAT_RF) {
p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; //these can't go as lossy
}
@@ -593,11 +593,6 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String
}
}
- if (compress_mode == COMPRESS_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) {
- // Basis universal does not support float formats, fallback.
- compress_mode = COMPRESS_VRAM_COMPRESSED;
- }
-
bool detect_3d = int(p_options["detect_3d/compress_to"]) > 0;
bool detect_roughness = roughness == 0;
bool detect_normal = normal == 0;
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index 3a3598c0b9..d13a022d52 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -800,7 +800,7 @@ InspectorDock::InspectorDock(EditorData &p_editor_data) {
inspector->set_use_folding(!bool(EDITOR_GET("interface/inspector/disable_folding")));
inspector->register_text_enter(search);
- inspector->set_use_filter(true); // TODO: check me
+ inspector->set_use_filter(true);
inspector->connect("resource_selected", callable_mp(this, &InspectorDock::_resource_selected));
diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp
index 45786c0ab5..ef6a9c9a88 100644
--- a/editor/multi_node_edit.cpp
+++ b/editor/multi_node_edit.cpp
@@ -186,25 +186,9 @@ bool MultiNodeEdit::_property_can_revert(const StringName &p_name) const {
}
if (ClassDB::has_property(get_edited_class_name(), p_name)) {
- StringName class_name;
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
- if (!node) {
- continue;
- }
-
- class_name = node->get_class_name();
- }
-
- Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name);
- for (const NodePath &E : nodes) {
- Node *node = es->get_node_or_null(E);
- if (!node) {
- continue;
- }
-
- if (node->get(p_name) != default_value) {
- // A node that doesn't have the default value has been found, so show the revert button.
+ if (node) {
return true;
}
}
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index a28fe01666..9e282cb3fa 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -36,6 +36,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_inspector.h"
#include "editor/editor_node.h"
+#include "editor/editor_properties.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
@@ -45,6 +46,7 @@
#include "scene/animation/animation_player.h"
#include "scene/gui/check_box.h"
#include "scene/gui/menu_button.h"
+#include "scene/gui/option_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/progress_bar.h"
#include "scene/gui/separator.h"
@@ -1262,4 +1264,168 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
open_file->set_title(TTR("Open Animation Node"));
open_file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
open_file->connect("file_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_file_opened));
+
+ animation_node_inspector_plugin = Ref<EditorInspectorPluginAnimationNodeAnimation>(memnew(EditorInspectorPluginAnimationNodeAnimation));
+ EditorInspector::add_inspector_plugin(animation_node_inspector_plugin);
+}
+
+AnimationNodeBlendTreeEditor::~AnimationNodeBlendTreeEditor() {
+}
+
+// EditorPluginAnimationNodeAnimation
+
+void AnimationNodeAnimationEditor::_open_set_custom_timeline_from_marker_dialog() {
+ AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
+ StringName anim_name = animation_node_animation->get_animation();
+ PackedStringArray markers = tree->has_animation(anim_name) ? tree->get_animation(anim_name)->get_marker_names() : PackedStringArray();
+
+ dialog->select_start->clear();
+ dialog->select_start->add_icon_item(get_editor_theme_icon(SNAME("PlayStart")), TTR("Start of Animation"));
+ dialog->select_start->add_separator();
+ dialog->select_end->clear();
+ dialog->select_end->add_icon_item(get_editor_theme_icon(SNAME("PlayStartBackwards")), TTR("End of Animation"));
+ dialog->select_end->add_separator();
+
+ for (const String &marker : markers) {
+ dialog->select_start->add_item(marker);
+ dialog->select_end->add_item(marker);
+ }
+
+ // Because the default selections are always valid, and marker times won't change during the dialog, we can ensure that the user can only select valid markers.
+ // This invariant is maintained by _validate_markers.
+ dialog->select_start->select(0);
+ dialog->select_end->select(0);
+
+ dialog->popup_centered(Size2(200, 0) * EDSCALE);
+}
+
+void AnimationNodeAnimationEditor::_validate_markers(int p_id) {
+ // Note: p_id is ignored. It is included because OptionButton's item_changed signal always passes it.
+ int start_id = dialog->select_start->get_selected_id();
+ int end_id = dialog->select_end->get_selected_id();
+
+ StringName anim_name = animation_node_animation->get_animation();
+ Ref<Animation> animation = AnimationTreeEditor::get_singleton()->get_animation_tree()->get_animation(anim_name);
+ ERR_FAIL_COND(animation.is_null());
+
+ double start_time = start_id < 2 ? 0 : animation->get_marker_time(dialog->select_start->get_item_text(start_id));
+ double end_time = end_id < 2 ? animation->get_length() : animation->get_marker_time(dialog->select_end->get_item_text(end_id));
+
+ // p_start and p_end have the same item count.
+ for (int i = 2; i < dialog->select_start->get_item_count(); i++) {
+ String start_marker = dialog->select_start->get_item_text(i);
+ String end_marker = dialog->select_end->get_item_text(i);
+ dialog->select_start->set_item_disabled(i, end_id >= 2 && (i == end_id || animation->get_marker_time(start_marker) > end_time));
+ dialog->select_end->set_item_disabled(i, start_id >= 2 && (i == start_id || start_time > animation->get_marker_time(end_marker)));
+ }
+}
+
+void AnimationNodeAnimationEditor::_confirm_set_custom_timeline_from_marker_dialog() {
+ int start_id = dialog->select_start->get_selected_id();
+ int end_id = dialog->select_end->get_selected_id();
+
+ Ref<Animation> animation = AnimationTreeEditor::get_singleton()->get_animation_tree()->get_animation(animation_node_animation->get_animation());
+ ERR_FAIL_COND(animation.is_null());
+ double start_time = start_id < 2 ? 0 : animation->get_marker_time(dialog->select_start->get_item_text(start_id));
+ double end_time = end_id < 2 ? animation->get_length() : animation->get_marker_time(dialog->select_end->get_item_text(end_id));
+ double length = end_time - start_time;
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Set Custom Timeline from Marker"));
+ undo_redo->add_do_method(*animation_node_animation, "set_start_offset", start_time);
+ undo_redo->add_undo_method(*animation_node_animation, "set_start_offset", animation_node_animation->get_start_offset());
+ undo_redo->add_do_method(*animation_node_animation, "set_stretch_time_scale", false);
+ undo_redo->add_undo_method(*animation_node_animation, "set_stretch_time_scale", animation_node_animation->is_stretching_time_scale());
+ undo_redo->add_do_method(*animation_node_animation, "set_timeline_length", length);
+ undo_redo->add_undo_method(*animation_node_animation, "set_timeline_length", animation_node_animation->get_timeline_length());
+ undo_redo->add_do_method(*animation_node_animation, "notify_property_list_changed");
+ undo_redo->add_undo_method(*animation_node_animation, "notify_property_list_changed");
+ undo_redo->commit_action();
+}
+
+AnimationNodeAnimationEditor::AnimationNodeAnimationEditor(Ref<AnimationNodeAnimation> p_animation_node_animation) {
+ animation_node_animation = p_animation_node_animation;
+
+ dialog = memnew(AnimationNodeAnimationEditorDialog);
+ add_child(dialog);
+ dialog->set_hide_on_ok(false);
+ dialog->select_start->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeAnimationEditor::_validate_markers));
+ dialog->select_end->connect(SceneStringName(item_selected), callable_mp(this, &AnimationNodeAnimationEditor::_validate_markers));
+ dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationNodeAnimationEditor::_confirm_set_custom_timeline_from_marker_dialog));
+
+ Control *top_spacer = memnew(Control);
+ add_child(top_spacer);
+ top_spacer->set_custom_minimum_size(Size2(0, 2) * EDSCALE);
+
+ button = memnew(Button);
+ add_child(button);
+ button->set_text(TTR("Set Custom Timeline from Marker"));
+ button->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ button->connect(SceneStringName(pressed), callable_mp(this, &AnimationNodeAnimationEditor::_open_set_custom_timeline_from_marker_dialog));
+
+ Control *bottom_spacer = memnew(Control);
+ add_child(bottom_spacer);
+ bottom_spacer->set_custom_minimum_size(Size2(0, 2) * EDSCALE);
+}
+
+AnimationNodeAnimationEditor::~AnimationNodeAnimationEditor() {
+}
+
+void AnimationNodeAnimationEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ button->set_theme_type_variation(SNAME("InspectorActionButton"));
+ button->set_icon(get_editor_theme_icon(SNAME("Edit")));
+ } break;
+ }
+}
+
+bool EditorInspectorPluginAnimationNodeAnimation::can_handle(Object *p_object) {
+ Ref<AnimationNodeAnimation> ana(Object::cast_to<AnimationNodeAnimation>(p_object));
+ return ana.is_valid() && ana->is_using_custom_timeline();
+}
+
+bool EditorInspectorPluginAnimationNodeAnimation::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
+ Ref<AnimationNodeAnimation> ana(Object::cast_to<AnimationNodeAnimation>(p_object));
+ ERR_FAIL_COND_V(ana.is_null(), false);
+
+ if (p_path == "timeline_length") {
+ add_custom_control(memnew(AnimationNodeAnimationEditor(ana)));
+ }
+
+ return false;
+}
+
+AnimationNodeAnimationEditorDialog::AnimationNodeAnimationEditorDialog() {
+ set_title(TTR("Select Markers..."));
+ VBoxContainer *vbox = memnew(VBoxContainer);
+ add_child(vbox);
+ vbox->set_offsets_preset(Control::PRESET_FULL_RECT);
+
+ HBoxContainer *container_start = memnew(HBoxContainer);
+ vbox->add_child(container_start);
+ Label *label_start = memnew(Label);
+ container_start->add_child(label_start);
+ label_start->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ label_start->set_stretch_ratio(1);
+ label_start->set_text(TTR("Start Marker"));
+ select_start = memnew(OptionButton);
+ container_start->add_child(select_start);
+ select_start->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ select_start->set_stretch_ratio(2);
+
+ HBoxContainer *container_end = memnew(HBoxContainer);
+ vbox->add_child(container_end);
+ Label *label_end = memnew(Label);
+ container_end->add_child(label_end);
+ label_end->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ label_end->set_stretch_ratio(1);
+ label_end->set_text(TTR("End Marker"));
+ select_end = memnew(OptionButton);
+ container_end->add_child(select_end);
+ select_end->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ select_end->set_stretch_ratio(2);
+}
+
+AnimationNodeAnimationEditorDialog::~AnimationNodeAnimationEditorDialog() {
}
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h
index ee6f087e07..9e7793977b 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.h
+++ b/editor/plugins/animation_blend_tree_editor_plugin.h
@@ -32,9 +32,11 @@
#define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
#include "core/object/script_language.h"
+#include "editor/editor_inspector.h"
#include "editor/plugins/animation_tree_editor_plugin.h"
#include "scene/animation/animation_blend_tree.h"
#include "scene/gui/button.h"
+#include "scene/gui/dialogs.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/popup.h"
@@ -47,6 +49,7 @@ class EditorFileDialog;
class EditorProperty;
class MenuButton;
class PanelContainer;
+class EditorInspectorPluginAnimationNodeAnimation;
class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin);
@@ -147,6 +150,8 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
MENU_LOAD_FILE_CONFIRM = 1002
};
+ Ref<EditorInspectorPluginAnimationNodeAnimation> animation_node_inspector_plugin;
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -165,6 +170,48 @@ public:
void update_graph();
AnimationNodeBlendTreeEditor();
+ ~AnimationNodeBlendTreeEditor();
+};
+
+// EditorPluginAnimationNodeAnimation
+
+class EditorInspectorPluginAnimationNodeAnimation : public EditorInspectorPlugin {
+ GDCLASS(EditorInspectorPluginAnimationNodeAnimation, EditorInspectorPlugin);
+
+public:
+ virtual bool can_handle(Object *p_object) override;
+ virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) override;
+};
+
+class AnimationNodeAnimationEditorDialog : public ConfirmationDialog {
+ GDCLASS(AnimationNodeAnimationEditorDialog, ConfirmationDialog);
+
+ friend class AnimationNodeAnimationEditor;
+
+ OptionButton *select_start = nullptr;
+ OptionButton *select_end = nullptr;
+
+public:
+ AnimationNodeAnimationEditorDialog();
+ ~AnimationNodeAnimationEditorDialog();
+};
+
+class AnimationNodeAnimationEditor : public VBoxContainer {
+ GDCLASS(AnimationNodeAnimationEditor, VBoxContainer);
+
+ Ref<AnimationNodeAnimation> animation_node_animation;
+ Button *button = nullptr;
+ AnimationNodeAnimationEditorDialog *dialog = nullptr;
+ void _open_set_custom_timeline_from_marker_dialog();
+ void _validate_markers(int p_id);
+ void _confirm_set_custom_timeline_from_marker_dialog();
+
+public:
+ AnimationNodeAnimationEditor(Ref<AnimationNodeAnimation> p_animation_node_animation);
+ ~AnimationNodeAnimationEditor();
+
+protected:
+ void _notification(int p_what);
};
#endif // ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp
index 38f8b16b34..147b92c094 100644
--- a/editor/plugins/animation_library_editor.cpp
+++ b/editor/plugins/animation_library_editor.cpp
@@ -598,7 +598,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
file_popup->add_separator();
file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY);
Rect2 pos = tree->get_item_rect(p_item, 1, 0);
- Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height));
+ Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)) - tree->get_scroll();
file_popup->popup(Rect2(popup_pos, Size2()));
file_dialog_animation = StringName();
@@ -638,7 +638,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
file_popup->add_separator();
file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION);
Rect2 pos = tree->get_item_rect(p_item, 1, 0);
- Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height));
+ Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)) - tree->get_scroll();
file_popup->popup(Rect2(popup_pos, Size2()));
file_dialog_animation = anim_name;
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 5cb558abbe..e6afc85e9e 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -41,6 +41,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/gui/editor_validation_panel.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning.
#include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning.
@@ -295,7 +296,14 @@ void AnimationPlayerEditor::_play_pressed() {
player->stop(); //so it won't blend with itself
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
- player->play(current);
+ PackedStringArray markers = track_editor->get_selected_section();
+ if (markers.size() == 2) {
+ StringName start_marker = markers[0];
+ StringName end_marker = markers[1];
+ player->play_section_with_markers(current, start_marker, end_marker);
+ } else {
+ player->play(current);
+ }
}
//unstop
@@ -312,7 +320,14 @@ void AnimationPlayerEditor::_play_from_pressed() {
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
player->seek_internal(time, true, true, true);
- player->play(current);
+ PackedStringArray markers = track_editor->get_selected_section();
+ if (markers.size() == 2) {
+ StringName start_marker = markers[0];
+ StringName end_marker = markers[1];
+ player->play_section_with_markers(current, start_marker, end_marker);
+ } else {
+ player->play(current);
+ }
}
//unstop
@@ -333,7 +348,14 @@ void AnimationPlayerEditor::_play_bw_pressed() {
player->stop(); //so it won't blend with itself
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
- player->play_backwards(current);
+ PackedStringArray markers = track_editor->get_selected_section();
+ if (markers.size() == 2) {
+ StringName start_marker = markers[0];
+ StringName end_marker = markers[1];
+ player->play_section_with_markers_backwards(current, start_marker, end_marker);
+ } else {
+ player->play_backwards(current);
+ }
}
//unstop
@@ -350,7 +372,14 @@ void AnimationPlayerEditor::_play_bw_from_pressed() {
}
ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing.");
player->seek_internal(time, true, true, true);
- player->play_backwards(current);
+ PackedStringArray markers = track_editor->get_selected_section();
+ if (markers.size() == 2) {
+ StringName start_marker = markers[0];
+ StringName end_marker = markers[1];
+ player->play_section_with_markers_backwards(current, start_marker, end_marker);
+ } else {
+ player->play_backwards(current);
+ }
}
//unstop
@@ -2397,3 +2426,24 @@ AnimationTrackKeyEditEditorPlugin::AnimationTrackKeyEditEditorPlugin() {
bool AnimationTrackKeyEditEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("AnimationTrackKeyEdit");
}
+
+bool EditorInspectorPluginAnimationMarkerKeyEdit::can_handle(Object *p_object) {
+ return Object::cast_to<AnimationMarkerKeyEdit>(p_object) != nullptr;
+}
+
+void EditorInspectorPluginAnimationMarkerKeyEdit::parse_begin(Object *p_object) {
+ AnimationMarkerKeyEdit *amk = Object::cast_to<AnimationMarkerKeyEdit>(p_object);
+ ERR_FAIL_NULL(amk);
+
+ amk_editor = memnew(AnimationMarkerKeyEditEditor(amk->animation, amk->marker_name, amk->use_fps));
+ add_custom_control(amk_editor);
+}
+
+AnimationMarkerKeyEditEditorPlugin::AnimationMarkerKeyEditEditorPlugin() {
+ amk_plugin = memnew(EditorInspectorPluginAnimationMarkerKeyEdit);
+ EditorInspector::add_inspector_plugin(amk_plugin);
+}
+
+bool AnimationMarkerKeyEditEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("AnimationMarkerKeyEdit");
+}
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index e4ca6c17c3..349ed7b5cd 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -338,4 +338,30 @@ public:
AnimationTrackKeyEditEditorPlugin();
};
+// AnimationMarkerKeyEditEditorPlugin
+
+class EditorInspectorPluginAnimationMarkerKeyEdit : public EditorInspectorPlugin {
+ GDCLASS(EditorInspectorPluginAnimationMarkerKeyEdit, EditorInspectorPlugin);
+
+ AnimationMarkerKeyEditEditor *amk_editor = nullptr;
+
+public:
+ virtual bool can_handle(Object *p_object) override;
+ virtual void parse_begin(Object *p_object) override;
+};
+
+class AnimationMarkerKeyEditEditorPlugin : public EditorPlugin {
+ GDCLASS(AnimationMarkerKeyEditEditorPlugin, EditorPlugin);
+
+ EditorInspectorPluginAnimationMarkerKeyEdit *amk_plugin = nullptr;
+
+public:
+ bool has_main_screen() const override { return false; }
+ virtual bool handles(Object *p_object) const override;
+
+ virtual String get_name() const override { return "AnimationMarkerKeyEdit"; }
+
+ AnimationMarkerKeyEditEditorPlugin();
+};
+
#endif // ANIMATION_PLAYER_EDITOR_PLUGIN_H
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index af4b3a1643..fec8d4b897 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -993,7 +993,8 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, String p
String url_host;
int url_port;
String url_path;
- Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path);
+ String url_fragment;
+ Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path, url_fragment);
if (err != OK) {
if (is_print_verbose_enabled()) {
ERR_PRINT(vformat("Asset Library: Invalid image URL '%s' for asset # %d.", trimmed_url, p_asset_id));
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index 5c5f236ff3..cd13deb3e9 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -31,7 +31,6 @@
#include "control_editor_plugin.h"
#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
@@ -511,6 +510,9 @@ void ControlEditorPopupButton::_notification(int p_what) {
case NOTIFICATION_DRAW: {
if (arrow_icon.is_valid()) {
Vector2 arrow_pos = Point2(26, 0) * EDSCALE;
+ if (is_layout_rtl()) {
+ arrow_pos.x = get_size().x - arrow_pos.x - arrow_icon->get_width();
+ }
arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2;
draw_texture(arrow_icon, arrow_pos);
}
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
deleted file mode 100644
index 4869a202d7..0000000000
--- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp
+++ /dev/null
@@ -1,309 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_2d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "cpu_particles_2d_editor_plugin.h"
-
-#include "canvas_item_editor_plugin.h"
-#include "core/io/image_loader.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "editor/editor_undo_redo_manager.h"
-#include "editor/gui/editor_file_dialog.h"
-#include "editor/scene_tree_dock.h"
-#include "scene/2d/cpu_particles_2d.h"
-#include "scene/2d/gpu_particles_2d.h"
-#include "scene/gui/check_box.h"
-#include "scene/gui/menu_button.h"
-#include "scene/gui/option_button.h"
-#include "scene/gui/separator.h"
-#include "scene/gui/spin_box.h"
-#include "scene/resources/particle_process_material.h"
-
-void CPUParticles2DEditorPlugin::edit(Object *p_object) {
- particles = Object::cast_to<CPUParticles2D>(p_object);
-}
-
-bool CPUParticles2DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("CPUParticles2D");
-}
-
-void CPUParticles2DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- toolbar->show();
- } else {
- toolbar->hide();
- }
-}
-
-void CPUParticles2DEditorPlugin::_file_selected(const String &p_file) {
- source_emission_file = p_file;
- emission_mask->popup_centered();
-}
-
-void CPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
- switch (p_idx) {
- case MENU_LOAD_EMISSION_MASK: {
- file->popup_file_dialog();
- } break;
- case MENU_CLEAR_EMISSION_MASK: {
- emission_mask->popup_centered();
- } break;
- case MENU_RESTART: {
- particles->restart();
- } break;
- case MENU_CONVERT_TO_GPU_PARTICLES: {
- GPUParticles2D *gpu_particles = memnew(GPUParticles2D);
- gpu_particles->convert_from_particles(particles);
- gpu_particles->set_name(particles->get_name());
- gpu_particles->set_transform(particles->get_transform());
- gpu_particles->set_visible(particles->is_visible());
- gpu_particles->set_process_mode(particles->get_process_mode());
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Convert to GPUParticles3D"), UndoRedo::MERGE_DISABLE, particles);
- SceneTreeDock::get_singleton()->replace_node(particles, gpu_particles);
- ur->commit_action(false);
- } break;
- }
-}
-
-void CPUParticles2DEditorPlugin::_generate_emission_mask() {
- Ref<Image> img;
- img.instantiate();
- Error err = ImageLoader::load_image(source_emission_file, img);
- ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
-
- if (img->is_compressed()) {
- img->decompress();
- }
- img->convert(Image::FORMAT_RGBA8);
- ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
- Size2i s = img->get_size();
- ERR_FAIL_COND(s.width == 0 || s.height == 0);
-
- Vector<Point2> valid_positions;
- Vector<Point2> valid_normals;
- Vector<uint8_t> valid_colors;
-
- valid_positions.resize(s.width * s.height);
-
- EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- valid_normals.resize(s.width * s.height);
- }
-
- bool capture_colors = emission_colors->is_pressed();
-
- if (capture_colors) {
- valid_colors.resize(s.width * s.height * 4);
- }
-
- int vpc = 0;
-
- {
- Vector<uint8_t> img_data = img->get_data();
- const uint8_t *r = img_data.ptr();
-
- for (int i = 0; i < s.width; i++) {
- for (int j = 0; j < s.height; j++) {
- uint8_t a = r[(j * s.width + i) * 4 + 3];
-
- if (a > 128) {
- if (emode == EMISSION_MODE_SOLID) {
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
- valid_positions.write[vpc++] = Point2(i, j);
-
- } else {
- bool on_border = false;
- for (int x = i - 1; x <= i + 1; x++) {
- for (int y = j - 1; y <= j + 1; y++) {
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- on_border = true;
- break;
- }
- }
-
- if (on_border) {
- break;
- }
- }
-
- if (on_border) {
- valid_positions.write[vpc] = Point2(i, j);
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- Vector2 normal;
- for (int x = i - 2; x <= i + 2; x++) {
- for (int y = j - 2; y <= j + 2; y++) {
- if (x == i && y == j) {
- continue;
- }
-
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- normal += Vector2(x - i, y - j).normalized();
- }
- }
- }
-
- normal.normalize();
- valid_normals.write[vpc] = normal;
- }
-
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
-
- vpc++;
- }
- }
- }
- }
- }
- }
-
- valid_positions.resize(vpc);
- if (valid_normals.size()) {
- valid_normals.resize(vpc);
- }
-
- ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
-
- if (capture_colors) {
- PackedColorArray pca;
- pca.resize(vpc);
- Color *pcaw = pca.ptrw();
- for (int i = 0; i < vpc; i += 1) {
- Color color;
- color.r = valid_colors[i * 4 + 0] / 255.0f;
- color.g = valid_colors[i * 4 + 1] / 255.0f;
- color.b = valid_colors[i * 4 + 2] / 255.0f;
- color.a = valid_colors[i * 4 + 3] / 255.0f;
- pcaw[i] = color;
- }
- particles->set_emission_colors(pca);
- }
-
- if (valid_normals.size()) {
- particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
- PackedVector2Array norms;
- norms.resize(valid_normals.size());
- Vector2 *normsw = norms.ptrw();
- for (int i = 0; i < valid_normals.size(); i += 1) {
- normsw[i] = valid_normals[i];
- }
- particles->set_emission_normals(norms);
- } else {
- particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS);
- }
-
- {
- Vector2 offset;
- if (emission_mask_centered->is_pressed()) {
- offset = Vector2(-s.width * 0.5, -s.height * 0.5);
- }
-
- PackedVector2Array points;
- points.resize(valid_positions.size());
- Vector2 *pointsw = points.ptrw();
- for (int i = 0; i < valid_positions.size(); i += 1) {
- pointsw[i] = valid_positions[i] + offset;
- }
- particles->set_emission_points(points);
- }
-}
-
-void CPUParticles2DEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback));
- menu->set_icon(file->get_editor_theme_icon(SNAME("CPUParticles2D")));
- file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected));
- } break;
- }
-}
-
-CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
- particles = nullptr;
-
- toolbar = memnew(HBoxContainer);
- add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
- toolbar->hide();
-
- menu = memnew(MenuButton);
- menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART);
- menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
- menu->get_popup()->add_item(TTR("Convert to GPUParticles2D"), MENU_CONVERT_TO_GPU_PARTICLES);
- menu->set_text(TTR("CPUParticles2D"));
- menu->set_switch_on_hover(true);
- toolbar->add_child(menu);
-
- file = memnew(EditorFileDialog);
- List<String> ext;
- ImageLoader::get_recognized_extensions(&ext);
- for (const String &E : ext) {
- file->add_filter("*." + E, E.to_upper());
- }
- file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
- toolbar->add_child(file);
-
- emission_mask = memnew(ConfirmationDialog);
- emission_mask->set_title(TTR("Load Emission Mask"));
- VBoxContainer *emvb = memnew(VBoxContainer);
- emission_mask->add_child(emvb);
- emission_mask_mode = memnew(OptionButton);
- emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
- emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
- emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
- emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
- VBoxContainer *optionsvb = memnew(VBoxContainer);
- emvb->add_margin_child(TTR("Options"), optionsvb);
- emission_mask_centered = memnew(CheckBox);
- emission_mask_centered->set_text(TTR("Centered"));
- optionsvb->add_child(emission_mask_centered);
- emission_colors = memnew(CheckBox);
- emission_colors->set_text(TTR("Capture Colors from Pixel"));
- optionsvb->add_child(emission_colors);
-
- toolbar->add_child(emission_mask);
-
- emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles2DEditorPlugin::_generate_emission_mask));
-}
-
-CPUParticles2DEditorPlugin::~CPUParticles2DEditorPlugin() {
-}
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.h b/editor/plugins/cpu_particles_2d_editor_plugin.h
deleted file mode 100644
index 645e6d8345..0000000000
--- a/editor/plugins/cpu_particles_2d_editor_plugin.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_2d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef CPU_PARTICLES_2D_EDITOR_PLUGIN_H
-#define CPU_PARTICLES_2D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/editor_plugin.h"
-#include "scene/2d/cpu_particles_2d.h"
-#include "scene/2d/physics/collision_polygon_2d.h"
-#include "scene/gui/box_container.h"
-
-class CheckBox;
-class ConfirmationDialog;
-class SpinBox;
-class EditorFileDialog;
-class MenuButton;
-class OptionButton;
-
-class CPUParticles2DEditorPlugin : public EditorPlugin {
- GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin);
-
- enum {
- MENU_LOAD_EMISSION_MASK,
- MENU_CLEAR_EMISSION_MASK,
- MENU_RESTART,
- MENU_CONVERT_TO_GPU_PARTICLES,
- };
-
- enum EmissionMode {
- EMISSION_MODE_SOLID,
- EMISSION_MODE_BORDER,
- EMISSION_MODE_BORDER_DIRECTED
- };
-
- CPUParticles2D *particles = nullptr;
-
- EditorFileDialog *file = nullptr;
-
- HBoxContainer *toolbar = nullptr;
- MenuButton *menu = nullptr;
-
- ConfirmationDialog *emission_mask = nullptr;
- OptionButton *emission_mask_mode = nullptr;
- CheckBox *emission_mask_centered = nullptr;
- CheckBox *emission_colors = nullptr;
-
- String source_emission_file;
-
- void _file_selected(const String &p_file);
- void _menu_callback(int p_idx);
- void _generate_emission_mask();
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual String get_name() const override { return "CPUParticles2D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- CPUParticles2DEditorPlugin();
- ~CPUParticles2DEditorPlugin();
-};
-
-#endif // CPU_PARTICLES_2D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp
deleted file mode 100644
index e0ce330813..0000000000
--- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/**************************************************************************/
-/* cpu_particles_3d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "cpu_particles_3d_editor_plugin.h"
-
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "editor/editor_undo_redo_manager.h"
-#include "editor/gui/scene_tree_editor.h"
-#include "editor/plugins/node_3d_editor_plugin.h"
-#include "editor/scene_tree_dock.h"
-#include "scene/gui/menu_button.h"
-
-void CPUParticles3DEditor::_node_removed(Node *p_node) {
- if (p_node == node) {
- node = nullptr;
- hide();
- }
-}
-
-void CPUParticles3DEditor::_notification(int p_notification) {
- switch (p_notification) {
- case NOTIFICATION_ENTER_TREE: {
- options->set_icon(get_editor_theme_icon(SNAME("CPUParticles3D")));
- } break;
- }
-}
-
-void CPUParticles3DEditor::_menu_option(int p_option) {
- switch (p_option) {
- case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
- emission_tree_dialog->popup_scenetree_dialog();
- } break;
-
- case MENU_OPTION_RESTART: {
- node->restart();
- } break;
-
- case MENU_OPTION_CONVERT_TO_GPU_PARTICLES: {
- GPUParticles3D *gpu_particles = memnew(GPUParticles3D);
- gpu_particles->convert_from_particles(node);
- gpu_particles->set_name(node->get_name());
- gpu_particles->set_transform(node->get_transform());
- gpu_particles->set_visible(node->is_visible());
- gpu_particles->set_process_mode(node->get_process_mode());
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Convert to GPUParticles3D"), UndoRedo::MERGE_DISABLE, node);
- SceneTreeDock::get_singleton()->replace_node(node, gpu_particles);
- ur->commit_action(false);
-
- } break;
- case MENU_OPTION_GENERATE_AABB: {
- // Add one second to the default generation lifetime, since the progress is updated every second.
- generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0));
-
- if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
- // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
- generate_aabb->popup_centered();
- } else {
- // Generate the visibility AABB immediately.
- _generate_aabb();
- }
- } break;
- }
-}
-
-void CPUParticles3DEditor::_generate_aabb() {
- double time = generate_seconds->get_value();
-
- double running = 0.0;
-
- EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
-
- bool was_emitting = node->is_emitting();
- if (!was_emitting) {
- node->set_emitting(true);
- OS::get_singleton()->delay_usec(1000);
- }
-
- AABB rect;
-
- while (running < time) {
- uint64_t ticks = OS::get_singleton()->get_ticks_usec();
- ep.step(TTR("Generating..."), int(running), true);
- OS::get_singleton()->delay_usec(1000);
-
- AABB capture = node->capture_aabb();
- if (rect == AABB()) {
- rect = capture;
- } else {
- rect.merge_with(capture);
- }
-
- running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
- }
-
- if (!was_emitting) {
- node->set_emitting(false);
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Generate Visibility AABB"));
- ur->add_do_method(node, "set_visibility_aabb", rect);
- ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb());
- ur->commit_action();
-}
-
-void CPUParticles3DEditor::edit(CPUParticles3D *p_particles) {
- base_node = p_particles;
- node = p_particles;
-}
-
-void CPUParticles3DEditor::_generate_emission_points() {
- /// hacer codigo aca
- Vector<Vector3> points;
- Vector<Vector3> normals;
-
- if (!_generate(points, normals)) {
- return;
- }
-
- if (normals.size() == 0) {
- node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS);
- node->set_emission_points(points);
- } else {
- node->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS);
- node->set_emission_points(points);
- node->set_emission_normals(normals);
- }
-}
-
-CPUParticles3DEditor::CPUParticles3DEditor() {
- particles_editor_hb = memnew(HBoxContainer);
- Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb);
- options = memnew(MenuButton);
- options->set_switch_on_hover(true);
- particles_editor_hb->add_child(options);
- particles_editor_hb->hide();
-
- options->set_text(TTR("CPUParticles3D"));
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART);
- options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
- options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
- options->get_popup()->add_item(TTR("Convert to GPUParticles3D"), MENU_OPTION_CONVERT_TO_GPU_PARTICLES);
- options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CPUParticles3DEditor::_menu_option));
-
- generate_aabb = memnew(ConfirmationDialog);
- generate_aabb->set_title(TTR("Generate Visibility AABB"));
- VBoxContainer *genvb = memnew(VBoxContainer);
- generate_aabb->add_child(genvb);
- generate_seconds = memnew(SpinBox);
- genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
- generate_seconds->set_min(0.1);
- generate_seconds->set_max(25);
- generate_seconds->set_value(2);
-
- add_child(generate_aabb);
-
- generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &CPUParticles3DEditor::_generate_aabb));
-}
-
-void CPUParticles3DEditorPlugin::edit(Object *p_object) {
- particles_editor->edit(Object::cast_to<CPUParticles3D>(p_object));
-}
-
-bool CPUParticles3DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("CPUParticles3D");
-}
-
-void CPUParticles3DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- particles_editor->show();
- particles_editor->particles_editor_hb->show();
- } else {
- particles_editor->particles_editor_hb->hide();
- particles_editor->hide();
- particles_editor->edit(nullptr);
- }
-}
-
-CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
- particles_editor = memnew(CPUParticles3DEditor);
- EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
-
- particles_editor->hide();
-}
-
-CPUParticles3DEditorPlugin::~CPUParticles3DEditorPlugin() {
-}
diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp
index 0648327fab..b635816bd9 100644
--- a/editor/plugins/editor_context_menu_plugin.cpp
+++ b/editor/plugins/editor_context_menu_plugin.cpp
@@ -67,10 +67,21 @@ void EditorContextMenuPlugin::add_context_menu_item_from_shortcut(const String &
context_menu_items.insert(p_name, item);
}
+void EditorContextMenuPlugin::add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture) {
+ ERR_FAIL_NULL(p_menu);
+
+ ContextMenuItem item;
+ item.item_name = p_name;
+ item.icon = p_texture;
+ item.submenu = p_menu;
+ context_menu_items.insert(p_name, item);
+}
+
void EditorContextMenuPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("add_context_menu_item_from_shortcut", "name", "shortcut", "icon"), &EditorContextMenuPlugin::add_context_menu_item_from_shortcut, DEFVAL(Ref<Texture2D>()));
+ ClassDB::bind_method(D_METHOD("add_context_submenu_item", "name", "menu", "icon"), &EditorContextMenuPlugin::add_context_submenu_item, DEFVAL(Ref<Texture2D>()));
GDVIRTUAL_BIND(_popup_menu, "paths");
@@ -117,12 +128,17 @@ void EditorContextMenuPluginManager::add_options_from_plugins(PopupMenu *p_popup
EditorContextMenuPlugin::ContextMenuItem &item = E.value;
item.id = id;
- if (item.icon.is_valid()) {
- p_popup->add_icon_item(item.icon, item.item_name, id);
- p_popup->set_item_icon_max_width(-1, icon_size);
+ if (item.submenu) {
+ p_popup->add_submenu_node_item(item.item_name, item.submenu, id);
} else {
p_popup->add_item(item.item_name, id);
}
+
+ if (item.icon.is_valid()) {
+ p_popup->set_item_icon(-1, item.icon);
+ p_popup->set_item_icon_max_width(-1, icon_size);
+ }
+
if (item.shortcut.is_valid()) {
p_popup->set_item_shortcut(-1, item.shortcut, true);
}
diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h
index 0232d254ba..86c67dedda 100644
--- a/editor/plugins/editor_context_menu_plugin.h
+++ b/editor/plugins/editor_context_menu_plugin.h
@@ -65,6 +65,7 @@ public:
Callable callable;
Ref<Texture2D> icon;
Ref<Shortcut> shortcut;
+ PopupMenu *submenu = nullptr;
};
HashMap<String, ContextMenuItem> context_menu_items;
HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts;
@@ -80,6 +81,7 @@ public:
void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture);
void add_context_menu_item_from_shortcut(const String &p_name, const Ref<Shortcut> &p_shortcut, const Ref<Texture2D> &p_texture);
+ void add_context_submenu_item(const String &p_name, PopupMenu *p_menu, const Ref<Texture2D> &p_texture);
};
VARIANT_ENUM_CAST(EditorContextMenuPlugin::ContextMenuSlot);
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index c8426bce73..29e4adb45a 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -153,7 +153,6 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C
} break;
case CONTAINER_PROJECT_SETTING_TAB_RIGHT: {
ProjectSettingsEditor::get_singleton()->get_tabs()->add_child(p_control);
- ProjectSettingsEditor::get_singleton()->get_tabs()->move_child(p_control, 1);
} break;
}
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index a2c36b1f3c..9a53f07a3f 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -112,9 +112,13 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
- const int mid_depth = (tex_3d->get_depth() - 1) / 2;
-
Vector<Ref<Image>> data = tex_3d->get_data();
+ if (data.size() != tex_3d->get_depth()) {
+ return Ref<Texture2D>();
+ }
+
+ // Use the middle slice for the thumbnail.
+ const int mid_depth = (tex_3d->get_depth() - 1) / 2;
if (!data.is_empty() && data[mid_depth].is_valid()) {
img = data[mid_depth]->duplicate();
}
@@ -124,6 +128,7 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from,
return Ref<Texture2D>();
}
+ // Use the middle slice for the thumbnail.
const int mid_layer = (tex_lyr->get_layers() - 1) / 2;
Ref<Image> data = tex_lyr->get_layer_data(mid_layer);
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index ec9513363d..6366d20539 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -121,13 +121,8 @@ bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name)
if (name.begins_with("keys")) {
int key = name.get_slicec('/', 1).to_int();
- if (defaults_dict.has(key) && dict.has(key)) {
- int value = dict[key];
- Vector3i range = defaults_dict[key];
- return range.z != value;
- }
+ return defaults_dict.has(key) && dict.has(key);
}
-
return false;
}
@@ -142,7 +137,6 @@ bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name,
return true;
}
}
-
return false;
}
diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h
index 0de6b7b611..ad6b534235 100644
--- a/editor/plugins/gdextension_export_plugin.h
+++ b/editor/plugins/gdextension_export_plugin.h
@@ -127,7 +127,10 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
for (const String &E : p_features) {
features_vector.append(E);
}
- ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector)));
+ if (get_export_platform().is_valid()) {
+ get_export_platform()->add_message(EditorExportPlatform::EXPORT_MESSAGE_WARNING, TTR("GDExtension"), vformat(TTR("No suitable library found for GDExtension: \"%s\". Possible feature flags for your platform: %s"), p_path, String(", ").join(features_vector)));
+ }
+ return;
}
Vector<SharedObject> dependencies_shared_objects = GDExtensionLibraryLoader::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp
deleted file mode 100644
index 1b68b55ff6..0000000000
--- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp
+++ /dev/null
@@ -1,427 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_2d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "gpu_particles_2d_editor_plugin.h"
-
-#include "canvas_item_editor_plugin.h"
-#include "core/io/image_loader.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "editor/editor_undo_redo_manager.h"
-#include "editor/gui/editor_file_dialog.h"
-#include "editor/scene_tree_dock.h"
-#include "scene/2d/cpu_particles_2d.h"
-#include "scene/gui/menu_button.h"
-#include "scene/gui/separator.h"
-#include "scene/resources/image_texture.h"
-#include "scene/resources/particle_process_material.h"
-
-void GPUParticles2DEditorPlugin::edit(Object *p_object) {
- particles = Object::cast_to<GPUParticles2D>(p_object);
-}
-
-bool GPUParticles2DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("GPUParticles2D");
-}
-
-void GPUParticles2DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- toolbar->show();
- } else {
- toolbar->hide();
- }
-}
-
-void GPUParticles2DEditorPlugin::_file_selected(const String &p_file) {
- source_emission_file = p_file;
- emission_mask->popup_centered();
-}
-
-void GPUParticles2DEditorPlugin::_selection_changed() {
- List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list();
-
- if (selected_particles.is_empty() && selected_nodes.is_empty()) {
- return;
- }
-
- for (GPUParticles2D *SP : selected_particles) {
- SP->set_show_visibility_rect(false);
- }
- selected_particles.clear();
-
- for (Node *P : selected_nodes) {
- GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(P);
- if (selected_particle != nullptr) {
- selected_particle->set_show_visibility_rect(true);
- selected_particles.push_back(selected_particle);
- }
- }
-}
-
-void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
- switch (p_idx) {
- case MENU_GENERATE_VISIBILITY_RECT: {
- // Add one second to the default generation lifetime, since the progress is updated every second.
- generate_seconds->set_value(MAX(1.0, trunc(particles->get_lifetime()) + 1.0));
-
- if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
- // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
- generate_visibility_rect->popup_centered();
- } else {
- // Generate the visibility rect immediately.
- _generate_visibility_rect();
- }
- } break;
- case MENU_LOAD_EMISSION_MASK: {
- file->popup_file_dialog();
-
- } break;
- case MENU_CLEAR_EMISSION_MASK: {
- emission_mask->popup_centered();
- } break;
- case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: {
- CPUParticles2D *cpu_particles = memnew(CPUParticles2D);
- cpu_particles->convert_from_particles(particles);
- cpu_particles->set_name(particles->get_name());
- cpu_particles->set_transform(particles->get_transform());
- cpu_particles->set_visible(particles->is_visible());
- cpu_particles->set_process_mode(particles->get_process_mode());
- cpu_particles->set_z_index(particles->get_z_index());
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Convert to CPUParticles2D"), UndoRedo::MERGE_DISABLE, particles);
- SceneTreeDock::get_singleton()->replace_node(particles, cpu_particles);
- ur->commit_action(false);
-
- } break;
- case MENU_RESTART: {
- particles->restart();
- }
- }
-}
-
-void GPUParticles2DEditorPlugin::_generate_visibility_rect() {
- double time = generate_seconds->get_value();
-
- float running = 0.0;
-
- EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time));
-
- bool was_emitting = particles->is_emitting();
- if (!was_emitting) {
- particles->set_emitting(true);
- OS::get_singleton()->delay_usec(1000);
- }
-
- Rect2 rect;
- while (running < time) {
- uint64_t ticks = OS::get_singleton()->get_ticks_usec();
- ep.step(TTR("Generating..."), int(running), true);
- OS::get_singleton()->delay_usec(1000);
-
- Rect2 capture = particles->capture_rect();
- if (rect == Rect2()) {
- rect = capture;
- } else {
- rect = rect.merge(capture);
- }
-
- running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
- }
-
- if (!was_emitting) {
- particles->set_emitting(false);
- }
-
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(TTR("Generate Visibility Rect"));
- undo_redo->add_do_method(particles, "set_visibility_rect", rect);
- undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect());
- undo_redo->commit_action();
-}
-
-void GPUParticles2DEditorPlugin::_generate_emission_mask() {
- Ref<ParticleProcessMaterial> pm = particles->get_process_material();
- if (!pm.is_valid()) {
- EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material"));
- return;
- }
-
- Ref<Image> img;
- img.instantiate();
- Error err = ImageLoader::load_image(source_emission_file, img);
- ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
-
- if (img->is_compressed()) {
- img->decompress();
- }
- img->convert(Image::FORMAT_RGBA8);
- ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
- Size2i s = img->get_size();
- ERR_FAIL_COND(s.width == 0 || s.height == 0);
-
- Vector<Point2> valid_positions;
- Vector<Point2> valid_normals;
- Vector<uint8_t> valid_colors;
-
- valid_positions.resize(s.width * s.height);
-
- EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- valid_normals.resize(s.width * s.height);
- }
-
- bool capture_colors = emission_colors->is_pressed();
-
- if (capture_colors) {
- valid_colors.resize(s.width * s.height * 4);
- }
-
- int vpc = 0;
-
- {
- Vector<uint8_t> img_data = img->get_data();
- const uint8_t *r = img_data.ptr();
-
- for (int i = 0; i < s.width; i++) {
- for (int j = 0; j < s.height; j++) {
- uint8_t a = r[(j * s.width + i) * 4 + 3];
-
- if (a > 128) {
- if (emode == EMISSION_MODE_SOLID) {
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
- valid_positions.write[vpc++] = Point2(i, j);
-
- } else {
- bool on_border = false;
- for (int x = i - 1; x <= i + 1; x++) {
- for (int y = j - 1; y <= j + 1; y++) {
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- on_border = true;
- break;
- }
- }
-
- if (on_border) {
- break;
- }
- }
-
- if (on_border) {
- valid_positions.write[vpc] = Point2(i, j);
-
- if (emode == EMISSION_MODE_BORDER_DIRECTED) {
- Vector2 normal;
- for (int x = i - 2; x <= i + 2; x++) {
- for (int y = j - 2; y <= j + 2; y++) {
- if (x == i && y == j) {
- continue;
- }
-
- if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
- normal += Vector2(x - i, y - j).normalized();
- }
- }
- }
-
- normal.normalize();
- valid_normals.write[vpc] = normal;
- }
-
- if (capture_colors) {
- valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
- valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
- valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
- valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
- }
-
- vpc++;
- }
- }
- }
- }
- }
- }
-
- valid_positions.resize(vpc);
- if (valid_normals.size()) {
- valid_normals.resize(vpc);
- }
-
- ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
-
- Vector<uint8_t> texdata;
-
- int w = 2048;
- int h = (vpc / 2048) + 1;
-
- texdata.resize(w * h * 2 * sizeof(float));
-
- {
- Vector2 offset;
- if (emission_mask_centered->is_pressed()) {
- offset = Vector2(-s.width * 0.5, -s.height * 0.5);
- }
-
- uint8_t *tw = texdata.ptrw();
- float *twf = reinterpret_cast<float *>(tw);
- for (int i = 0; i < vpc; i++) {
- twf[i * 2 + 0] = valid_positions[i].x + offset.x;
- twf[i * 2 + 1] = valid_positions[i].y + offset.y;
- }
- }
-
- img.instantiate();
- img->set_data(w, h, false, Image::FORMAT_RGF, texdata);
- pm->set_emission_point_texture(ImageTexture::create_from_image(img));
- pm->set_emission_point_count(vpc);
-
- if (capture_colors) {
- Vector<uint8_t> colordata;
- colordata.resize(w * h * 4); //use RG texture
-
- {
- uint8_t *tw = colordata.ptrw();
- for (int i = 0; i < vpc * 4; i++) {
- tw[i] = valid_colors[i];
- }
- }
-
- img.instantiate();
- img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata);
- pm->set_emission_color_texture(ImageTexture::create_from_image(img));
- }
-
- if (valid_normals.size()) {
- pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
-
- Vector<uint8_t> normdata;
- normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
-
- {
- uint8_t *tw = normdata.ptrw();
- float *twf = reinterpret_cast<float *>(tw);
- for (int i = 0; i < vpc; i++) {
- twf[i * 2 + 0] = valid_normals[i].x;
- twf[i * 2 + 1] = valid_normals[i].y;
- }
- }
-
- img.instantiate();
- img->set_data(w, h, false, Image::FORMAT_RGF, normdata);
- pm->set_emission_normal_texture(ImageTexture::create_from_image(img));
-
- } else {
- pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
- }
-}
-
-void GPUParticles2DEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback));
- menu->set_icon(menu->get_editor_theme_icon(SNAME("GPUParticles2D")));
- file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected));
- EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed));
- } break;
- }
-}
-
-GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
- particles = nullptr;
-
- toolbar = memnew(HBoxContainer);
- add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
- toolbar->hide();
-
- menu = memnew(MenuButton);
- menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_RESTART);
- menu->get_popup()->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
- menu->get_popup()->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
- // menu->get_popup()->add_item(TTR("Clear Emission Mask"), MENU_CLEAR_EMISSION_MASK);
- menu->get_popup()->add_item(TTR("Convert to CPUParticles2D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES);
- menu->set_text(TTR("GPUParticles2D"));
- menu->set_switch_on_hover(true);
- toolbar->add_child(menu);
-
- file = memnew(EditorFileDialog);
- List<String> ext;
- ImageLoader::get_recognized_extensions(&ext);
- for (const String &E : ext) {
- file->add_filter("*." + E, E.to_upper());
- }
- file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
- toolbar->add_child(file);
-
- generate_visibility_rect = memnew(ConfirmationDialog);
- generate_visibility_rect->set_title(TTR("Generate Visibility Rect"));
- VBoxContainer *genvb = memnew(VBoxContainer);
- generate_visibility_rect->add_child(genvb);
- generate_seconds = memnew(SpinBox);
- genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
- generate_seconds->set_min(0.1);
- generate_seconds->set_max(25);
- generate_seconds->set_value(2);
-
- toolbar->add_child(generate_visibility_rect);
-
- generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
-
- emission_mask = memnew(ConfirmationDialog);
- emission_mask->set_title(TTR("Load Emission Mask"));
- VBoxContainer *emvb = memnew(VBoxContainer);
- emission_mask->add_child(emvb);
- emission_mask_mode = memnew(OptionButton);
- emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
- emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
- emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
- emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
- VBoxContainer *optionsvb = memnew(VBoxContainer);
- emvb->add_margin_child(TTR("Options"), optionsvb);
- emission_mask_centered = memnew(CheckBox);
- emission_mask_centered->set_text(TTR("Centered"));
- optionsvb->add_child(emission_mask_centered);
- emission_colors = memnew(CheckBox);
- emission_colors->set_text(TTR("Capture Colors from Pixel"));
- optionsvb->add_child(emission_colors);
-
- toolbar->add_child(emission_mask);
-
- emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_emission_mask));
-}
-
-GPUParticles2DEditorPlugin::~GPUParticles2DEditorPlugin() {
-}
diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.h b/editor/plugins/gpu_particles_2d_editor_plugin.h
deleted file mode 100644
index 658e4d87e5..0000000000
--- a/editor/plugins/gpu_particles_2d_editor_plugin.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_2d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef GPU_PARTICLES_2D_EDITOR_PLUGIN_H
-#define GPU_PARTICLES_2D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/editor_plugin.h"
-#include "scene/2d/gpu_particles_2d.h"
-#include "scene/2d/physics/collision_polygon_2d.h"
-#include "scene/gui/box_container.h"
-#include "scene/gui/spin_box.h"
-
-class CheckBox;
-class ConfirmationDialog;
-class EditorFileDialog;
-class MenuButton;
-class OptionButton;
-
-class GPUParticles2DEditorPlugin : public EditorPlugin {
- GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin);
-
- enum {
- MENU_GENERATE_VISIBILITY_RECT,
- MENU_LOAD_EMISSION_MASK,
- MENU_CLEAR_EMISSION_MASK,
- MENU_OPTION_CONVERT_TO_CPU_PARTICLES,
- MENU_RESTART
- };
-
- enum EmissionMode {
- EMISSION_MODE_SOLID,
- EMISSION_MODE_BORDER,
- EMISSION_MODE_BORDER_DIRECTED
- };
-
- GPUParticles2D *particles = nullptr;
- List<GPUParticles2D *> selected_particles;
-
- EditorFileDialog *file = nullptr;
-
- HBoxContainer *toolbar = nullptr;
- MenuButton *menu = nullptr;
-
- ConfirmationDialog *generate_visibility_rect = nullptr;
- SpinBox *generate_seconds = nullptr;
-
- ConfirmationDialog *emission_mask = nullptr;
- OptionButton *emission_mask_mode = nullptr;
- CheckBox *emission_mask_centered = nullptr;
- CheckBox *emission_colors = nullptr;
-
- String source_emission_file;
-
- void _file_selected(const String &p_file);
- void _menu_callback(int p_idx);
- void _generate_visibility_rect();
- void _generate_emission_mask();
- void _selection_changed();
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual String get_name() const override { return "GPUParticles2D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- GPUParticles2DEditorPlugin();
- ~GPUParticles2DEditorPlugin();
-};
-
-#endif // GPU_PARTICLES_2D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp
deleted file mode 100644
index e711f44ccf..0000000000
--- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp
+++ /dev/null
@@ -1,461 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_3d_editor_plugin.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "gpu_particles_3d_editor_plugin.h"
-
-#include "core/io/resource_loader.h"
-#include "editor/editor_node.h"
-#include "editor/editor_settings.h"
-#include "editor/editor_undo_redo_manager.h"
-#include "editor/plugins/node_3d_editor_plugin.h"
-#include "editor/scene_tree_dock.h"
-#include "scene/3d/cpu_particles_3d.h"
-#include "scene/3d/mesh_instance_3d.h"
-#include "scene/gui/menu_button.h"
-#include "scene/resources/image_texture.h"
-#include "scene/resources/particle_process_material.h"
-
-bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3> &normals) {
- bool use_normals = emission_fill->get_selected() == 1;
-
- if (emission_fill->get_selected() < 2) {
- float area_accum = 0;
- RBMap<float, int> triangle_area_map;
-
- for (int i = 0; i < geometry.size(); i++) {
- float area = geometry[i].get_area();
- if (area < CMP_EPSILON) {
- continue;
- }
- triangle_area_map[area_accum] = i;
- area_accum += area;
- }
-
- if (!triangle_area_map.size() || area_accum == 0) {
- EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area."));
- return false;
- }
-
- int emissor_count = emission_amount->get_value();
-
- for (int i = 0; i < emissor_count; i++) {
- float areapos = Math::random(0.0f, area_accum);
-
- RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos);
- ERR_FAIL_COND_V(!E, false);
- int index = E->value;
- ERR_FAIL_INDEX_V(index, geometry.size(), false);
-
- // ok FINALLY get face
- Face3 face = geometry[index];
- //now compute some position inside the face...
-
- Vector3 pos = face.get_random_point_inside();
-
- points.push_back(pos);
-
- if (use_normals) {
- Vector3 normal = face.get_plane().normal;
- normals.push_back(normal);
- }
- }
- } else {
- int gcount = geometry.size();
-
- if (gcount == 0) {
- EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces."));
- return false;
- }
-
- const Face3 *r = geometry.ptr();
-
- AABB aabb;
-
- for (int i = 0; i < gcount; i++) {
- for (int j = 0; j < 3; j++) {
- if (i == 0 && j == 0) {
- aabb.position = r[i].vertex[j];
- } else {
- aabb.expand_to(r[i].vertex[j]);
- }
- }
- }
-
- int emissor_count = emission_amount->get_value();
-
- for (int i = 0; i < emissor_count; i++) {
- int attempts = 5;
-
- for (int j = 0; j < attempts; j++) {
- Vector3 dir;
- dir[Math::rand() % 3] = 1.0;
- Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position;
-
- Vector3 ofsv = ofs + aabb.size * dir;
-
- //space it a little
- ofs -= dir;
- ofsv += dir;
-
- float max = -1e7, min = 1e7;
-
- for (int k = 0; k < gcount; k++) {
- const Face3 &f3 = r[k];
-
- Vector3 res;
- if (f3.intersects_segment(ofs, ofsv, &res)) {
- res -= ofs;
- float d = dir.dot(res);
-
- if (d < min) {
- min = d;
- }
- if (d > max) {
- max = d;
- }
- }
- }
-
- if (max < min) {
- continue; //lost attempt
- }
-
- float val = min + (max - min) * Math::randf();
-
- Vector3 point = ofs + dir * val;
-
- points.push_back(point);
- break;
- }
- }
- }
-
- return true;
-}
-
-void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) {
- Node *sel = get_node(p_path);
- if (!sel) {
- return;
- }
-
- if (!sel->is_class("Node3D")) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D."), sel->get_name()));
- return;
- }
-
- MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel);
- if (!mi || mi->get_mesh().is_null()) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name()));
- return;
- }
-
- geometry = mi->get_mesh()->get_faces();
-
- if (geometry.size() == 0) {
- EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name()));
- return;
- }
-
- Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * mi->get_global_transform();
-
- int gc = geometry.size();
- Face3 *w = geometry.ptrw();
-
- for (int i = 0; i < gc; i++) {
- for (int j = 0; j < 3; j++) {
- w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
- }
- }
-
- emission_dialog->popup_centered(Size2(300, 130));
-}
-
-GPUParticles3DEditorBase::GPUParticles3DEditorBase() {
- emission_dialog = memnew(ConfirmationDialog);
- emission_dialog->set_title(TTR("Create Emitter"));
- add_child(emission_dialog);
- VBoxContainer *emd_vb = memnew(VBoxContainer);
- emission_dialog->add_child(emd_vb);
-
- emission_amount = memnew(SpinBox);
- emission_amount->set_min(1);
- emission_amount->set_max(100000);
- emission_amount->set_value(512);
- emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount);
-
- emission_fill = memnew(OptionButton);
- emission_fill->add_item(TTR("Surface Points"));
- emission_fill->add_item(TTR("Surface Points+Normal (Directed)"));
- emission_fill->add_item(TTR("Volume"));
- emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill);
-
- emission_dialog->set_ok_button_text(TTR("Create"));
- emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles3DEditorBase::_generate_emission_points));
-
- emission_tree_dialog = memnew(SceneTreeDialog);
- Vector<StringName> valid_types;
- valid_types.push_back("MeshInstance3D");
- emission_tree_dialog->set_valid_types(valid_types);
- add_child(emission_tree_dialog);
- emission_tree_dialog->connect("selected", callable_mp(this, &GPUParticles3DEditorBase::_node_selected));
-}
-
-void GPUParticles3DEditor::_node_removed(Node *p_node) {
- if (p_node == node) {
- node = nullptr;
- hide();
- }
-}
-
-void GPUParticles3DEditor::_notification(int p_notification) {
- switch (p_notification) {
- case NOTIFICATION_ENTER_TREE: {
- options->set_icon(options->get_popup()->get_editor_theme_icon(SNAME("GPUParticles3D")));
- get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed));
- } break;
- }
-}
-
-void GPUParticles3DEditor::_menu_option(int p_option) {
- switch (p_option) {
- case MENU_OPTION_GENERATE_AABB: {
- // Add one second to the default generation lifetime, since the progress is updated every second.
- generate_seconds->set_value(MAX(1.0, trunc(node->get_lifetime()) + 1.0));
-
- if (generate_seconds->get_value() >= 11.0 + CMP_EPSILON) {
- // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
- generate_aabb->popup_centered();
- } else {
- // Generate the visibility AABB immediately.
- _generate_aabb();
- }
- } break;
- case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
- Ref<ParticleProcessMaterial> mat = node->get_process_material();
- if (mat.is_null()) {
- EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required."));
- return;
- }
-
- emission_tree_dialog->popup_scenetree_dialog();
-
- } break;
- case MENU_OPTION_CONVERT_TO_CPU_PARTICLES: {
- CPUParticles3D *cpu_particles = memnew(CPUParticles3D);
- cpu_particles->convert_from_particles(node);
- cpu_particles->set_name(node->get_name());
- cpu_particles->set_transform(node->get_transform());
- cpu_particles->set_visible(node->is_visible());
- cpu_particles->set_process_mode(node->get_process_mode());
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Convert to CPUParticles3D"), UndoRedo::MERGE_DISABLE, node);
- SceneTreeDock::get_singleton()->replace_node(node, cpu_particles);
- ur->commit_action(false);
-
- } break;
- case MENU_OPTION_RESTART: {
- node->restart();
-
- } break;
- }
-}
-
-void GPUParticles3DEditor::_generate_aabb() {
- double time = generate_seconds->get_value();
-
- double running = 0.0;
-
- EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
-
- bool was_emitting = node->is_emitting();
- if (!was_emitting) {
- node->set_emitting(true);
- OS::get_singleton()->delay_usec(1000);
- }
-
- AABB rect;
-
- while (running < time) {
- uint64_t ticks = OS::get_singleton()->get_ticks_usec();
- ep.step(TTR("Generating..."), int(running), true);
- OS::get_singleton()->delay_usec(1000);
-
- AABB capture = node->capture_aabb();
- if (rect == AABB()) {
- rect = capture;
- } else {
- rect.merge_with(capture);
- }
-
- running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
- }
-
- if (!was_emitting) {
- node->set_emitting(false);
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->create_action(TTR("Generate Visibility AABB"));
- ur->add_do_method(node, "set_visibility_aabb", rect);
- ur->add_undo_method(node, "set_visibility_aabb", node->get_visibility_aabb());
- ur->commit_action();
-}
-
-void GPUParticles3DEditor::edit(GPUParticles3D *p_particles) {
- base_node = p_particles;
- node = p_particles;
-}
-
-void GPUParticles3DEditor::_generate_emission_points() {
- /// hacer codigo aca
- Vector<Vector3> points;
- Vector<Vector3> normals;
-
- if (!_generate(points, normals)) {
- return;
- }
-
- int point_count = points.size();
-
- int w = 2048;
- int h = (point_count / 2048) + 1;
-
- Vector<uint8_t> point_img;
- point_img.resize(w * h * 3 * sizeof(float));
-
- {
- uint8_t *iw = point_img.ptrw();
- memset(iw, 0, w * h * 3 * sizeof(float));
- const Vector3 *r = points.ptr();
- float *wf = reinterpret_cast<float *>(iw);
- for (int i = 0; i < point_count; i++) {
- wf[i * 3 + 0] = r[i].x;
- wf[i * 3 + 1] = r[i].y;
- wf[i * 3 + 2] = r[i].z;
- }
- }
-
- Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img));
- Ref<ImageTexture> tex = ImageTexture::create_from_image(image);
-
- Ref<ParticleProcessMaterial> mat = node->get_process_material();
- ERR_FAIL_COND(mat.is_null());
-
- if (normals.size() > 0) {
- mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
- mat->set_emission_point_count(point_count);
- mat->set_emission_point_texture(tex);
-
- Vector<uint8_t> point_img2;
- point_img2.resize(w * h * 3 * sizeof(float));
-
- {
- uint8_t *iw = point_img2.ptrw();
- memset(iw, 0, w * h * 3 * sizeof(float));
- const Vector3 *r = normals.ptr();
- float *wf = reinterpret_cast<float *>(iw);
- for (int i = 0; i < point_count; i++) {
- wf[i * 3 + 0] = r[i].x;
- wf[i * 3 + 1] = r[i].y;
- wf[i * 3 + 2] = r[i].z;
- }
- }
-
- Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2));
- mat->set_emission_normal_texture(ImageTexture::create_from_image(image2));
- } else {
- mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
- mat->set_emission_point_count(point_count);
- mat->set_emission_point_texture(tex);
- }
-}
-
-GPUParticles3DEditor::GPUParticles3DEditor() {
- node = nullptr;
- particles_editor_hb = memnew(HBoxContainer);
- Node3DEditor::get_singleton()->add_control_to_menu_panel(particles_editor_hb);
- options = memnew(MenuButton);
- options->set_switch_on_hover(true);
- particles_editor_hb->add_child(options);
- particles_editor_hb->hide();
-
- options->set_text(TTR("GPUParticles3D"));
- options->get_popup()->add_shortcut(ED_GET_SHORTCUT("particles/restart_emission"), MENU_OPTION_RESTART);
- options->get_popup()->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
- options->get_popup()->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
- options->get_popup()->add_item(TTR("Convert to CPUParticles3D"), MENU_OPTION_CONVERT_TO_CPU_PARTICLES);
-
- options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GPUParticles3DEditor::_menu_option));
-
- generate_aabb = memnew(ConfirmationDialog);
- generate_aabb->set_title(TTR("Generate Visibility AABB"));
- VBoxContainer *genvb = memnew(VBoxContainer);
- generate_aabb->add_child(genvb);
- generate_seconds = memnew(SpinBox);
- genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
- generate_seconds->set_min(0.1);
- generate_seconds->set_max(25);
- generate_seconds->set_value(2);
-
- add_child(generate_aabb);
-
- generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles3DEditor::_generate_aabb));
-}
-
-void GPUParticles3DEditorPlugin::edit(Object *p_object) {
- particles_editor->edit(Object::cast_to<GPUParticles3D>(p_object));
-}
-
-bool GPUParticles3DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("GPUParticles3D");
-}
-
-void GPUParticles3DEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- particles_editor->show();
- particles_editor->particles_editor_hb->show();
- } else {
- particles_editor->particles_editor_hb->hide();
- particles_editor->hide();
- particles_editor->edit(nullptr);
- }
-}
-
-GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
- particles_editor = memnew(GPUParticles3DEditor);
- EditorNode::get_singleton()->get_gui_base()->add_child(particles_editor);
-
- particles_editor->hide();
-}
-
-GPUParticles3DEditorPlugin::~GPUParticles3DEditorPlugin() {
-}
diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.h b/editor/plugins/gpu_particles_3d_editor_plugin.h
deleted file mode 100644
index 1295836b5f..0000000000
--- a/editor/plugins/gpu_particles_3d_editor_plugin.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/**************************************************************************/
-/* gpu_particles_3d_editor_plugin.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef GPU_PARTICLES_3D_EDITOR_PLUGIN_H
-#define GPU_PARTICLES_3D_EDITOR_PLUGIN_H
-
-#include "editor/plugins/editor_plugin.h"
-#include "scene/3d/gpu_particles_3d.h"
-#include "scene/gui/spin_box.h"
-
-class ConfirmationDialog;
-class HBoxContainer;
-class MenuButton;
-class OptionButton;
-class SceneTreeDialog;
-
-class GPUParticles3DEditorBase : public Control {
- GDCLASS(GPUParticles3DEditorBase, Control);
-
-protected:
- Node3D *base_node = nullptr;
- Panel *panel = nullptr;
- MenuButton *options = nullptr;
- HBoxContainer *particles_editor_hb = nullptr;
-
- SceneTreeDialog *emission_tree_dialog = nullptr;
-
- ConfirmationDialog *emission_dialog = nullptr;
- SpinBox *emission_amount = nullptr;
- OptionButton *emission_fill = nullptr;
-
- Vector<Face3> geometry;
-
- bool _generate(Vector<Vector3> &points, Vector<Vector3> &normals);
- virtual void _generate_emission_points() {}
- void _node_selected(const NodePath &p_path);
-
-public:
- GPUParticles3DEditorBase();
-};
-
-class GPUParticles3DEditor : public GPUParticles3DEditorBase {
- GDCLASS(GPUParticles3DEditor, GPUParticles3DEditorBase);
-
- ConfirmationDialog *generate_aabb = nullptr;
- SpinBox *generate_seconds = nullptr;
- GPUParticles3D *node = nullptr;
-
- enum Menu {
- MENU_OPTION_GENERATE_AABB,
- MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
- MENU_OPTION_CLEAR_EMISSION_VOLUME,
- MENU_OPTION_CONVERT_TO_CPU_PARTICLES,
- MENU_OPTION_RESTART,
-
- };
-
- void _generate_aabb();
-
- void _menu_option(int);
-
- friend class GPUParticles3DEditorPlugin;
-
- virtual void _generate_emission_points() override;
-
-protected:
- void _notification(int p_notification);
- void _node_removed(Node *p_node);
-
-public:
- void edit(GPUParticles3D *p_particles);
- GPUParticles3DEditor();
-};
-
-class GPUParticles3DEditorPlugin : public EditorPlugin {
- GDCLASS(GPUParticles3DEditorPlugin, EditorPlugin);
-
- GPUParticles3DEditor *particles_editor = nullptr;
-
-public:
- virtual String get_name() const override { return "GPUParticles3D"; }
- bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- GPUParticles3DEditorPlugin();
- ~GPUParticles3DEditorPlugin();
-};
-
-#endif // GPU_PARTICLES_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp
index 1c17d99d0d..854ab7de8f 100644
--- a/editor/plugins/lightmap_gi_editor_plugin.cpp
+++ b/editor/plugins/lightmap_gi_editor_plugin.cpp
@@ -179,6 +179,23 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin() {
// when the editor theme updates.
bake->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Bake"), EditorStringName(EditorIcons)));
bake->set_text(TTR("Bake Lightmaps"));
+
+#ifdef MODULE_LIGHTMAPPER_RD_ENABLED
+ // Disable lightmap baking if not supported on the current GPU.
+ if (!DisplayServer::get_singleton()->can_create_rendering_device()) {
+ bake->set_disabled(true);
+ bake->set_tooltip_text(vformat(TTR("Lightmap baking is not supported on this GPU (%s)."), RenderingServer::get_singleton()->get_video_adapter_name()));
+ }
+#else
+ // Disable lightmap baking if the module is disabled at compile-time.
+ bake->set_disabled(true);
+#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+ bake->set_tooltip_text(vformat(TTR("Lightmaps cannot be baked on %s."), OS::get_singleton()->get_name()));
+#else
+ bake->set_tooltip_text(TTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time."));
+#endif
+#endif // MODULE_LIGHTMAPPER_RD_ENABLED
+
bake->hide();
bake->connect(SceneStringName(pressed), Callable(this, "_bake"));
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index dc86acd884..0df0274495 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -673,6 +673,7 @@ void Node3DEditorViewport::cancel_transform() {
sp->set_global_transform(se->original);
}
+ collision_reposition = false;
finish_transform();
set_message(TTR("Transform Aborted."), 3);
}
@@ -1802,7 +1803,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (b->is_pressed()) {
clicked_wants_append = b->is_shift_pressed();
- if (_edit.mode != TRANSFORM_NONE && _edit.instant) {
+ if (_edit.mode != TRANSFORM_NONE && (_edit.instant || collision_reposition)) {
commit_transform();
break; // just commit the edit, stop processing the event so we don't deselect the object
}
@@ -2398,10 +2399,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
cancel_transform();
}
if (!is_freelook_active() && !k->is_echo()) {
- if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) {
+ if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && (_edit.mode != TRANSFORM_TRANSLATE || collision_reposition)) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_TRANSLATE, true);
- } else if (_edit.instant) {
+ } else if (_edit.instant || collision_reposition) {
commit_transform();
begin_transform(TRANSFORM_TRANSLATE, true);
}
@@ -2409,7 +2410,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_ROTATE, true);
- } else if (_edit.instant) {
+ } else if (_edit.instant || collision_reposition) {
commit_transform();
begin_transform(TRANSFORM_ROTATE, true);
}
@@ -2417,11 +2418,23 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) {
if (_edit.mode == TRANSFORM_NONE) {
begin_transform(TRANSFORM_SCALE, true);
- } else if (_edit.instant) {
+ } else if (_edit.instant || collision_reposition) {
commit_transform();
begin_transform(TRANSFORM_SCALE, true);
}
}
+ if (ED_IS_SHORTCUT("spatial_editor/collision_reposition", p_event) && editor_selection->get_selected_node_list().size() == 1 && !collision_reposition) {
+ if (_edit.mode == TRANSFORM_NONE || _edit.instant) {
+ if (_edit.mode == TRANSFORM_NONE) {
+ _compute_edit(_edit.mouse_pos);
+ } else {
+ commit_transform();
+ _compute_edit(_edit.mouse_pos);
+ }
+ _edit.mode = TRANSFORM_TRANSLATE;
+ collision_reposition = true;
+ }
+ }
}
// Freelook doesn't work in orthogonal mode.
@@ -3072,11 +3085,24 @@ void Node3DEditorViewport::_notification(int p_what) {
} break;
case NOTIFICATION_PHYSICS_PROCESS: {
+ if (collision_reposition) {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+
+ if (selection.size() == 1) {
+ Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());
+ double snap = EDITOR_GET("interface/inspector/default_float_step");
+ int snap_step_decimals = Math::range_step_decimals(snap);
+ set_message(TTR("Translating:") + " (" + String::num(first_selected_node->get_global_position().x, snap_step_decimals) + ", " +
+ String::num(first_selected_node->get_global_position().y, snap_step_decimals) + ", " + String::num(first_selected_node->get_global_position().z, snap_step_decimals) + ")");
+ first_selected_node->set_global_position(spatial_editor->snap_point(_get_instance_position(_edit.mouse_pos, first_selected_node)));
+ }
+ }
+
if (!update_preview_node) {
return;
}
if (preview_node->is_inside_tree()) {
- preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos));
+ preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos, preview_node));
double snap = EDITOR_GET("interface/inspector/default_float_step");
int snap_step_decimals = Math::range_step_decimals(snap);
set_message(TTR("Instantiating:") + " (" + String::num(preview_node_pos.x, snap_step_decimals) + ", " +
@@ -3966,7 +3992,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
return;
}
- bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible;
+ bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition;
for (int i = 0; i < 3; i++) {
Transform3D axis_angle;
if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {
@@ -3997,7 +4023,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
xform.orthonormalize();
xform.basis.scale(scale);
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);
- RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
+ RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
}
void Node3DEditorViewport::set_state(const Dictionary &p_state) {
@@ -4241,7 +4267,19 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node,
accept = p_accept;
}
-Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const {
+void _insert_rid_recursive(Node *node, HashSet<RID> &rids) {
+ CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node);
+ if (co) {
+ rids.insert(co->get_rid());
+ }
+
+ for (int i = 0; i < node->get_child_count(); i++) {
+ Node *child = node->get_child(i);
+ _insert_rid_recursive(child, rids);
+ }
+}
+
+Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos, Node3D *p_node) const {
const float MAX_DISTANCE = 50.0;
const float FALLBACK_DISTANCE = 5.0;
@@ -4250,13 +4288,51 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const
PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
+ HashSet<RID> rids;
+
+ if (!preview_node->is_inside_tree()) {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+
+ Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());
+
+ Array children = first_selected_node->get_children();
+
+ if (first_selected_node) {
+ _insert_rid_recursive(first_selected_node, rids);
+ }
+ }
+
PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.exclude = rids;
ray_params.from = world_pos;
ray_params.to = world_pos + world_ray * camera->get_far();
PhysicsDirectSpaceState3D::RayResult result;
- if (ss->intersect_ray(ray_params, result)) {
- return result.position;
+ if (ss->intersect_ray(ray_params, result) && (preview_node->get_child_count() > 0 || !preview_node->is_inside_tree())) {
+ // Calculate an offset for the `p_node` such that the its bounding box is on top of and touching the contact surface's plane.
+
+ // Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal.
+ const Vector3 bb_basis_x = result.normal;
+ Vector3 bb_basis_y = Vector3(0, 1, 0);
+ bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);
+ if (bb_basis_y.is_zero_approx()) {
+ bb_basis_y = Vector3(0, 0, 1);
+ bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);
+ }
+ bb_basis_y = bb_basis_y.normalized();
+ const Vector3 bb_basis_z = bb_basis_x.cross(bb_basis_y);
+ const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z);
+
+ // This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible.
+ const Transform3D bb_transform = Transform3D(bb_basis, p_node->get_transform().origin);
+ const AABB p_node_bb = _calculate_spatial_bounds(p_node, true, &bb_transform);
+ // The x-axis's alignment with the surface normal also makes it trivial to get the distance from `p_node`'s origin at (0, 0, 0) to the correct AABB face.
+ const float offset_distance = -p_node_bb.position.x;
+
+ // `result_offset` is in global space.
+ const Vector3 result_offset = result.position + result.normal * offset_distance;
+
+ return result_offset;
}
const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
@@ -4284,18 +4360,21 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const
return world_pos + world_ray * FALLBACK_DISTANCE;
}
-AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent) {
+AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level, const Transform3D *p_bounds_orientation) {
AABB bounds;
- if (!p_top_level_parent) {
- p_top_level_parent = p_parent;
+ Transform3D bounds_orientation;
+ if (p_bounds_orientation) {
+ bounds_orientation = *p_bounds_orientation;
+ } else {
+ bounds_orientation = p_parent->get_global_transform();
}
if (!p_parent) {
return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
}
- Transform3D xform_to_top_level_parent_space = p_top_level_parent->get_global_transform().affine_inverse() * p_parent->get_global_transform();
+ const Transform3D xform_to_top_level_parent_space = bounds_orientation.affine_inverse() * p_parent->get_global_transform();
const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent);
if (visual_instance) {
@@ -4306,9 +4385,9 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, con
bounds = xform_to_top_level_parent_space.xform(bounds);
for (int i = 0; i < p_parent->get_child_count(); i++) {
- Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i));
- if (child) {
- AABB child_bounds = _calculate_spatial_bounds(child, p_top_level_parent);
+ const Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i));
+ if (child && !(p_omit_top_level && child->is_set_as_top_level())) {
+ const AABB child_bounds = _calculate_spatial_bounds(child, p_omit_top_level, &bounds_orientation);
bounds.merge_with(child_bounds);
}
}
@@ -4359,6 +4438,10 @@ void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) con
if (instance) {
instance = _sanitize_preview_node(instance);
preview_node->add_child(instance);
+ Node3D *node_3d = Object::cast_to<Node3D>(instance);
+ if (node_3d) {
+ node_3d->set_as_top_level(false);
+ }
}
add_preview = true;
}
@@ -4579,8 +4662,12 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path
}
Transform3D new_tf = node3d->get_transform();
- new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position());
- new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;
+ if (node3d->is_set_as_top_level()) {
+ new_tf.origin += preview_node_pos;
+ } else {
+ new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position());
+ new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;
+ }
undo_redo->add_do_method(instantiated_scene, "set_transform", new_tf);
}
@@ -4878,6 +4965,7 @@ void Node3DEditorViewport::commit_transform() {
}
undo_redo->commit_action();
+ collision_reposition = false;
finish_transform();
set_message("");
}
@@ -5475,6 +5563,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
ED_SHORTCUT("spatial_editor/instant_translate", TTR("Begin Translate Transformation"));
ED_SHORTCUT("spatial_editor/instant_rotate", TTR("Begin Rotate Transformation"));
ED_SHORTCUT("spatial_editor/instant_scale", TTR("Begin Scale Transformation"));
+ ED_SHORTCUT("spatial_editor/collision_reposition", TTR("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G);
preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
@@ -9003,8 +9092,7 @@ Node3DEditor::Node3DEditor() {
current_hover_gizmo_handle = -1;
current_hover_gizmo_handle_secondary = false;
{
- //sun popup
-
+ // Sun/preview environment popup.
sun_environ_popup = memnew(PopupPanel);
add_child(sun_environ_popup);
@@ -9018,7 +9106,7 @@ Node3DEditor::Node3DEditor() {
sun_vb->hide();
sun_title = memnew(Label);
- sun_title->set_theme_type_variation("HeaderSmall");
+ sun_title->set_theme_type_variation("HeaderMedium");
sun_vb->add_child(sun_title);
sun_title->set_text(TTR("Preview Sun"));
sun_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
@@ -9056,11 +9144,14 @@ void fragment() {
sun_direction->set_material(sun_direction_material);
HBoxContainer *sun_angle_hbox = memnew(HBoxContainer);
+ sun_angle_hbox->set_h_size_flags(SIZE_EXPAND_FILL);
VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer);
+ sun_angle_altitude_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
Label *sun_angle_altitude_label = memnew(Label);
sun_angle_altitude_label->set_text(TTR("Angular Altitude"));
sun_angle_altitude_vbox->add_child(sun_angle_altitude_label);
sun_angle_altitude = memnew(EditorSpinSlider);
+ sun_angle_altitude->set_suffix(U"\u00B0");
sun_angle_altitude->set_max(90);
sun_angle_altitude->set_min(-90);
sun_angle_altitude->set_step(0.1);
@@ -9068,11 +9159,13 @@ void fragment() {
sun_angle_altitude_vbox->add_child(sun_angle_altitude);
sun_angle_hbox->add_child(sun_angle_altitude_vbox);
VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer);
+ sun_angle_azimuth_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0));
Label *sun_angle_azimuth_label = memnew(Label);
sun_angle_azimuth_label->set_text(TTR("Azimuth"));
sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label);
sun_angle_azimuth = memnew(EditorSpinSlider);
+ sun_angle_azimuth->set_suffix(U"\u00B0");
sun_angle_azimuth->set_max(180);
sun_angle_azimuth->set_min(-180);
sun_angle_azimuth->set_step(0.1);
@@ -9117,7 +9210,7 @@ void fragment() {
sun_state->set_h_size_flags(SIZE_EXPAND_FILL);
VSeparator *sc = memnew(VSeparator);
- sc->set_custom_minimum_size(Size2(50 * EDSCALE, 0));
+ sc->set_custom_minimum_size(Size2(10 * EDSCALE, 0));
sc->set_v_size_flags(SIZE_EXPAND_FILL);
sun_environ_hb->add_child(sc);
@@ -9127,7 +9220,7 @@ void fragment() {
environ_vb->hide();
environ_title = memnew(Label);
- environ_title->set_theme_type_variation("HeaderSmall");
+ environ_title->set_theme_type_variation("HeaderMedium");
environ_vb->add_child(environ_title);
environ_title->set_text(TTR("Preview Environment"));
@@ -9154,21 +9247,25 @@ void fragment() {
environ_ao_button = memnew(Button);
environ_ao_button->set_text(TTR("AO"));
+ environ_ao_button->set_h_size_flags(SIZE_EXPAND_FILL);
environ_ao_button->set_toggle_mode(true);
environ_ao_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED);
fx_vb->add_child(environ_ao_button);
environ_glow_button = memnew(Button);
environ_glow_button->set_text(TTR("Glow"));
+ environ_glow_button->set_h_size_flags(SIZE_EXPAND_FILL);
environ_glow_button->set_toggle_mode(true);
environ_glow_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED);
fx_vb->add_child(environ_glow_button);
environ_tonemap_button = memnew(Button);
environ_tonemap_button->set_text(TTR("Tonemap"));
+ environ_tonemap_button->set_h_size_flags(SIZE_EXPAND_FILL);
environ_tonemap_button->set_toggle_mode(true);
environ_tonemap_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED);
fx_vb->add_child(environ_tonemap_button);
environ_gi_button = memnew(Button);
environ_gi_button->set_text(TTR("GI"));
+ environ_gi_button->set_h_size_flags(SIZE_EXPAND_FILL);
environ_gi_button->set_toggle_mode(true);
environ_gi_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_preview_settings_changed), CONNECT_DEFERRED);
fx_vb->add_child(environ_gi_button);
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index c7e6420875..1b03362606 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -245,6 +245,7 @@ private:
bool auto_orthogonal;
bool lock_rotation;
bool transform_gizmo_visible = true;
+ bool collision_reposition = false;
real_t gizmo_scale;
bool freelook_active;
@@ -338,7 +339,6 @@ private:
TRANSFORM_ROTATE,
TRANSFORM_TRANSLATE,
TRANSFORM_SCALE
-
};
enum TransformPlane {
TRANSFORM_VIEW,
@@ -471,8 +471,8 @@ private:
void _list_select(Ref<InputEventMouseButton> b);
Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const;
- Vector3 _get_instance_position(const Point2 &p_pos) const;
- static AABB _calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent = nullptr);
+ Vector3 _get_instance_position(const Point2 &p_pos, Node3D *p_node) const;
+ static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level = false, const Transform3D *p_bounds_orientation = nullptr);
Node *_sanitize_preview_node(Node *p_node) const;
diff --git a/editor/plugins/particles_editor_plugin.cpp b/editor/plugins/particles_editor_plugin.cpp
new file mode 100644
index 0000000000..34f5dcf963
--- /dev/null
+++ b/editor/plugins/particles_editor_plugin.cpp
@@ -0,0 +1,968 @@
+/**************************************************************************/
+/* particles_editor_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "particles_editor_plugin.h"
+
+#include "canvas_item_editor_plugin.h"
+#include "core/io/image_loader.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_file_dialog.h"
+#include "editor/scene_tree_dock.h"
+#include "scene/2d/cpu_particles_2d.h"
+#include "scene/2d/gpu_particles_2d.h"
+#include "scene/3d/cpu_particles_3d.h"
+#include "scene/3d/gpu_particles_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/separator.h"
+#include "scene/gui/spin_box.h"
+#include "scene/resources/image_texture.h"
+#include "scene/resources/particle_process_material.h"
+
+void ParticlesEditorPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (handled_type.ends_with("2D")) {
+ add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);
+ } else if (handled_type.ends_with("3D")) {
+ add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, toolbar);
+ } else {
+ DEV_ASSERT(false);
+ }
+
+ menu->set_icon(menu->get_editor_theme_icon(handled_type));
+ menu->set_text(handled_type);
+
+ PopupMenu *popup = menu->get_popup();
+ popup->add_shortcut(ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R), MENU_RESTART);
+ _add_menu_options(popup);
+ popup->add_item(conversion_option_name, MENU_OPTION_CONVERT);
+ } break;
+ }
+}
+
+bool ParticlesEditorPlugin::need_show_lifetime_dialog(SpinBox *p_seconds) {
+ // Add one second to the default generation lifetime, since the progress is updated every second.
+ p_seconds->set_value(MAX(1.0, trunc(edited_node->get("lifetime").operator double()) + 1.0));
+
+ if (p_seconds->get_value() >= 11.0 + CMP_EPSILON) {
+ // Only pop up the time dialog if the particle's lifetime is long enough to warrant shortening it.
+ return true;
+ } else {
+ // Generate the visibility rect/AABB immediately.
+ return false;
+ }
+}
+
+void ParticlesEditorPlugin::_menu_callback(int p_idx) {
+ switch (p_idx) {
+ case MENU_OPTION_CONVERT: {
+ Node *converted_node = _convert_particles();
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(conversion_option_name, UndoRedo::MERGE_DISABLE, edited_node);
+ SceneTreeDock::get_singleton()->replace_node(edited_node, converted_node);
+ ur->commit_action(false);
+ } break;
+
+ case MENU_RESTART: {
+ edited_node->call("restart");
+ }
+ }
+}
+
+void ParticlesEditorPlugin::edit(Object *p_object) {
+ edited_node = Object::cast_to<Node>(p_object);
+}
+
+bool ParticlesEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class(handled_type);
+}
+
+void ParticlesEditorPlugin::make_visible(bool p_visible) {
+ toolbar->set_visible(p_visible);
+}
+
+ParticlesEditorPlugin::ParticlesEditorPlugin() {
+ toolbar = memnew(HBoxContainer);
+ toolbar->hide();
+
+ menu = memnew(MenuButton);
+ menu->set_switch_on_hover(true);
+ toolbar->add_child(menu);
+ menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ParticlesEditorPlugin::_menu_callback));
+}
+
+// 2D /////////////////////////////////////////////
+
+void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) {
+ if (p_idx == MENU_GENERATE_VISIBILITY_RECT) {
+ if (need_show_lifetime_dialog(generate_seconds)) {
+ generate_visibility_rect->popup_centered();
+ } else {
+ _generate_visibility_rect();
+ }
+ } else {
+ Particles2DEditorPlugin::_menu_callback(p_idx);
+ }
+}
+
+void GPUParticles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
+ Particles2DEditorPlugin::_add_menu_options(p_menu);
+ p_menu->add_item(TTR("Generate Visibility Rect"), MENU_GENERATE_VISIBILITY_RECT);
+}
+
+void Particles2DEditorPlugin::_file_selected(const String &p_file) {
+ source_emission_file = p_file;
+ emission_mask->popup_centered();
+}
+
+void Particles2DEditorPlugin::_get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size) {
+ Ref<Image> img;
+ img.instantiate();
+ Error err = ImageLoader::load_image(source_emission_file, img);
+ ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'.");
+
+ if (img->is_compressed()) {
+ img->decompress();
+ }
+ img->convert(Image::FORMAT_RGBA8);
+ ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8);
+ Size2i s = img->get_size();
+ ERR_FAIL_COND(s.width == 0 || s.height == 0);
+
+ r_image_size = s;
+
+ r_valid_positions.resize(s.width * s.height);
+
+ EmissionMode emode = (EmissionMode)emission_mask_mode->get_selected();
+
+ if (emode == EMISSION_MODE_BORDER_DIRECTED) {
+ r_valid_normals.resize(s.width * s.height);
+ }
+
+ bool capture_colors = emission_colors->is_pressed();
+
+ if (capture_colors) {
+ r_valid_colors.resize(s.width * s.height * 4);
+ }
+
+ int vpc = 0;
+
+ {
+ Vector<uint8_t> img_data = img->get_data();
+ const uint8_t *r = img_data.ptr();
+
+ for (int i = 0; i < s.width; i++) {
+ for (int j = 0; j < s.height; j++) {
+ uint8_t a = r[(j * s.width + i) * 4 + 3];
+
+ if (a > 128) {
+ if (emode == EMISSION_MODE_SOLID) {
+ if (capture_colors) {
+ r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
+ r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
+ r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
+ r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
+ }
+ r_valid_positions.write[vpc++] = Point2(i, j);
+
+ } else {
+ bool on_border = false;
+ for (int x = i - 1; x <= i + 1; x++) {
+ for (int y = j - 1; y <= j + 1; y++) {
+ if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
+ on_border = true;
+ break;
+ }
+ }
+
+ if (on_border) {
+ break;
+ }
+ }
+
+ if (on_border) {
+ r_valid_positions.write[vpc] = Point2(i, j);
+
+ if (emode == EMISSION_MODE_BORDER_DIRECTED) {
+ Vector2 normal;
+ for (int x = i - 2; x <= i + 2; x++) {
+ for (int y = j - 2; y <= j + 2; y++) {
+ if (x == i && y == j) {
+ continue;
+ }
+
+ if (x < 0 || y < 0 || x >= s.width || y >= s.height || r[(y * s.width + x) * 4 + 3] <= 128) {
+ normal += Vector2(x - i, y - j).normalized();
+ }
+ }
+ }
+
+ normal.normalize();
+ r_valid_normals.write[vpc] = normal;
+ }
+
+ if (capture_colors) {
+ r_valid_colors.write[vpc * 4 + 0] = r[(j * s.width + i) * 4 + 0];
+ r_valid_colors.write[vpc * 4 + 1] = r[(j * s.width + i) * 4 + 1];
+ r_valid_colors.write[vpc * 4 + 2] = r[(j * s.width + i) * 4 + 2];
+ r_valid_colors.write[vpc * 4 + 3] = r[(j * s.width + i) * 4 + 3];
+ }
+
+ vpc++;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ r_valid_positions.resize(vpc);
+ if (!r_valid_normals.is_empty()) {
+ r_valid_normals.resize(vpc);
+ }
+}
+
+Particles2DEditorPlugin::Particles2DEditorPlugin() {
+ file = memnew(EditorFileDialog);
+
+ List<String> ext;
+ ImageLoader::get_recognized_extensions(&ext);
+ for (const String &E : ext) {
+ file->add_filter("*." + E, E.to_upper());
+ }
+
+ file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ EditorNode::get_singleton()->get_gui_base()->add_child(file);
+ file->connect("file_selected", callable_mp(this, &Particles2DEditorPlugin::_file_selected));
+
+ emission_mask = memnew(ConfirmationDialog);
+ emission_mask->set_title(TTR("Load Emission Mask"));
+
+ VBoxContainer *emvb = memnew(VBoxContainer);
+ emission_mask->add_child(emvb);
+
+ emission_mask_mode = memnew(OptionButton);
+ emission_mask_mode->add_item(TTR("Solid Pixels"), EMISSION_MODE_SOLID);
+ emission_mask_mode->add_item(TTR("Border Pixels"), EMISSION_MODE_BORDER);
+ emission_mask_mode->add_item(TTR("Directed Border Pixels"), EMISSION_MODE_BORDER_DIRECTED);
+ emvb->add_margin_child(TTR("Emission Mask"), emission_mask_mode);
+
+ VBoxContainer *optionsvb = memnew(VBoxContainer);
+ emvb->add_margin_child(TTR("Options"), optionsvb);
+
+ emission_mask_centered = memnew(CheckBox(TTR("Centered")));
+ optionsvb->add_child(emission_mask_centered);
+ emission_colors = memnew(CheckBox(TTR("Capture Colors from Pixel")));
+ optionsvb->add_child(emission_colors);
+
+ EditorNode::get_singleton()->get_gui_base()->add_child(emission_mask);
+
+ emission_mask->connect(SceneStringName(confirmed), callable_mp(this, &Particles2DEditorPlugin::_generate_emission_mask));
+}
+
+void GPUParticles2DEditorPlugin::_selection_changed() {
+ List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list();
+ if (selected_particles.is_empty() && selected_nodes.is_empty()) {
+ return;
+ }
+
+ for (GPUParticles2D *particles : selected_particles) {
+ particles->set_show_visibility_rect(false);
+ }
+ selected_particles.clear();
+
+ for (Node *node : selected_nodes) {
+ GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(node);
+ if (selected_particle) {
+ selected_particle->set_show_visibility_rect(true);
+ selected_particles.push_back(selected_particle);
+ }
+ }
+}
+
+void GPUParticles2DEditorPlugin::_generate_visibility_rect() {
+ GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
+
+ double time = generate_seconds->get_value();
+
+ float running = 0.0;
+
+ EditorProgress ep("gen_vrect", TTR("Generating Visibility Rect (Waiting for Particle Simulation)"), int(time));
+
+ bool was_emitting = particles->is_emitting();
+ if (!was_emitting) {
+ particles->set_emitting(true);
+ OS::get_singleton()->delay_usec(1000);
+ }
+
+ Rect2 rect;
+ while (running < time) {
+ uint64_t ticks = OS::get_singleton()->get_ticks_usec();
+ ep.step(TTR("Generating..."), int(running), true);
+ OS::get_singleton()->delay_usec(1000);
+
+ Rect2 capture = particles->capture_rect();
+ if (rect == Rect2()) {
+ rect = capture;
+ } else {
+ rect = rect.merge(capture);
+ }
+
+ running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
+ }
+
+ if (!was_emitting) {
+ particles->set_emitting(false);
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Generate Visibility Rect"));
+ undo_redo->add_do_method(particles, "set_visibility_rect", rect);
+ undo_redo->add_undo_method(particles, "set_visibility_rect", particles->get_visibility_rect());
+ undo_redo->commit_action();
+}
+
+void GPUParticles2DEditorPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed));
+ } break;
+ }
+}
+
+void Particles2DEditorPlugin::_menu_callback(int p_idx) {
+ if (p_idx == MENU_LOAD_EMISSION_MASK) {
+ file->popup_file_dialog();
+ } else {
+ ParticlesEditorPlugin::_menu_callback(p_idx);
+ }
+}
+
+void Particles2DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
+ p_menu->add_item(TTR("Load Emission Mask"), MENU_LOAD_EMISSION_MASK);
+}
+
+Node *GPUParticles2DEditorPlugin::_convert_particles() {
+ GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
+
+ CPUParticles2D *cpu_particles = memnew(CPUParticles2D);
+ cpu_particles->convert_from_particles(particles);
+ cpu_particles->set_name(particles->get_name());
+ cpu_particles->set_transform(particles->get_transform());
+ cpu_particles->set_visible(particles->is_visible());
+ cpu_particles->set_process_mode(particles->get_process_mode());
+ cpu_particles->set_z_index(particles->get_z_index());
+ return cpu_particles;
+}
+
+void GPUParticles2DEditorPlugin::_generate_emission_mask() {
+ GPUParticles2D *particles = Object::cast_to<GPUParticles2D>(edited_node);
+ Ref<ParticleProcessMaterial> pm = particles->get_process_material();
+ if (pm.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("Can only set point into a ParticleProcessMaterial process material"));
+ return;
+ }
+
+ PackedVector2Array valid_positions;
+ PackedVector2Array valid_normals;
+ PackedByteArray valid_colors;
+ Vector2i image_size;
+ _get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
+
+ ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
+
+ Vector<uint8_t> texdata;
+
+ int vpc = valid_positions.size();
+ int w = 2048;
+ int h = (vpc / 2048) + 1;
+
+ texdata.resize(w * h * 2 * sizeof(float));
+
+ {
+ Vector2 offset;
+ if (emission_mask_centered->is_pressed()) {
+ offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
+ }
+
+ uint8_t *tw = texdata.ptrw();
+ float *twf = reinterpret_cast<float *>(tw);
+ for (int i = 0; i < vpc; i++) {
+ twf[i * 2 + 0] = valid_positions[i].x + offset.x;
+ twf[i * 2 + 1] = valid_positions[i].y + offset.y;
+ }
+ }
+
+ Ref<Image> img;
+ img.instantiate();
+ img->set_data(w, h, false, Image::FORMAT_RGF, texdata);
+ pm->set_emission_point_texture(ImageTexture::create_from_image(img));
+ pm->set_emission_point_count(vpc);
+
+ if (emission_colors->is_pressed()) {
+ Vector<uint8_t> colordata;
+ colordata.resize(w * h * 4); //use RG texture
+
+ {
+ uint8_t *tw = colordata.ptrw();
+ for (int i = 0; i < vpc * 4; i++) {
+ tw[i] = valid_colors[i];
+ }
+ }
+
+ img.instantiate();
+ img->set_data(w, h, false, Image::FORMAT_RGBA8, colordata);
+ pm->set_emission_color_texture(ImageTexture::create_from_image(img));
+ }
+
+ if (valid_normals.size()) {
+ pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
+
+ Vector<uint8_t> normdata;
+ normdata.resize(w * h * 2 * sizeof(float)); //use RG texture
+
+ {
+ uint8_t *tw = normdata.ptrw();
+ float *twf = reinterpret_cast<float *>(tw);
+ for (int i = 0; i < vpc; i++) {
+ twf[i * 2 + 0] = valid_normals[i].x;
+ twf[i * 2 + 1] = valid_normals[i].y;
+ }
+ }
+
+ img.instantiate();
+ img->set_data(w, h, false, Image::FORMAT_RGF, normdata);
+ pm->set_emission_normal_texture(ImageTexture::create_from_image(img));
+
+ } else {
+ pm->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
+ }
+}
+
+GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() {
+ handled_type = "GPUParticles2D"; // TTR("GPUParticles2D")
+ conversion_option_name = TTR("Convert to CPUParticles2D");
+
+ generate_visibility_rect = memnew(ConfirmationDialog);
+ generate_visibility_rect->set_title(TTR("Generate Visibility Rect"));
+
+ VBoxContainer *genvb = memnew(VBoxContainer);
+ generate_visibility_rect->add_child(genvb);
+
+ generate_seconds = memnew(SpinBox);
+ generate_seconds->set_min(0.1);
+ generate_seconds->set_max(25);
+ generate_seconds->set_value(2);
+ genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
+
+ EditorNode::get_singleton()->get_gui_base()->add_child(generate_visibility_rect);
+
+ generate_visibility_rect->connect(SceneStringName(confirmed), callable_mp(this, &GPUParticles2DEditorPlugin::_generate_visibility_rect));
+}
+
+Node *CPUParticles2DEditorPlugin::_convert_particles() {
+ CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
+
+ GPUParticles2D *gpu_particles = memnew(GPUParticles2D);
+ gpu_particles->convert_from_particles(particles);
+ gpu_particles->set_name(particles->get_name());
+ gpu_particles->set_transform(particles->get_transform());
+ gpu_particles->set_visible(particles->is_visible());
+ gpu_particles->set_process_mode(particles->get_process_mode());
+ return gpu_particles;
+}
+
+void CPUParticles2DEditorPlugin::_generate_emission_mask() {
+ CPUParticles2D *particles = Object::cast_to<CPUParticles2D>(edited_node);
+
+ PackedVector2Array valid_positions;
+ PackedVector2Array valid_normals;
+ PackedByteArray valid_colors;
+ Vector2i image_size;
+ _get_base_emission_mask(valid_positions, valid_normals, valid_colors, image_size);
+
+ ERR_FAIL_COND_MSG(valid_positions.is_empty(), "No pixels with transparency > 128 in image...");
+
+ int vpc = valid_positions.size();
+ if (emission_colors->is_pressed()) {
+ PackedColorArray pca;
+ pca.resize(vpc);
+ Color *pcaw = pca.ptrw();
+ for (int i = 0; i < vpc; i += 1) {
+ Color color;
+ color.r = valid_colors[i * 4 + 0] / 255.0f;
+ color.g = valid_colors[i * 4 + 1] / 255.0f;
+ color.b = valid_colors[i * 4 + 2] / 255.0f;
+ color.a = valid_colors[i * 4 + 3] / 255.0f;
+ pcaw[i] = color;
+ }
+ particles->set_emission_colors(pca);
+ }
+
+ if (valid_normals.size()) {
+ particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_DIRECTED_POINTS);
+ PackedVector2Array norms;
+ norms.resize(valid_normals.size());
+ Vector2 *normsw = norms.ptrw();
+ for (int i = 0; i < valid_normals.size(); i += 1) {
+ normsw[i] = valid_normals[i];
+ }
+ particles->set_emission_normals(norms);
+ } else {
+ particles->set_emission_shape(CPUParticles2D::EMISSION_SHAPE_POINTS);
+ }
+
+ {
+ Vector2 offset;
+ if (emission_mask_centered->is_pressed()) {
+ offset = Vector2(-image_size.width * 0.5, -image_size.height * 0.5);
+ }
+
+ PackedVector2Array points;
+ points.resize(valid_positions.size());
+ Vector2 *pointsw = points.ptrw();
+ for (int i = 0; i < valid_positions.size(); i += 1) {
+ pointsw[i] = valid_positions[i] + offset;
+ }
+ particles->set_emission_points(points);
+ }
+}
+
+CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() {
+ handled_type = "CPUParticles2D"; // TTR("CPUParticles2D")
+ conversion_option_name = TTR("Convert to GPUParticles2D");
+}
+
+// 3D /////////////////////////////////////////////
+
+void Particles3DEditorPlugin::_generate_aabb() {
+ double time = generate_seconds->get_value();
+
+ double running = 0.0;
+
+ EditorProgress ep("gen_aabb", TTR("Generating Visibility AABB (Waiting for Particle Simulation)"), int(time));
+
+ bool was_emitting = edited_node->get("emitting");
+ if (!was_emitting) {
+ edited_node->set("emitting", true);
+ OS::get_singleton()->delay_usec(1000);
+ }
+
+ AABB rect;
+ Callable capture_aabb = Callable(edited_node, "capture_aabb");
+
+ while (running < time) {
+ uint64_t ticks = OS::get_singleton()->get_ticks_usec();
+ ep.step(TTR("Generating..."), int(running), true);
+ OS::get_singleton()->delay_usec(1000);
+
+ AABB capture = capture_aabb.call();
+ if (rect == AABB()) {
+ rect = capture;
+ } else {
+ rect.merge_with(capture);
+ }
+
+ running += (OS::get_singleton()->get_ticks_usec() - ticks) / 1000000.0;
+ }
+
+ if (!was_emitting) {
+ edited_node->set("emitting", false);
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Generate Visibility AABB"));
+ ur->add_do_property(edited_node, "visibility_aabb", rect);
+ ur->add_undo_property(edited_node, "visibility_aabb", edited_node->get("visibility_aabb"));
+ ur->commit_action();
+}
+
+void Particles3DEditorPlugin::_node_selected(const NodePath &p_path) {
+ Node *sel = get_node(p_path);
+ if (!sel) {
+ return;
+ }
+
+ if (!sel->is_class("Node3D")) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't inherit from Node3D."), sel->get_name()));
+ return;
+ }
+
+ MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel);
+ if (!mi || mi->get_mesh().is_null()) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name()));
+ return;
+ }
+
+ geometry = mi->get_mesh()->get_faces();
+ if (geometry.size() == 0) {
+ EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name()));
+ return;
+ }
+
+ Transform3D geom_xform = edited_node->get("global_transform");
+ geom_xform = geom_xform.affine_inverse() * mi->get_global_transform();
+ int gc = geometry.size();
+ Face3 *w = geometry.ptrw();
+
+ for (int i = 0; i < gc; i++) {
+ for (int j = 0; j < 3; j++) {
+ w[i].vertex[j] = geom_xform.xform(w[i].vertex[j]);
+ }
+ }
+ emission_dialog->popup_centered(Size2(300, 130));
+}
+
+void Particles3DEditorPlugin::_menu_callback(int p_idx) {
+ switch (p_idx) {
+ case MENU_OPTION_GENERATE_AABB: {
+ if (need_show_lifetime_dialog(generate_seconds)) {
+ generate_aabb->popup_centered();
+ } else {
+ _generate_aabb();
+ }
+ } break;
+
+ case MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE: {
+ if (_can_generate_points()) {
+ emission_tree_dialog->popup_scenetree_dialog();
+ }
+ } break;
+
+ default: {
+ ParticlesEditorPlugin::_menu_callback(p_idx);
+ }
+ }
+}
+
+void Particles3DEditorPlugin::_add_menu_options(PopupMenu *p_menu) {
+ p_menu->add_item(TTR("Generate AABB"), MENU_OPTION_GENERATE_AABB);
+ p_menu->add_item(TTR("Create Emission Points From Node"), MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE);
+}
+
+bool Particles3DEditorPlugin::_generate(Vector<Vector3> &r_points, Vector<Vector3> &r_normals) {
+ bool use_normals = emission_fill->get_selected() == 1;
+
+ if (emission_fill->get_selected() < 2) {
+ float area_accum = 0;
+ RBMap<float, int> triangle_area_map;
+
+ for (int i = 0; i < geometry.size(); i++) {
+ float area = geometry[i].get_area();
+ if (area < CMP_EPSILON) {
+ continue;
+ }
+ triangle_area_map[area_accum] = i;
+ area_accum += area;
+ }
+
+ if (!triangle_area_map.size() || area_accum == 0) {
+ EditorNode::get_singleton()->show_warning(TTR("The geometry's faces don't contain any area."));
+ return false;
+ }
+
+ int emissor_count = emission_amount->get_value();
+
+ for (int i = 0; i < emissor_count; i++) {
+ float areapos = Math::random(0.0f, area_accum);
+
+ RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos);
+ ERR_FAIL_COND_V(!E, false);
+ int index = E->value;
+ ERR_FAIL_INDEX_V(index, geometry.size(), false);
+
+ // ok FINALLY get face
+ Face3 face = geometry[index];
+ //now compute some position inside the face...
+
+ Vector3 pos = face.get_random_point_inside();
+
+ r_points.push_back(pos);
+
+ if (use_normals) {
+ Vector3 normal = face.get_plane().normal;
+ r_normals.push_back(normal);
+ }
+ }
+ } else {
+ int gcount = geometry.size();
+
+ if (gcount == 0) {
+ EditorNode::get_singleton()->show_warning(TTR("The geometry doesn't contain any faces."));
+ return false;
+ }
+
+ const Face3 *r = geometry.ptr();
+
+ AABB aabb;
+
+ for (int i = 0; i < gcount; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (i == 0 && j == 0) {
+ aabb.position = r[i].vertex[j];
+ } else {
+ aabb.expand_to(r[i].vertex[j]);
+ }
+ }
+ }
+
+ int emissor_count = emission_amount->get_value();
+
+ for (int i = 0; i < emissor_count; i++) {
+ int attempts = 5;
+
+ for (int j = 0; j < attempts; j++) {
+ Vector3 dir;
+ dir[Math::rand() % 3] = 1.0;
+ Vector3 ofs = (Vector3(1, 1, 1) - dir) * Vector3(Math::randf(), Math::randf(), Math::randf()) * aabb.size + aabb.position;
+
+ Vector3 ofsv = ofs + aabb.size * dir;
+
+ //space it a little
+ ofs -= dir;
+ ofsv += dir;
+
+ float max = -1e7, min = 1e7;
+
+ for (int k = 0; k < gcount; k++) {
+ const Face3 &f3 = r[k];
+
+ Vector3 res;
+ if (f3.intersects_segment(ofs, ofsv, &res)) {
+ res -= ofs;
+ float d = dir.dot(res);
+
+ if (d < min) {
+ min = d;
+ }
+ if (d > max) {
+ max = d;
+ }
+ }
+ }
+
+ if (max < min) {
+ continue; //lost attempt
+ }
+
+ float val = min + (max - min) * Math::randf();
+
+ Vector3 point = ofs + dir * val;
+
+ r_points.push_back(point);
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+Particles3DEditorPlugin::Particles3DEditorPlugin() {
+ generate_aabb = memnew(ConfirmationDialog);
+ generate_aabb->set_title(TTR("Generate Visibility AABB"));
+
+ VBoxContainer *genvb = memnew(VBoxContainer);
+ generate_aabb->add_child(genvb);
+
+ generate_seconds = memnew(SpinBox);
+ generate_seconds->set_min(0.1);
+ generate_seconds->set_max(25);
+ generate_seconds->set_value(2);
+ genvb->add_margin_child(TTR("Generation Time (sec):"), generate_seconds);
+
+ EditorNode::get_singleton()->get_gui_base()->add_child(generate_aabb);
+
+ generate_aabb->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_aabb));
+
+ emission_tree_dialog = memnew(SceneTreeDialog);
+ Vector<StringName> valid_types;
+ valid_types.push_back("MeshInstance3D");
+ emission_tree_dialog->set_valid_types(valid_types);
+ EditorNode::get_singleton()->get_gui_base()->add_child(emission_tree_dialog);
+ emission_tree_dialog->connect("selected", callable_mp(this, &Particles3DEditorPlugin::_node_selected));
+
+ emission_dialog = memnew(ConfirmationDialog);
+ emission_dialog->set_title(TTR("Create Emitter"));
+ EditorNode::get_singleton()->get_gui_base()->add_child(emission_dialog);
+
+ VBoxContainer *emd_vb = memnew(VBoxContainer);
+ emission_dialog->add_child(emd_vb);
+
+ emission_amount = memnew(SpinBox);
+ emission_amount->set_min(1);
+ emission_amount->set_max(100000);
+ emission_amount->set_value(512);
+ emd_vb->add_margin_child(TTR("Emission Points:"), emission_amount);
+
+ emission_fill = memnew(OptionButton);
+ emission_fill->add_item(TTR("Surface Points"));
+ emission_fill->add_item(TTR("Surface Points+Normal (Directed)"));
+ emission_fill->add_item(TTR("Volume"));
+ emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill);
+
+ emission_dialog->set_ok_button_text(TTR("Create"));
+ emission_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Particles3DEditorPlugin::_generate_emission_points));
+}
+
+Node *GPUParticles3DEditorPlugin::_convert_particles() {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
+
+ CPUParticles3D *cpu_particles = memnew(CPUParticles3D);
+ cpu_particles->convert_from_particles(particles);
+ cpu_particles->set_name(particles->get_name());
+ cpu_particles->set_transform(particles->get_transform());
+ cpu_particles->set_visible(particles->is_visible());
+ cpu_particles->set_process_mode(particles->get_process_mode());
+ return cpu_particles;
+}
+
+bool GPUParticles3DEditorPlugin::_can_generate_points() const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
+ Ref<ParticleProcessMaterial> mat = particles->get_process_material();
+ if (mat.is_null()) {
+ EditorNode::get_singleton()->show_warning(TTR("A processor material of type 'ParticleProcessMaterial' is required."));
+ return false;
+ }
+ return true;
+}
+
+void GPUParticles3DEditorPlugin::_generate_emission_points() {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(edited_node);
+
+ /// hacer codigo aca
+ Vector<Vector3> points;
+ Vector<Vector3> normals;
+
+ if (!_generate(points, normals)) {
+ return;
+ }
+
+ int point_count = points.size();
+
+ int w = 2048;
+ int h = (point_count / 2048) + 1;
+
+ Vector<uint8_t> point_img;
+ point_img.resize(w * h * 3 * sizeof(float));
+
+ {
+ uint8_t *iw = point_img.ptrw();
+ memset(iw, 0, w * h * 3 * sizeof(float));
+ const Vector3 *r = points.ptr();
+ float *wf = reinterpret_cast<float *>(iw);
+ for (int i = 0; i < point_count; i++) {
+ wf[i * 3 + 0] = r[i].x;
+ wf[i * 3 + 1] = r[i].y;
+ wf[i * 3 + 2] = r[i].z;
+ }
+ }
+
+ Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img));
+ Ref<ImageTexture> tex = ImageTexture::create_from_image(image);
+
+ Ref<ParticleProcessMaterial> mat = particles->get_process_material();
+ ERR_FAIL_COND(mat.is_null());
+
+ if (normals.size() > 0) {
+ mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_DIRECTED_POINTS);
+ mat->set_emission_point_count(point_count);
+ mat->set_emission_point_texture(tex);
+
+ Vector<uint8_t> point_img2;
+ point_img2.resize(w * h * 3 * sizeof(float));
+
+ {
+ uint8_t *iw = point_img2.ptrw();
+ memset(iw, 0, w * h * 3 * sizeof(float));
+ const Vector3 *r = normals.ptr();
+ float *wf = reinterpret_cast<float *>(iw);
+ for (int i = 0; i < point_count; i++) {
+ wf[i * 3 + 0] = r[i].x;
+ wf[i * 3 + 1] = r[i].y;
+ wf[i * 3 + 2] = r[i].z;
+ }
+ }
+
+ Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2));
+ mat->set_emission_normal_texture(ImageTexture::create_from_image(image2));
+ } else {
+ mat->set_emission_shape(ParticleProcessMaterial::EMISSION_SHAPE_POINTS);
+ mat->set_emission_point_count(point_count);
+ mat->set_emission_point_texture(tex);
+ }
+}
+
+GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() {
+ handled_type = "GPUParticles3D"; // TTR("GPUParticles3D")
+ conversion_option_name = TTR("Convert to CPUParticles3D");
+}
+
+Node *CPUParticles3DEditorPlugin::_convert_particles() {
+ CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node);
+
+ GPUParticles3D *gpu_particles = memnew(GPUParticles3D);
+ gpu_particles->convert_from_particles(particles);
+ gpu_particles->set_name(particles->get_name());
+ gpu_particles->set_transform(particles->get_transform());
+ gpu_particles->set_visible(particles->is_visible());
+ gpu_particles->set_process_mode(particles->get_process_mode());
+ return gpu_particles;
+}
+
+void CPUParticles3DEditorPlugin::_generate_emission_points() {
+ CPUParticles3D *particles = Object::cast_to<CPUParticles3D>(edited_node);
+
+ /// hacer codigo aca
+ Vector<Vector3> points;
+ Vector<Vector3> normals;
+
+ if (!_generate(points, normals)) {
+ return;
+ }
+
+ if (normals.is_empty()) {
+ particles->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_POINTS);
+ particles->set_emission_points(points);
+ } else {
+ particles->set_emission_shape(CPUParticles3D::EMISSION_SHAPE_DIRECTED_POINTS);
+ particles->set_emission_points(points);
+ particles->set_emission_normals(normals);
+ }
+}
+
+CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() {
+ handled_type = "CPUParticles3D"; // TTR("CPUParticles3D")
+ conversion_option_name = TTR("Convert to GPUParticles3D");
+}
diff --git a/editor/plugins/particles_editor_plugin.h b/editor/plugins/particles_editor_plugin.h
new file mode 100644
index 0000000000..d4459765c4
--- /dev/null
+++ b/editor/plugins/particles_editor_plugin.h
@@ -0,0 +1,216 @@
+/**************************************************************************/
+/* particles_editor_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef PARTICLES_EDITOR_PLUGIN_H
+#define PARTICLES_EDITOR_PLUGIN_H
+
+#include "editor/plugins/editor_plugin.h"
+
+class CheckBox;
+class ConfirmationDialog;
+class EditorFileDialog;
+class GPUParticles2D;
+class HBoxContainer;
+class MenuButton;
+class OptionButton;
+class SceneTreeDialog;
+class SpinBox;
+
+class ParticlesEditorPlugin : public EditorPlugin {
+ GDCLASS(ParticlesEditorPlugin, EditorPlugin);
+
+private:
+ enum {
+ MENU_OPTION_CONVERT,
+ MENU_RESTART
+ };
+
+ HBoxContainer *toolbar = nullptr;
+ MenuButton *menu = nullptr;
+
+protected:
+ String handled_type;
+ String conversion_option_name;
+
+ Node *edited_node = nullptr;
+
+ void _notification(int p_what);
+
+ bool need_show_lifetime_dialog(SpinBox *p_seconds);
+ virtual void _menu_callback(int p_idx);
+
+ virtual void _add_menu_options(PopupMenu *p_menu) {}
+ virtual Node *_convert_particles() = 0;
+
+public:
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ ParticlesEditorPlugin();
+};
+
+// 2D /////////////////////////////////////////////
+
+class Particles2DEditorPlugin : public ParticlesEditorPlugin {
+ GDCLASS(Particles2DEditorPlugin, ParticlesEditorPlugin);
+
+protected:
+ enum {
+ MENU_LOAD_EMISSION_MASK = 100,
+ };
+
+ enum EmissionMode {
+ EMISSION_MODE_SOLID,
+ EMISSION_MODE_BORDER,
+ EMISSION_MODE_BORDER_DIRECTED
+ };
+
+ EditorFileDialog *file = nullptr;
+ ConfirmationDialog *emission_mask = nullptr;
+ OptionButton *emission_mask_mode = nullptr;
+ CheckBox *emission_mask_centered = nullptr;
+ CheckBox *emission_colors = nullptr;
+ String source_emission_file;
+
+ virtual void _menu_callback(int p_idx) override;
+ virtual void _add_menu_options(PopupMenu *p_menu) override;
+
+ void _file_selected(const String &p_file);
+ void _get_base_emission_mask(PackedVector2Array &r_valid_positions, PackedVector2Array &r_valid_normals, PackedByteArray &r_valid_colors, Vector2i &r_image_size);
+ virtual void _generate_emission_mask() = 0;
+
+public:
+ Particles2DEditorPlugin();
+};
+
+class GPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
+ GDCLASS(GPUParticles2DEditorPlugin, Particles2DEditorPlugin);
+
+ enum {
+ MENU_GENERATE_VISIBILITY_RECT = 200,
+ };
+
+ List<GPUParticles2D *> selected_particles;
+
+ ConfirmationDialog *generate_visibility_rect = nullptr;
+ SpinBox *generate_seconds = nullptr;
+
+ void _selection_changed();
+ void _generate_visibility_rect();
+
+protected:
+ void _notification(int p_what);
+
+ void _menu_callback(int p_idx) override;
+ void _add_menu_options(PopupMenu *p_menu) override;
+
+ Node *_convert_particles() override;
+
+ void _generate_emission_mask() override;
+
+public:
+ GPUParticles2DEditorPlugin();
+};
+
+class CPUParticles2DEditorPlugin : public Particles2DEditorPlugin {
+ GDCLASS(CPUParticles2DEditorPlugin, Particles2DEditorPlugin);
+
+protected:
+ Node *_convert_particles() override;
+
+ void _generate_emission_mask() override;
+
+public:
+ CPUParticles2DEditorPlugin();
+};
+
+// 3D /////////////////////////////////////////////
+
+class Particles3DEditorPlugin : public ParticlesEditorPlugin {
+ GDCLASS(Particles3DEditorPlugin, ParticlesEditorPlugin);
+
+ enum {
+ MENU_OPTION_GENERATE_AABB = 300,
+ MENU_OPTION_CREATE_EMISSION_VOLUME_FROM_NODE,
+ };
+
+ ConfirmationDialog *generate_aabb = nullptr;
+ SpinBox *generate_seconds = nullptr;
+
+ SceneTreeDialog *emission_tree_dialog = nullptr;
+ ConfirmationDialog *emission_dialog = nullptr;
+ SpinBox *emission_amount = nullptr;
+ OptionButton *emission_fill = nullptr;
+
+ void _generate_aabb();
+ void _node_selected(const NodePath &p_path);
+
+protected:
+ Vector<Face3> geometry;
+
+ virtual void _menu_callback(int p_idx) override;
+ virtual void _add_menu_options(PopupMenu *p_menu) override;
+
+ bool _generate(Vector<Vector3> &r_points, Vector<Vector3> &r_normals);
+ virtual bool _can_generate_points() const = 0;
+ virtual void _generate_emission_points() = 0;
+
+public:
+ Particles3DEditorPlugin();
+};
+
+class GPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
+ GDCLASS(GPUParticles3DEditorPlugin, Particles3DEditorPlugin);
+
+protected:
+ Node *_convert_particles() override;
+
+ bool _can_generate_points() const override;
+ void _generate_emission_points() override;
+
+public:
+ GPUParticles3DEditorPlugin();
+};
+
+class CPUParticles3DEditorPlugin : public Particles3DEditorPlugin {
+ GDCLASS(CPUParticles3DEditorPlugin, Particles3DEditorPlugin);
+
+protected:
+ Node *_convert_particles() override;
+
+ bool _can_generate_points() const override { return true; }
+ void _generate_emission_points() override;
+
+public:
+ CPUParticles3DEditorPlugin();
+};
+
+#endif // PARTICLES_EDITOR_PLUGIN_H
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 54730ec674..7e0331d15c 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -2798,6 +2798,8 @@ void ScriptEditor::_reload_scripts(bool p_refresh_only) {
scr->set_source_code(rel_scr->get_source_code());
scr->set_last_modified_time(rel_scr->get_last_modified_time());
scr->reload(true);
+
+ update_docs_from_script(scr);
}
Ref<JSON> json = edited_res;
@@ -3644,11 +3646,9 @@ void ScriptEditor::update_doc(const String &p_name) {
void ScriptEditor::clear_docs_from_script(const Ref<Script> &p_script) {
ERR_FAIL_COND(p_script.is_null());
- Vector<DocData::ClassDoc> documentations = p_script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- if (EditorHelp::get_doc_data()->has_doc(doc.name)) {
- EditorHelp::get_doc_data()->remove_doc(doc.name);
+ for (const DocData::ClassDoc &cd : p_script->get_documentation()) {
+ if (EditorHelp::get_doc_data()->has_doc(cd.name)) {
+ EditorHelp::get_doc_data()->remove_doc(cd.name);
}
}
}
@@ -3656,11 +3656,9 @@ void ScriptEditor::clear_docs_from_script(const Ref<Script> &p_script) {
void ScriptEditor::update_docs_from_script(const Ref<Script> &p_script) {
ERR_FAIL_COND(p_script.is_null());
- Vector<DocData::ClassDoc> documentations = p_script->get_documentation();
- for (int j = 0; j < documentations.size(); j++) {
- const DocData::ClassDoc &doc = documentations.get(j);
- EditorHelp::get_doc_data()->add_doc(doc);
- update_doc(doc.name);
+ for (const DocData::ClassDoc &cd : p_script->get_documentation()) {
+ EditorHelp::get_doc_data()->add_doc(cd);
+ update_doc(cd.name);
}
}
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 8f646a7621..cc488ff340 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -3596,6 +3596,13 @@ void ThemeEditor::_theme_close_button_cbk() {
}
}
+void ThemeEditor::_scene_closed(const String &p_path) {
+ if (theme.is_valid() && theme->is_built_in() && theme->get_path().get_slice("::", 0) == p_path) {
+ theme = Ref<Theme>();
+ EditorNode::get_singleton()->hide_unused_editors(plugin);
+ }
+}
+
void ThemeEditor::_add_preview_button_cbk() {
preview_scene_dialog->popup_file_dialog();
}
@@ -3679,7 +3686,10 @@ void ThemeEditor::_preview_control_picked(String p_class_name) {
void ThemeEditor::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_READY: {
+ EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ThemeEditor::_scene_closed));
+ } break;
+
case NOTIFICATION_THEME_CHANGED: {
preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox(SNAME("ThemeEditorPreviewFG"), EditorStringName(EditorStyles)));
preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox(SNAME("ThemeEditorPreviewBG"), EditorStringName(EditorStyles)));
@@ -3807,71 +3817,7 @@ void ThemeEditorPlugin::make_visible(bool p_visible) {
}
bool ThemeEditorPlugin::can_auto_hide() const {
- Ref<Theme> edited_theme = theme_editor->theme;
- if (edited_theme.is_null()) {
- return true;
- }
-
- Ref<Resource> edited_resource = Ref<Resource>(InspectorDock::get_inspector_singleton()->get_next_edited_object());
- if (edited_resource.is_null()) {
- return true;
- }
-
- // Don't hide if edited resource used by this theme.
- Ref<StyleBox> sbox = edited_resource;
- if (sbox.is_valid()) {
- List<StringName> type_list;
- edited_theme->get_stylebox_type_list(&type_list);
-
- for (const StringName &E : type_list) {
- List<StringName> list;
- edited_theme->get_stylebox_list(E, &list);
-
- for (const StringName &F : list) {
- if (edited_theme->get_stylebox(F, E) == sbox) {
- return false;
- }
- }
- }
- return true;
- }
-
- Ref<Texture2D> tex = edited_resource;
- if (tex.is_valid()) {
- List<StringName> type_list;
- edited_theme->get_icon_type_list(&type_list);
-
- for (const StringName &E : type_list) {
- List<StringName> list;
- edited_theme->get_icon_list(E, &list);
-
- for (const StringName &F : list) {
- if (edited_theme->get_icon(F, E) == tex) {
- return false;
- }
- }
- }
- return true;
- }
-
- Ref<Font> fnt = edited_resource;
- if (fnt.is_valid()) {
- List<StringName> type_list;
- edited_theme->get_font_type_list(&type_list);
-
- for (const StringName &E : type_list) {
- List<StringName> list;
- edited_theme->get_font_list(E, &list);
-
- for (const StringName &F : list) {
- if (edited_theme->get_font(F, E) == fnt) {
- return false;
- }
- }
- }
- return true;
- }
- return true;
+ return theme_editor->theme.is_null();
}
ThemeEditorPlugin::ThemeEditorPlugin() {
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index 1d009637b7..39dc8d154b 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -445,6 +445,7 @@ class ThemeEditor : public VBoxContainer {
void _theme_save_button_cbk(bool p_save_as);
void _theme_edit_button_cbk();
void _theme_close_button_cbk();
+ void _scene_closed(const String &p_path);
void _add_preview_button_cbk();
void _preview_scene_dialog_cbk(const String &p_path);
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index 12b9761fd2..c6921699a4 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -874,6 +874,11 @@ void GenericTilePolygonEditor::_notification(int p_what) {
button_expand->set_pressed_no_signal(false);
}
} break;
+
+ case NOTIFICATION_READY: {
+ get_parent()->connect(SceneStringName(tree_exited), callable_mp(TileSetEditor::get_singleton(), &TileSetEditor::remove_expanded_editor));
+ } break;
+
case NOTIFICATION_THEME_CHANGED: {
button_expand->set_icon(get_editor_theme_icon(SNAME("DistractionFree")));
button_create->set_icon(get_editor_theme_icon(SNAME("CurveCreate")));
diff --git a/editor/plugins/tool_button_editor_plugin.cpp b/editor/plugins/tool_button_editor_plugin.cpp
new file mode 100644
index 0000000000..d9852c8694
--- /dev/null
+++ b/editor/plugins/tool_button_editor_plugin.cpp
@@ -0,0 +1,82 @@
+/**************************************************************************/
+/* tool_button_editor_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "tool_button_editor_plugin.h"
+
+#include "scene/gui/button.h"
+
+void EditorInspectorToolButtonPlugin::_update_action_icon(Button *p_action_button, const String &p_action_icon) {
+ p_action_button->set_icon(p_action_button->get_editor_theme_icon(p_action_icon));
+}
+
+void EditorInspectorToolButtonPlugin::_call_action(const Variant &p_object, const StringName &p_property) {
+ Object *object = p_object.get_validated_object();
+ ERR_FAIL_NULL_MSG(object, vformat(R"(Failed to get property "%s" on a previously freed instance.)", p_property));
+
+ const Variant value = object->get(p_property);
+ ERR_FAIL_COND_MSG(value.get_type() != Variant::CALLABLE, vformat(R"(The value of property "%s" is %s, but Callable was expected.)", p_property, Variant::get_type_name(value.get_type())));
+
+ const Callable callable = value;
+ ERR_FAIL_COND_MSG(!callable.is_valid(), vformat(R"(Tool button action "%s" is an invalid callable.)", callable));
+
+ Variant ret;
+ Callable::CallError ce;
+ callable.callp(nullptr, 0, ret, ce);
+ ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, vformat(R"(Error calling tool button action "%s": %s)", callable, Variant::get_call_error_text(callable.get_method(), nullptr, 0, ce)));
+}
+
+bool EditorInspectorToolButtonPlugin::can_handle(Object *p_object) {
+ return true;
+}
+
+bool EditorInspectorToolButtonPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
+ if (p_type != Variant::CALLABLE || p_hint != PROPERTY_HINT_TOOL_BUTTON || !p_usage.has_flag(PROPERTY_USAGE_EDITOR)) {
+ return false;
+ }
+
+ const PackedStringArray splits = p_hint_text.rsplit(",", true, 1);
+ const String &hint_text = splits[0]; // Safe since `splits` cannot be empty.
+ const String &hint_icon = splits.size() > 1 ? splits[1] : "Callable";
+
+ Button *action_button = EditorInspector::create_inspector_action_button(hint_text);
+ action_button->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED);
+ action_button->set_disabled(p_usage & PROPERTY_USAGE_READ_ONLY);
+ action_button->connect(SceneStringName(theme_changed), callable_mp(this, &EditorInspectorToolButtonPlugin::_update_action_icon).bind(action_button, hint_icon));
+ action_button->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorToolButtonPlugin::_call_action).bind(p_object, p_path));
+
+ add_custom_control(action_button);
+ return true;
+}
+
+ToolButtonEditorPlugin::ToolButtonEditorPlugin() {
+ Ref<EditorInspectorToolButtonPlugin> plugin;
+ plugin.instantiate();
+ add_inspector_plugin(plugin);
+}
diff --git a/editor/plugins/tool_button_editor_plugin.h b/editor/plugins/tool_button_editor_plugin.h
new file mode 100644
index 0000000000..2d185c3a8f
--- /dev/null
+++ b/editor/plugins/tool_button_editor_plugin.h
@@ -0,0 +1,57 @@
+/**************************************************************************/
+/* tool_button_editor_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TOOL_BUTTON_EDITOR_PLUGIN_H
+#define TOOL_BUTTON_EDITOR_PLUGIN_H
+
+#include "editor/editor_inspector.h"
+#include "editor/plugins/editor_plugin.h"
+
+class EditorInspectorToolButtonPlugin : public EditorInspectorPlugin {
+ GDCLASS(EditorInspectorToolButtonPlugin, EditorInspectorPlugin);
+
+ void _update_action_icon(Button *p_action_button, const String &p_action_icon);
+ void _call_action(const Variant &p_object, const StringName &p_property);
+
+public:
+ virtual bool can_handle(Object *p_object) override;
+ virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide = false) override;
+};
+
+class ToolButtonEditorPlugin : public EditorPlugin {
+ GDCLASS(ToolButtonEditorPlugin, EditorPlugin);
+
+public:
+ virtual String get_name() const override { return "ToolButtonEditorPlugin"; }
+
+ ToolButtonEditorPlugin();
+};
+
+#endif // TOOL_BUTTON_EDITOR_PLUGIN_H
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index f98a30ebb3..ede8351e41 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -6300,6 +6300,7 @@ VisualShaderEditor::VisualShaderEditor() {
graph->connect("scroll_offset_changed", callable_mp(this, &VisualShaderEditor::_scroll_changed));
graph->connect("duplicate_nodes_request", callable_mp(this, &VisualShaderEditor::_duplicate_nodes));
graph->connect("copy_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes).bind(false));
+ graph->connect("cut_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes).bind(true));
graph->connect("paste_nodes_request", callable_mp(this, &VisualShaderEditor::_paste_nodes).bind(false, Point2()));
graph->connect("delete_nodes_request", callable_mp(this, &VisualShaderEditor::_delete_nodes_request));
graph->connect(SceneStringName(gui_input), callable_mp(this, &VisualShaderEditor::_graph_gui_input));
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 8411c0edea..30878a2488 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -38,7 +38,6 @@
#include "core/io/stream_peer_tls.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
-#include "core/os/time.h"
#include "core/version.h"
#include "editor/editor_about.h"
#include "editor/editor_settings.h"
@@ -46,6 +45,7 @@
#include "editor/engine_update_label.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_title_bar.h"
+#include "editor/gui/editor_version_button.h"
#include "editor/plugins/asset_library_editor_plugin.h"
#include "editor/project_manager/project_dialog.h"
#include "editor/project_manager/project_list.h"
@@ -398,12 +398,6 @@ void ProjectManager::_restart_confirmed() {
get_tree()->quit();
}
-// Footer.
-
-void ProjectManager::_version_button_pressed() {
- DisplayServer::get_singleton()->clipboard_set(version_btn->get_text());
-}
-
// Project list.
void ProjectManager::_update_list_placeholder() {
@@ -1459,23 +1453,9 @@ ProjectManager::ProjectManager() {
update_label->connect("offline_clicked", callable_mp(this, &ProjectManager::_show_quick_settings));
#endif
- version_btn = memnew(LinkButton);
- String hash = String(VERSION_HASH);
- if (hash.length() != 0) {
- hash = " " + vformat("[%s]", hash.left(9));
- }
- version_btn->set_text("v" VERSION_FULL_BUILD + hash);
+ EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_BUILD));
// Fade the version label to be less prominent, but still readable.
version_btn->set_self_modulate(Color(1, 1, 1, 0.6));
- version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
- String build_date;
- if (VERSION_TIMESTAMP > 0) {
- build_date = Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC";
- } else {
- build_date = TTR("(unknown)");
- }
- version_btn->set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version information."), build_date));
- version_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_version_button_pressed));
footer_bar->add_child(version_btn);
}
diff --git a/editor/project_manager.h b/editor/project_manager.h
index aad51d0e98..07da0059c0 100644
--- a/editor/project_manager.h
+++ b/editor/project_manager.h
@@ -41,7 +41,6 @@ class EditorFileDialog;
class EditorTitleBar;
class HFlowContainer;
class LineEdit;
-class LinkButton;
class MarginContainer;
class OptionButton;
class PanelContainer;
@@ -124,12 +123,6 @@ class ProjectManager : public Control {
void _show_quick_settings();
void _restart_confirmed();
- // Footer.
-
- LinkButton *version_btn = nullptr;
-
- void _version_button_pressed();
-
// Project list.
VBoxContainer *empty_list_placeholder = nullptr;
diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp
index 01868846bf..7aadb9ac3c 100644
--- a/editor/project_manager/project_dialog.cpp
+++ b/editor/project_manager/project_dialog.cpp
@@ -552,6 +552,21 @@ void ProjectDialog::ok_pressed() {
fa_icon->store_string(get_default_project_icon());
EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), path);
+
+ // Ensures external editors and IDEs use UTF-8 encoding.
+ const String editor_config_path = path.path_join(".editorconfig");
+ Ref<FileAccess> f = FileAccess::open(editor_config_path, FileAccess::WRITE);
+ if (f.is_null()) {
+ // .editorconfig isn't so critical.
+ ERR_PRINT("Couldn't create .editorconfig in project path.");
+ } else {
+ f->store_line("root = true");
+ f->store_line("");
+ f->store_line("[*]");
+ f->store_line("charset = utf-8");
+ f->close();
+ FileAccess::set_hidden_attribute(editor_config_path, true);
+ }
}
// Two cases for importing a ZIP.
@@ -986,7 +1001,7 @@ ProjectDialog::ProjectDialog() {
rvb->add_child(renderer_info);
rd_not_supported = memnew(Label);
- rd_not_supported->set_text(TTR("Rendering Device backend not available. Please use the Compatibility renderer."));
+ rd_not_supported->set_text(vformat(TTR("RenderingDevice-based methods not available on this GPU:\n%s\nPlease use the Compatibility renderer."), RenderingServer::get_singleton()->get_video_adapter_name()));
rd_not_supported->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
rd_not_supported->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
rd_not_supported->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index 5767293718..f973367bed 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -104,7 +104,8 @@ void ProjectSettingsEditor::_update_advanced(bool p_is_advanced) {
}
void ProjectSettingsEditor::_advanced_toggled(bool p_button_pressed) {
- EditorSettings::get_singleton()->set_project_metadata("project_settings", "advanced_mode", p_button_pressed);
+ EditorSettings::get_singleton()->set("_project_settings_advanced_mode", p_button_pressed);
+ EditorSettings::get_singleton()->save();
_update_advanced(p_button_pressed);
}
@@ -389,7 +390,7 @@ void ProjectSettingsEditor::_action_added(const String &p_name) {
Dictionary action;
action["events"] = Array();
- action["deadzone"] = 0.5f;
+ action["deadzone"] = 0.2f;
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Input Action"));
@@ -768,8 +769,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
set_ok_button_text(TTR("Close"));
set_hide_on_ok(true);
- bool use_advanced = EditorSettings::get_singleton()->get_project_metadata("project_settings", "advanced_mode", false);
-
+ bool use_advanced = EDITOR_DEF("_project_settings_advanced_mode", false);
if (use_advanced) {
advanced->set_pressed(true);
}
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 00377a0dd2..c4ebca7308 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -76,15 +76,11 @@
#include "editor/plugins/collision_polygon_2d_editor_plugin.h"
#include "editor/plugins/collision_shape_2d_editor_plugin.h"
#include "editor/plugins/control_editor_plugin.h"
-#include "editor/plugins/cpu_particles_2d_editor_plugin.h"
-#include "editor/plugins/cpu_particles_3d_editor_plugin.h"
#include "editor/plugins/curve_editor_plugin.h"
#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_tooltip_plugins.h"
#include "editor/plugins/font_config_plugin.h"
-#include "editor/plugins/gpu_particles_2d_editor_plugin.h"
-#include "editor/plugins/gpu_particles_3d_editor_plugin.h"
#include "editor/plugins/gpu_particles_collision_sdf_editor_plugin.h"
#include "editor/plugins/gradient_editor_plugin.h"
#include "editor/plugins/gradient_texture_2d_editor_plugin.h"
@@ -105,6 +101,7 @@
#include "editor/plugins/occluder_instance_3d_editor_plugin.h"
#include "editor/plugins/packed_scene_editor_plugin.h"
#include "editor/plugins/parallax_background_editor_plugin.h"
+#include "editor/plugins/particles_editor_plugin.h"
#include "editor/plugins/path_2d_editor_plugin.h"
#include "editor/plugins/path_3d_editor_plugin.h"
#include "editor/plugins/physical_bone_3d_editor_plugin.h"
@@ -127,6 +124,7 @@
#include "editor/plugins/texture_region_editor_plugin.h"
#include "editor/plugins/theme_editor_plugin.h"
#include "editor/plugins/tiles/tiles_editor_plugin.h"
+#include "editor/plugins/tool_button_editor_plugin.h"
#include "editor/plugins/version_control_editor_plugin.h"
#include "editor/plugins/visual_shader_editor_plugin.h"
#include "editor/plugins/voxel_gi_editor_plugin.h"
@@ -247,6 +245,7 @@ void register_editor_types() {
EditorPlugins::add_by_type<TextureLayeredEditorPlugin>();
EditorPlugins::add_by_type<TextureRegionEditorPlugin>();
EditorPlugins::add_by_type<ThemeEditorPlugin>();
+ EditorPlugins::add_by_type<ToolButtonEditorPlugin>();
EditorPlugins::add_by_type<VoxelGIEditorPlugin>();
// 2D
diff --git a/editor/renames_map_3_to_4.cpp b/editor/renames_map_3_to_4.cpp
index ae7c86a5e5..ca5d3cbf2a 100644
--- a/editor/renames_map_3_to_4.cpp
+++ b/editor/renames_map_3_to_4.cpp
@@ -1354,7 +1354,6 @@ const char *RenamesMap3To4::project_settings_renames[][2] = {
{ "rendering/quality/shading/force_lambert_over_burley", "rendering/shading/overrides/force_lambert_over_burley" },
{ "rendering/quality/shading/force_lambert_over_burley.mobile", "rendering/shading/overrides/force_lambert_over_burley.mobile" },
{ "rendering/quality/shading/force_vertex_shading", "rendering/shading/overrides/force_vertex_shading" },
- { "rendering/quality/shading/force_vertex_shading.mobile", "rendering/shading/overrides/force_vertex_shading.mobile" },
{ "rendering/quality/shadow_atlas/quadrant_0_subdiv", "rendering/lights_and_shadows/shadow_atlas/quadrant_0_subdiv" },
{ "rendering/quality/shadow_atlas/quadrant_1_subdiv", "rendering/lights_and_shadows/shadow_atlas/quadrant_1_subdiv" },
{ "rendering/quality/shadow_atlas/quadrant_2_subdiv", "rendering/lights_and_shadows/shadow_atlas/quadrant_2_subdiv" },
@@ -1400,7 +1399,6 @@ const char *RenamesMap3To4::project_godot_renames[][2] = {
{ "quality/shading/force_lambert_over_burley", "shading/overrides/force_lambert_over_burley" },
{ "quality/shading/force_lambert_over_burley.mobile", "shading/overrides/force_lambert_over_burley.mobile" },
{ "quality/shading/force_vertex_shading", "shading/overrides/force_vertex_shading" },
- { "quality/shading/force_vertex_shading.mobile", "shading/overrides/force_vertex_shading.mobile" },
{ "quality/shadow_atlas/quadrant_0_subdiv", "lights_and_shadows/shadow_atlas/quadrant_0_subdiv" },
{ "quality/shadow_atlas/quadrant_1_subdiv", "lights_and_shadows/shadow_atlas/quadrant_1_subdiv" },
{ "quality/shadow_atlas/quadrant_2_subdiv", "lights_and_shadows/shadow_atlas/quadrant_2_subdiv" },
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 7187da851e..bcab0c2883 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -37,15 +37,16 @@
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_feature_profile.h"
+#include "editor/editor_file_system.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
-#include "editor/editor_quick_open.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/inspector_dock.h"
#include "editor/multi_node_edit.h"
#include "editor/node_dock.h"
@@ -73,8 +74,8 @@ void SceneTreeDock::_nodes_drag_begin() {
pending_click_select = nullptr;
}
-void SceneTreeDock::_quick_open() {
- instantiate_scenes(quick_open->get_selected_files(), scene_tree->get_selected());
+void SceneTreeDock::_quick_open(const String &p_file_path) {
+ instantiate_scenes({ p_file_path }, scene_tree->get_selected());
}
void SceneTreeDock::_inspect_hovered_node() {
@@ -88,6 +89,8 @@ void SceneTreeDock::_inspect_hovered_node() {
tree_item_inspected = item;
tree_item_inspected->set_custom_color(0, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
}
+ EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
+ editor_history->add_object(node_hovered_now->get_instance_id());
InspectorDock::get_inspector_singleton()->edit(node_hovered_now);
InspectorDock::get_inspector_singleton()->propagate_notification(NOTIFICATION_DRAG_BEGIN); // Enable inspector drag preview after it updated.
InspectorDock::get_singleton()->update(node_hovered_now);
@@ -133,14 +136,6 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) {
_push_item(pending_click_select);
pending_click_select = nullptr;
}
-
- if (mb->is_released()) {
- if (tree_item_inspected) {
- tree_item_inspected->clear_custom_color(0);
- tree_item_inspected = nullptr;
- }
- _reset_hovering_timer();
- }
}
if (tree_clicked && get_viewport()->gui_is_dragging()) {
@@ -436,6 +431,22 @@ void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base)
instantiated_scene->set_unique_name_in_owner(base->is_unique_name_in_owner());
+ Node2D *copy_2d = Object::cast_to<Node2D>(instantiated_scene);
+ Node2D *base_2d = Object::cast_to<Node2D>(base);
+ if (copy_2d && base_2d) {
+ copy_2d->set_position(base_2d->get_position());
+ copy_2d->set_rotation(base_2d->get_rotation());
+ copy_2d->set_scale(base_2d->get_scale());
+ }
+
+ Node3D *copy_3d = Object::cast_to<Node3D>(instantiated_scene);
+ Node3D *base_3d = Object::cast_to<Node3D>(base);
+ if (copy_3d && base_3d) {
+ copy_3d->set_position(base_3d->get_position());
+ copy_3d->set_rotation(base_3d->get_rotation());
+ copy_3d->set_scale(base_3d->get_scale());
+ }
+
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Replace with Branch Scene"));
@@ -599,8 +610,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
break;
}
- quick_open->popup_dialog("PackedScene", true);
- quick_open->set_title(TTR("Instantiate Child Scene"));
+ EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &SceneTreeDock::_quick_open));
if (!p_confirm_override) {
emit_signal(SNAME("add_node_used"));
}
@@ -1702,13 +1712,30 @@ void SceneTreeDock::_notification(int p_what) {
case NOTIFICATION_DRAG_END: {
_reset_hovering_timer();
- if (select_node_hovered_at_end_of_drag && !hovered_but_reparenting) {
- Node *node_inspected = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
- if (node_inspected) {
+ if (tree_item_inspected) {
+ tree_item_inspected->clear_custom_color(0);
+ tree_item_inspected = nullptr;
+ } else {
+ return;
+ }
+ if (!hovered_but_reparenting) {
+ InspectorDock *inspector_dock = InspectorDock::get_singleton();
+ if (!inspector_dock->get_rect().has_point(inspector_dock->get_local_mouse_position())) {
+ List<Node *> full_selection = editor_selection->get_full_selected_node_list();
editor_selection->clear();
- editor_selection->add_node(node_inspected);
- scene_tree->set_selected(node_inspected);
- select_node_hovered_at_end_of_drag = false;
+ for (Node *E : full_selection) {
+ editor_selection->add_node(E);
+ }
+ return;
+ }
+ if (select_node_hovered_at_end_of_drag) {
+ Node *node_inspected = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
+ if (node_inspected) {
+ editor_selection->clear();
+ editor_selection->add_node(node_inspected);
+ scene_tree->set_selected(node_inspected);
+ select_node_hovered_at_end_of_drag = false;
+ }
}
}
hovered_but_reparenting = false;
@@ -3259,6 +3286,36 @@ void SceneTreeDock::_new_scene_from(const String &p_file) {
// Root node cannot ever be unique name in its own Scene!
copy->set_unique_name_in_owner(false);
+ const Dictionary dict = new_scene_from_dialog->get_selected_options();
+ bool reset_position = dict.get(TTR("Reset Position"), true);
+ bool reset_scale = dict.get(TTR("Reset Scale"), false);
+ bool reset_rotation = dict.get(TTR("Reset Rotation"), false);
+
+ Node2D *copy_2d = Object::cast_to<Node2D>(copy);
+ if (copy_2d != nullptr) {
+ if (reset_position) {
+ copy_2d->set_position(Vector2(0, 0));
+ }
+ if (reset_rotation) {
+ copy_2d->set_rotation(0);
+ }
+ if (reset_scale) {
+ copy_2d->set_scale(Size2(1, 1));
+ }
+ }
+ Node3D *copy_3d = Object::cast_to<Node3D>(copy);
+ if (copy_3d != nullptr) {
+ if (reset_position) {
+ copy_3d->set_position(Vector3(0, 0, 0));
+ }
+ if (reset_rotation) {
+ copy_3d->set_rotation(Vector3(0, 0, 0));
+ }
+ if (reset_scale) {
+ copy_3d->set_scale(Vector3(0, 0, 0));
+ }
+ }
+
Ref<PackedScene> sdata = memnew(PackedScene);
Error err = sdata->pack(copy);
memdelete(copy);
@@ -4598,7 +4655,6 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
scene_tree->connect("files_dropped", callable_mp(this, &SceneTreeDock::_files_dropped));
scene_tree->connect("script_dropped", callable_mp(this, &SceneTreeDock::_script_dropped));
scene_tree->connect("nodes_dragged", callable_mp(this, &SceneTreeDock::_nodes_drag_begin));
- scene_tree->connect(SceneStringName(mouse_exited), callable_mp(this, &SceneTreeDock::_reset_hovering_timer));
scene_tree->get_scene_tree()->connect(SceneStringName(gui_input), callable_mp(this, &SceneTreeDock::_scene_tree_gui_input));
scene_tree->get_scene_tree()->connect("item_icon_double_clicked", callable_mp(this, &SceneTreeDock::_focus_node));
@@ -4638,10 +4694,6 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
accept = memnew(AcceptDialog);
add_child(accept);
- quick_open = memnew(EditorQuickOpen);
- add_child(quick_open);
- quick_open->connect("quick_open", callable_mp(this, &SceneTreeDock::_quick_open));
-
set_process_shortcut_input(true);
delete_dialog = memnew(ConfirmationDialog);
@@ -4668,6 +4720,9 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
new_scene_from_dialog = memnew(EditorFileDialog);
new_scene_from_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+ new_scene_from_dialog->add_option(TTR("Reset Position"), Vector<String>(), true);
+ new_scene_from_dialog->add_option(TTR("Reset Rotation"), Vector<String>(), false);
+ new_scene_from_dialog->add_option(TTR("Reset Scale"), Vector<String>(), false);
add_child(new_scene_from_dialog);
new_scene_from_dialog->connect("file_selected", callable_mp(this, &SceneTreeDock::_new_scene_from));
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index d0cdaf8dd1..05ad0f36e4 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -39,7 +39,6 @@
class CheckBox;
class EditorData;
class EditorSelection;
-class EditorQuickOpen;
class MenuButton;
class ReparentDialog;
class ShaderCreateDialog;
@@ -159,7 +158,6 @@ class SceneTreeDock : public VBoxContainer {
ConfirmationDialog *placeholder_editable_instance_remove_dialog = nullptr;
ReparentDialog *reparent_dialog = nullptr;
- EditorQuickOpen *quick_open = nullptr;
EditorFileDialog *new_scene_from_dialog = nullptr;
enum FilterMenuItems {
@@ -267,7 +265,7 @@ class SceneTreeDock : public VBoxContainer {
void _nodes_dragged(const Array &p_nodes, NodePath p_to, int p_type);
void _files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type);
void _script_dropped(const String &p_file, NodePath p_to);
- void _quick_open();
+ void _quick_open(const String &p_file_path);
void _tree_rmb(const Vector2 &p_menu_pos);
void _update_tree_menu();
diff --git a/editor/themes/editor_fonts.cpp b/editor/themes/editor_fonts.cpp
index c50d1237e0..da35ed332e 100644
--- a/editor/themes/editor_fonts.cpp
+++ b/editor/themes/editor_fonts.cpp
@@ -156,6 +156,18 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
Ref<Font> default_font = load_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false);
Ref<Font> default_font_msdf = load_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, font_allow_msdf);
+ String noto_cjk_path;
+ String noto_cjk_bold_path;
+ String var_suffix[] = { "HK", "KR", "SC", "TC", "JP" }; // Note: All Noto Sans CJK versions support all glyph variations, it should not match current locale.
+ for (size_t i = 0; i < sizeof(var_suffix) / sizeof(String); i++) {
+ if (noto_cjk_path.is_empty()) {
+ noto_cjk_path = OS::get_singleton()->get_system_font_path("Noto Sans CJK " + var_suffix[i], 400, 100);
+ }
+ if (noto_cjk_bold_path.is_empty()) {
+ noto_cjk_bold_path = OS::get_singleton()->get_system_font_path("Noto Sans CJK " + var_suffix[i], 800, 100);
+ }
+ }
+
TypedArray<Font> fallbacks;
Ref<FontFile> arabic_font = load_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
Ref<FontFile> bengali_font = load_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
@@ -168,6 +180,9 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
Ref<FontFile> tamil_font = load_internal_font(_font_NotoSansTamilUI_Regular, _font_NotoSansTamilUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
Ref<FontFile> telugu_font = load_internal_font(_font_NotoSansTeluguUI_Regular, _font_NotoSansTeluguUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
Ref<FontFile> thai_font = load_internal_font(_font_NotoSansThai_Regular, _font_NotoSansThai_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
+ if (!noto_cjk_path.is_empty()) {
+ load_external_font(noto_cjk_path, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
+ }
Ref<FontFile> fallback_font = load_internal_font(_font_DroidSansFallback, _font_DroidSansFallback_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
Ref<FontFile> japanese_font = load_internal_font(_font_DroidSansJapanese, _font_DroidSansJapanese_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
default_font->set_fallbacks(fallbacks);
@@ -188,6 +203,9 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
Ref<FontFile> tamil_font_bold = load_internal_font(_font_NotoSansTamilUI_Bold, _font_NotoSansTamilUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
Ref<FontFile> telugu_font_bold = load_internal_font(_font_NotoSansTeluguUI_Bold, _font_NotoSansTeluguUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
Ref<FontFile> thai_font_bold = load_internal_font(_font_NotoSansThai_Bold, _font_NotoSansThai_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
+ if (!noto_cjk_bold_path.is_empty()) {
+ load_external_font(noto_cjk_bold_path, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
+ }
Ref<FontVariation> fallback_font_bold = make_bold_font(fallback_font, embolden_strength, &fallbacks_bold);
Ref<FontVariation> japanese_font_bold = make_bold_font(japanese_font, embolden_strength, &fallbacks_bold);
diff --git a/main/main.cpp b/main/main.cpp
index 9014bfad29..5206e9b84c 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -245,6 +245,7 @@ static MovieWriter *movie_writer = nullptr;
static bool disable_vsync = false;
static bool print_fps = false;
#ifdef TOOLS_ENABLED
+static bool editor_pseudolocalization = false;
static bool dump_gdextension_interface = false;
static bool dump_extension_api = false;
static bool include_docs_in_extension_api_dump = false;
@@ -649,6 +650,9 @@ void Main::print_help(const char *p_binary) {
print_help_option("--fixed-fps <fps>", "Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
print_help_option("--delta-smoothing <enable>", "Enable or disable frame delta smoothing [\"enable\", \"disable\"].\n");
print_help_option("--print-fps", "Print the frames per second to the stdout.\n");
+#ifdef TOOLS_ENABLED
+ print_help_option("--editor-pseudolocalization", "Enable pseudolocalization for the editor and the project manager.\n");
+#endif
print_help_title("Standalone tools");
print_help_option("-s, --script <script>", "Run a script.\n");
@@ -1716,6 +1720,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
disable_vsync = true;
} else if (arg == "--print-fps") {
print_fps = true;
+#ifdef TOOLS_ENABLED
+ } else if (arg == "--editor-pseudolocalization") {
+ editor_pseudolocalization = true;
+#endif // TOOLS_ENABLED
} else if (arg == "--profile-gpu") {
profile_gpu = true;
} else if (arg == "--disable-crash-handler") {
@@ -2755,7 +2763,6 @@ Error Main::setup2(bool p_show_boot_logo) {
bool prefer_wayland_found = false;
bool prefer_wayland = false;
- bool remember_window_size_and_position_found = false;
if (editor) {
screen_property = "interface/editor/editor_screen";
@@ -2771,7 +2778,7 @@ Error Main::setup2(bool p_show_boot_logo) {
prefer_wayland_found = true;
}
- while (!screen_found || !prefer_wayland_found || !remember_window_size_and_position_found) {
+ while (!screen_found || !prefer_wayland_found) {
assign = Variant();
next_tag.fields.clear();
next_tag.name = String();
@@ -2785,17 +2792,16 @@ Error Main::setup2(bool p_show_boot_logo) {
if (!screen_found && assign == screen_property) {
init_screen = value;
screen_found = true;
+
+ if (editor) {
+ restore_editor_window_layout = value.operator int() == EditorSettings::InitialScreen::INITIAL_SCREEN_AUTO;
+ }
}
if (!prefer_wayland_found && assign == "run/platforms/linuxbsd/prefer_wayland") {
prefer_wayland = value;
prefer_wayland_found = true;
}
-
- if (!remember_window_size_and_position_found && assign == "interface/editor/remember_window_size_and_position") {
- restore_editor_window_layout = value;
- remember_window_size_and_position_found = true;
- }
}
}
@@ -4008,6 +4014,11 @@ int Main::start() {
if (editor) {
OS::get_singleton()->benchmark_begin_measure("Startup", "Editor");
editor_node = memnew(EditorNode);
+
+ if (editor_pseudolocalization) {
+ translation_server->get_editor_domain()->set_pseudolocalization_enabled(true);
+ }
+
sml->get_root()->add_child(editor_node);
if (!_export_preset.is_empty()) {
@@ -4095,8 +4106,7 @@ int Main::start() {
if (editor_embed_subwindows) {
sml->get_root()->set_embedding_subwindows(true);
}
- restore_editor_window_layout = EditorSettings::get_singleton()->get_setting(
- "interface/editor/remember_window_size_and_position");
+ restore_editor_window_layout = EditorSettings::get_singleton()->get_setting("interface/editor/editor_screen").operator int() == EditorSettings::InitialScreen::INITIAL_SCREEN_AUTO;
}
#endif
@@ -4203,6 +4213,11 @@ int Main::start() {
ProjectManager *pmanager = memnew(ProjectManager);
ProgressDialog *progress_dialog = memnew(ProgressDialog);
pmanager->add_child(progress_dialog);
+
+ if (editor_pseudolocalization) {
+ translation_server->get_editor_domain()->set_pseudolocalization_enabled(true);
+ }
+
sml->get_root()->add_child(pmanager);
OS::get_singleton()->benchmark_end_measure("Startup", "Project Manager");
}
diff --git a/main/performance.cpp b/main/performance.cpp
index 0547b3bff0..c73fb62b76 100644
--- a/main/performance.cpp
+++ b/main/performance.cpp
@@ -92,6 +92,11 @@ void Performance::_bind_methods() {
BIND_ENUM_CONSTANT(NAVIGATION_EDGE_CONNECTION_COUNT);
BIND_ENUM_CONSTANT(NAVIGATION_EDGE_FREE_COUNT);
BIND_ENUM_CONSTANT(NAVIGATION_OBSTACLE_COUNT);
+ BIND_ENUM_CONSTANT(PIPELINE_COMPILATIONS_CANVAS);
+ BIND_ENUM_CONSTANT(PIPELINE_COMPILATIONS_MESH);
+ BIND_ENUM_CONSTANT(PIPELINE_COMPILATIONS_SURFACE);
+ BIND_ENUM_CONSTANT(PIPELINE_COMPILATIONS_DRAW);
+ BIND_ENUM_CONSTANT(PIPELINE_COMPILATIONS_SPECIALIZATION);
BIND_ENUM_CONSTANT(MONITOR_MAX);
}
@@ -143,7 +148,11 @@ String Performance::get_monitor_name(Monitor p_monitor) const {
PNAME("navigation/edges_connected"),
PNAME("navigation/edges_free"),
PNAME("navigation/obstacles"),
-
+ PNAME("pipeline/compilations_canvas"),
+ PNAME("pipeline/compilations_mesh"),
+ PNAME("pipeline/compilations_surface"),
+ PNAME("pipeline/compilations_draw"),
+ PNAME("pipeline/compilations_specialization"),
};
return names[p_monitor];
@@ -185,6 +194,16 @@ double Performance::get_monitor(Monitor p_monitor) const {
return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_TEXTURE_MEM_USED);
case RENDER_BUFFER_MEM_USED:
return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_BUFFER_MEM_USED);
+ case PIPELINE_COMPILATIONS_CANVAS:
+ return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_PIPELINE_COMPILATIONS_CANVAS);
+ case PIPELINE_COMPILATIONS_MESH:
+ return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_PIPELINE_COMPILATIONS_MESH);
+ case PIPELINE_COMPILATIONS_SURFACE:
+ return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_PIPELINE_COMPILATIONS_SURFACE);
+ case PIPELINE_COMPILATIONS_DRAW:
+ return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_PIPELINE_COMPILATIONS_DRAW);
+ case PIPELINE_COMPILATIONS_SPECIALIZATION:
+ return RS::get_singleton()->get_rendering_info(RS::RENDERING_INFO_PIPELINE_COMPILATIONS_SPECIALIZATION);
case PHYSICS_2D_ACTIVE_OBJECTS:
return PhysicsServer2D::get_singleton()->get_process_info(PhysicsServer2D::INFO_ACTIVE_OBJECTS);
case PHYSICS_2D_COLLISION_PAIRS:
diff --git a/main/performance.h b/main/performance.h
index 05d678fe55..e88bdcb337 100644
--- a/main/performance.h
+++ b/main/performance.h
@@ -101,6 +101,11 @@ public:
NAVIGATION_EDGE_CONNECTION_COUNT,
NAVIGATION_EDGE_FREE_COUNT,
NAVIGATION_OBSTACLE_COUNT,
+ PIPELINE_COMPILATIONS_CANVAS,
+ PIPELINE_COMPILATIONS_MESH,
+ PIPELINE_COMPILATIONS_SURFACE,
+ PIPELINE_COMPILATIONS_DRAW,
+ PIPELINE_COMPILATIONS_SPECIALIZATION,
MONITOR_MAX
};
diff --git a/methods.py b/methods.py
index 3f11d39bd0..9e881773c9 100644
--- a/methods.py
+++ b/methods.py
@@ -795,9 +795,9 @@ def get_compiler_version(env):
"major": -1,
"minor": -1,
"patch": -1,
- "metadata1": None,
- "metadata2": None,
- "date": None,
+ "metadata1": "",
+ "metadata2": "",
+ "date": "",
"apple_major": -1,
"apple_minor": -1,
"apple_patch1": -1,
@@ -806,7 +806,35 @@ def get_compiler_version(env):
}
if env.msvc and not using_clang(env):
- # TODO: Implement for MSVC
+ try:
+ # FIXME: `-latest` works for most cases, but there are edge-cases where this would
+ # benefit from a more nuanced search.
+ # https://github.com/godotengine/godot/pull/91069#issuecomment-2358956731
+ # https://github.com/godotengine/godot/pull/91069#issuecomment-2380836341
+ args = [
+ env["VSWHERE"],
+ "-latest",
+ "-prerelease",
+ "-products",
+ "*",
+ "-requires",
+ "Microsoft.Component.MSBuild",
+ "-utf8",
+ ]
+ version = subprocess.check_output(args, encoding="utf-8").strip()
+ for line in version.splitlines():
+ split = line.split(":", 1)
+ if split[0] == "catalog_productDisplayVersion":
+ sem_ver = split[1].split(".")
+ ret["major"] = int(sem_ver[0])
+ ret["minor"] = int(sem_ver[1])
+ ret["patch"] = int(sem_ver[2].split()[0])
+ # Could potentially add section for determining preview version, but
+ # that can wait until metadata is actually used for something.
+ if split[0] == "catalog_buildVersion":
+ ret["metadata1"] = split[1]
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't find vswhere to determine compiler version.")
return ret
# Not using -dumpversion as some GCC distros only return major, and
diff --git a/misc/dist/html/full-size.html b/misc/dist/html/full-size.html
index 1d76abe0a5..352046df30 100644
--- a/misc/dist/html/full-size.html
+++ b/misc/dist/html/full-size.html
@@ -164,10 +164,11 @@ const engine = new Engine(GODOT_CONFIG);
new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
}),
- ]).catch((err) => {
- console.error('Error while registering service worker:', err);
- }).then(() => {
+ ]).then(() => {
+ // Reload if there was no error.
window.location.reload();
+ }).catch((err) => {
+ console.error('Error while registering service worker:', err);
});
} else {
// Display the message as usual
diff --git a/misc/dist/linux/org.godotengine.Godot.desktop b/misc/dist/linux/org.godotengine.Godot.desktop
index 28669548d6..6483f51d92 100644
--- a/misc/dist/linux/org.godotengine.Godot.desktop
+++ b/misc/dist/linux/org.godotengine.Godot.desktop
@@ -5,12 +5,14 @@ GenericName[el]=Ελεύθερη μηχανή παιχνιδιού
GenericName[fr]=Moteur de jeu libre
GenericName[nl]=Libre game-engine
GenericName[ru]=Свободный игровой движок
+GenericName[uk]=Вільний ігровий рушій
GenericName[zh_CN]=自由的游戏引擎
Comment=Multi-platform 2D and 3D game engine with a feature-rich editor
Comment[el]=2D και 3D μηχανή παιχνιδιού πολλαπλών πλατφορμών με επεξεργαστή πλούσιο σε χαρακτηριστικά
Comment[fr]=Moteur de jeu 2D et 3D multiplateforme avec un éditeur riche en fonctionnalités
Comment[nl]=Multi-platform 2D- en 3D-game-engine met een veelzijdige editor
Comment[ru]=Кроссплатформенный движок с многофункциональным редактором для 2D- и 3D-игр
+Comment[uk]=Багатофункціональний кросплатформний рушій для створення 2D та 3D ігор
Comment[zh_CN]=多平台 2D 和 3D 游戏引擎,带有功能丰富的编辑器
Exec=godot %f
Icon=godot
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 06933b5841..960edd0575 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -87,3 +87,11 @@ GH-94684
Validate extension JSON: Error: Field 'classes/SoftBody3D/methods/set_point_pinned/arguments': size changed value in new API, from 3 to 4.
Optional argument added to allow for adding pin point at specific index. Compatibility method registered.
+
+
+GH-97281
+--------
+Validate extension JSON: Error: Field 'classes/InputMap/methods/add_action/arguments/1': default_value changed value in new API, from "0.5" to "0.2".
+
+Default deadzone value was changed. No adjustments should be necessary.
+Compatibility method registered.
diff --git a/misc/scripts/install_vulkan_sdk_macos.sh b/misc/scripts/install_vulkan_sdk_macos.sh
index 17d567f208..6e9fa69eea 100755
--- a/misc/scripts/install_vulkan_sdk_macos.sh
+++ b/misc/scripts/install_vulkan_sdk_macos.sh
@@ -4,19 +4,13 @@ set -euo pipefail
IFS=$'\n\t'
# Download and install the Vulkan SDK.
-curl -L "https://sdk.lunarg.com/sdk/download/latest/mac/vulkan-sdk.dmg" -o /tmp/vulkan-sdk.dmg
-hdiutil attach /tmp/vulkan-sdk.dmg -mountpoint /Volumes/vulkan-sdk
-/Volumes/vulkan-sdk/InstallVulkan.app/Contents/MacOS/InstallVulkan \
+curl -L "https://sdk.lunarg.com/sdk/download/latest/mac/vulkan-sdk.zip" -o /tmp/vulkan-sdk.zip
+unzip /tmp/vulkan-sdk.zip -d /tmp
+/tmp/InstallVulkan.app/Contents/MacOS/InstallVulkan \
--accept-licenses --default-answer --confirm-command install
-cnt=5
-until hdiutil detach -force /Volumes/vulkan-sdk
-do
- [[ cnt -eq "0" ]] && break
- sleep 1
- ((cnt--))
-done
-rm -f /tmp/vulkan-sdk.dmg
+rm -rf /tmp/InstallVulkan.app
+rm -f /tmp/vulkan-sdk.zip
echo 'Vulkan SDK installed successfully! You can now build Godot by running "scons".'
diff --git a/modules/basis_universal/SCsub b/modules/basis_universal/SCsub
index 0142317e1e..e201452385 100644
--- a/modules/basis_universal/SCsub
+++ b/modules/basis_universal/SCsub
@@ -14,6 +14,8 @@ thirdparty_obj = []
thirdparty_dir = "#thirdparty/basis_universal/"
# Sync list with upstream CMakeLists.txt
encoder_sources = [
+ "3rdparty/android_astc_decomp.cpp",
+ "basisu_astc_hdr_enc.cpp",
"basisu_backend.cpp",
"basisu_basis_file.cpp",
"basisu_bc7enc.cpp",
@@ -45,11 +47,33 @@ else:
if env["builtin_zstd"]:
env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"])
+env_basisu.Prepend(CPPPATH=["#thirdparty/tinyexr"])
+
if env.dev_build:
env_basisu.Append(CPPDEFINES=[("BASISU_DEVEL_MESSAGES", 1), ("BASISD_ENABLE_DEBUG_FLAGS", 1)])
env_thirdparty = env_basisu.Clone()
env_thirdparty.disable_warnings()
+
+# Disable unneeded features to reduce binary size.
+# <https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder>
+env_thirdparty.Append(
+ CPPDEFINES=[
+ # Storage formats.
+ # Godot only implements `.basis` support through basis_universal.
+ # Support for `.ktx` files are implemented with a direct libktx implementation.
+ # Building the encoder requires `BASISD_SUPPORT_KTX2` to be enabled,
+ # so we can only disable Zstandard compression for `.ktx` files
+ # (this is not used in `.basis` files).
+ ("BASISD_SUPPORT_KTX2_ZSTD", 0),
+ # GPU compression formats.
+ ("BASISD_SUPPORT_ATC", 0), # Proprietary Adreno format not supported by Godot.
+ ("BASISD_SUPPORT_FXT1", 0), # Legacy format not supported by Godot.
+ ("BASISD_SUPPORT_PVRTC1", 0), # Legacy format not supported by Godot.
+ ("BASISD_SUPPORT_PVRTC2", 0), # Legacy format not supported by Godot.
+ ]
+)
+
if env.editor_build:
env_thirdparty.Append(CPPDEFINES=["BASISU_NO_IMG_LOADERS"])
env_thirdparty.add_source_files(thirdparty_obj, encoder_sources)
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index ab20d00b5b..d48ea363a7 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -30,6 +30,8 @@
#include "image_compress_basisu.h"
+#include "core/os/os.h"
+#include "core/string/print_string.h"
#include "servers/rendering_server.h"
#include <transcoder/basisu_transcoder.h>
@@ -46,9 +48,48 @@ void basis_universal_init() {
}
#ifdef TOOLS_ENABLED
+template <typename T>
+inline void _basisu_pad_mipmap(const uint8_t *p_image_mip_data, Vector<uint8_t> &r_mip_data_padded, int p_next_width, int p_next_height, int p_width, int p_height, int64_t p_size) {
+ // Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
+ const T *mip_src_data = reinterpret_cast<const T *>(p_image_mip_data);
+
+ // Reserve space in the padded buffer.
+ r_mip_data_padded.resize(p_next_width * p_next_height * sizeof(T));
+ T *data_padded_ptr = reinterpret_cast<T *>(r_mip_data_padded.ptrw());
+
+ // Pad mipmap to the nearest block by smearing.
+ int x = 0, y = 0;
+ for (y = 0; y < p_height; y++) {
+ for (x = 0; x < p_width; x++) {
+ data_padded_ptr[p_next_width * y + x] = mip_src_data[p_width * y + x];
+ }
+
+ // First, smear in x.
+ for (; x < p_next_width; x++) {
+ data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - 1];
+ }
+ }
+
+ // Then, smear in y.
+ for (; y < p_next_height; y++) {
+ for (x = 0; x < p_next_width; x++) {
+ data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - p_next_width];
+ }
+ }
+}
+
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
Ref<Image> image = p_image->duplicate();
- image->convert(Image::FORMAT_RGBA8);
+ bool is_hdr = false;
+
+ if (image->get_format() <= Image::FORMAT_RGB565) {
+ image->convert(Image::FORMAT_RGBA8);
+ } else if (image->get_format() <= Image::FORMAT_RGBE9995) {
+ image->convert(Image::FORMAT_RGBAF);
+ is_hdr = true;
+ }
basisu::basis_compressor_params params;
@@ -74,32 +115,42 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());
params.m_pJob_pool = &job_pool;
- BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG;
- switch (p_channels) {
- case Image::USED_CHANNELS_L: {
- decompress_format = BASIS_DECOMPRESS_RGB;
- } break;
- case Image::USED_CHANNELS_LA: {
- params.m_force_alpha = true;
- decompress_format = BASIS_DECOMPRESS_RGBA;
- } break;
- case Image::USED_CHANNELS_R: {
- decompress_format = BASIS_DECOMPRESS_R;
- } break;
- case Image::USED_CHANNELS_RG: {
- params.m_force_alpha = true;
- image->convert_rg_to_ra_rgba8();
- decompress_format = BASIS_DECOMPRESS_RG;
- } break;
- case Image::USED_CHANNELS_RGB: {
- decompress_format = BASIS_DECOMPRESS_RGB;
- } break;
- case Image::USED_CHANNELS_RGBA: {
- params.m_force_alpha = true;
- decompress_format = BASIS_DECOMPRESS_RGBA;
- } break;
+ BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_MAX;
+
+ if (is_hdr) {
+ decompress_format = BASIS_DECOMPRESS_HDR_RGB;
+ params.m_hdr = true;
+ params.m_uastc_hdr_options.set_quality_level(0);
+
+ } else {
+ switch (p_channels) {
+ case Image::USED_CHANNELS_L: {
+ decompress_format = BASIS_DECOMPRESS_RGB;
+ } break;
+ case Image::USED_CHANNELS_LA: {
+ params.m_force_alpha = true;
+ decompress_format = BASIS_DECOMPRESS_RGBA;
+ } break;
+ case Image::USED_CHANNELS_R: {
+ decompress_format = BASIS_DECOMPRESS_R;
+ } break;
+ case Image::USED_CHANNELS_RG: {
+ params.m_force_alpha = true;
+ image->convert_rg_to_ra_rgba8();
+ decompress_format = BASIS_DECOMPRESS_RG;
+ } break;
+ case Image::USED_CHANNELS_RGB: {
+ decompress_format = BASIS_DECOMPRESS_RGB;
+ } break;
+ case Image::USED_CHANNELS_RGBA: {
+ params.m_force_alpha = true;
+ decompress_format = BASIS_DECOMPRESS_RGBA;
+ } break;
+ }
}
+ ERR_FAIL_COND_V(decompress_format == BASIS_DECOMPRESS_MAX, Vector<uint8_t>());
+
// Copy the source image data with mipmaps into BasisU.
{
const int orig_width = image->get_width();
@@ -113,9 +164,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
Vector<uint8_t> image_data = image->get_data();
basisu::vector<basisu::image> basisu_mipmaps;
+ basisu::vector<basisu::imagef> basisu_mipmaps_hdr;
// Buffer for storing padded mipmap data.
- Vector<uint32_t> mip_data_padded;
+ Vector<uint8_t> mip_data_padded;
for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
int64_t ofs, size;
@@ -126,31 +178,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
// Pad the mipmap's data if its resolution isn't divisible by 4.
if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {
- // Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
- const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data);
-
- // Reserve space in the padded buffer.
- mip_data_padded.resize(next_width * next_height);
- uint32_t *data_padded_ptr = mip_data_padded.ptrw();
-
- // Pad mipmap to the nearest block by smearing.
- int x = 0, y = 0;
- for (y = 0; y < height; y++) {
- for (x = 0; x < width; x++) {
- data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x];
- }
-
- // First, smear in x.
- for (; x < next_width; x++) {
- data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1];
- }
- }
-
- // Then, smear in y.
- for (; y < next_height; y++) {
- for (x = 0; x < next_width; x++) {
- data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width];
- }
+ if (is_hdr) {
+ _basisu_pad_mipmap<BasisRGBAF>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
+ } else {
+ _basisu_pad_mipmap<uint32_t>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
}
// Override the image_mip_data pointer with our temporary Vector.
@@ -159,7 +190,7 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
// Override the mipmap's properties.
width = next_width;
height = next_height;
- size = mip_data_padded.size() * 4;
+ size = mip_data_padded.size();
}
// Get the next mipmap's resolution.
@@ -167,44 +198,61 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
next_height /= 2;
// Copy the source mipmap's data to a BasisU image.
- basisu::image basisu_image(width, height);
- memcpy(basisu_image.get_ptr(), image_mip_data, size);
+ if (is_hdr) {
+ basisu::imagef basisu_image(width, height);
+ memcpy(reinterpret_cast<uint8_t *>(basisu_image.get_ptr()), image_mip_data, size);
+
+ if (i == 0) {
+ params.m_source_images_hdr.push_back(basisu_image);
+ } else {
+ basisu_mipmaps_hdr.push_back(basisu_image);
+ }
- if (i == 0) {
- params.m_source_images.push_back(basisu_image);
} else {
- basisu_mipmaps.push_back(basisu_image);
+ basisu::image basisu_image(width, height);
+ memcpy(basisu_image.get_ptr(), image_mip_data, size);
+
+ if (i == 0) {
+ params.m_source_images.push_back(basisu_image);
+ } else {
+ basisu_mipmaps.push_back(basisu_image);
+ }
}
}
- params.m_source_mipmap_images.push_back(basisu_mipmaps);
+ if (is_hdr) {
+ params.m_source_mipmap_images_hdr.push_back(basisu_mipmaps_hdr);
+ } else {
+ params.m_source_mipmap_images.push_back(basisu_mipmaps);
+ }
}
// Encode the image data.
- Vector<uint8_t> basisu_data;
-
basisu::basis_compressor compressor;
compressor.init(params);
int basisu_err = compressor.process();
- ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, basisu_data);
+ ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());
- const basisu::uint8_vec &basisu_out = compressor.get_output_basis_file();
- basisu_data.resize(basisu_out.size() + 4);
+ const basisu::uint8_vec &basisu_encoded = compressor.get_output_basis_file();
- // Copy the encoded data to the buffer.
- {
- uint8_t *wb = basisu_data.ptrw();
- *(uint32_t *)wb = decompress_format;
+ Vector<uint8_t> basisu_data;
+ basisu_data.resize(basisu_encoded.size() + 4);
+ uint8_t *basisu_data_ptr = basisu_data.ptrw();
- memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size());
- }
+ // Copy the encoded BasisU data into the output buffer.
+ *(uint32_t *)basisu_data_ptr = decompress_format;
+ memcpy(basisu_data_ptr + 4, basisu_encoded.get_ptr(), basisu_encoded.size());
+
+ print_verbose(vformat("BasisU: Encoding a %dx%d image with %d mipmaps took %d ms.", p_image->get_width(), p_image->get_height(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
return basisu_data;
}
#endif // TOOLS_ENABLED
Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
Ref<Image> image;
ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data.");
@@ -320,6 +368,23 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
}
} break;
+ case BASIS_DECOMPRESS_HDR_RGB: {
+ if (bptc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC6H;
+ image_format = Image::FORMAT_BPTC_RGBFU;
+ } else if (astc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA;
+ image_format = Image::FORMAT_ASTC_4x4_HDR;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGB_9E5;
+ image_format = Image::FORMAT_RGBE9995;
+ }
+
+ } break;
+ default: {
+ ERR_FAIL_V(image);
+ } break;
}
src_ptr += 4;
@@ -371,6 +436,9 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
}
}
+ print_verbose(vformat("BasisU: Transcoding a %dx%d image with %d mipmaps into %s took %d ms.",
+ image->get_width(), image->get_height(), image->get_mipmap_count(), Image::get_format_name(image_format), OS::get_singleton()->get_ticks_msec() - start_time));
+
return image;
}
diff --git a/modules/basis_universal/image_compress_basisu.h b/modules/basis_universal/image_compress_basisu.h
index 5e36d448f6..81c8511f60 100644
--- a/modules/basis_universal/image_compress_basisu.h
+++ b/modules/basis_universal/image_compress_basisu.h
@@ -39,11 +39,20 @@ enum BasisDecompressFormat {
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
BASIS_DECOMPRESS_R,
+ BASIS_DECOMPRESS_HDR_RGB,
+ BASIS_DECOMPRESS_MAX
};
void basis_universal_init();
#ifdef TOOLS_ENABLED
+struct BasisRGBAF {
+ uint32_t r;
+ uint32_t g;
+ uint32_t b;
+ uint32_t a;
+};
+
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels);
#endif
diff --git a/modules/bcdec/SCsub b/modules/bcdec/SCsub
new file mode 100644
index 0000000000..32198eff96
--- /dev/null
+++ b/modules/bcdec/SCsub
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+from misc.utility.scons_hints import *
+
+Import("env")
+Import("env_modules")
+
+env_bcdec = env_modules.Clone()
+
+# Godot source files
+env_bcdec.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/squish/config.py b/modules/bcdec/config.py
index d22f9454ed..d22f9454ed 100644
--- a/modules/squish/config.py
+++ b/modules/bcdec/config.py
diff --git a/modules/bcdec/image_decompress_bcdec.cpp b/modules/bcdec/image_decompress_bcdec.cpp
new file mode 100644
index 0000000000..c76470e3cc
--- /dev/null
+++ b/modules/bcdec/image_decompress_bcdec.cpp
@@ -0,0 +1,190 @@
+/**************************************************************************/
+/* image_decompress_bcdec.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "image_decompress_bcdec.h"
+
+#include "core/os/os.h"
+#include "core/string/print_string.h"
+
+#define BCDEC_IMPLEMENTATION
+#include "thirdparty/misc/bcdec.h"
+
+inline void bcdec_bc6h_half_s(const void *compressedBlock, void *decompressedBlock, int destinationPitch) {
+ bcdec_bc6h_half(compressedBlock, decompressedBlock, destinationPitch, true);
+}
+
+inline void bcdec_bc6h_half_u(const void *compressedBlock, void *decompressedBlock, int destinationPitch) {
+ bcdec_bc6h_half(compressedBlock, decompressedBlock, destinationPitch, false);
+}
+
+static void decompress_image(BCdecFormat format, const void *src, void *dst, const uint64_t width, const uint64_t height) {
+ const uint8_t *src_blocks = reinterpret_cast<const uint8_t *>(src);
+ uint8_t *dec_blocks = reinterpret_cast<uint8_t *>(dst);
+ uint64_t src_pos = 0, dst_pos = 0;
+
+#define DECOMPRESS_LOOP(func, block_size, color_bytesize, color_components) \
+ for (uint64_t y = 0; y < height; y += 4) { \
+ for (uint64_t x = 0; x < width; x += 4) { \
+ func(&src_blocks[src_pos], &dec_blocks[dst_pos], width *color_components); \
+ src_pos += block_size; \
+ dst_pos += 4 * color_bytesize; \
+ } \
+ dst_pos += 3 * width * color_bytesize; \
+ }
+
+ switch (format) {
+ case BCdec_BC1: {
+ DECOMPRESS_LOOP(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4)
+ } break;
+ case BCdec_BC2: {
+ DECOMPRESS_LOOP(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4)
+ } break;
+ case BCdec_BC3: {
+ DECOMPRESS_LOOP(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4)
+ } break;
+ case BCdec_BC4: {
+ DECOMPRESS_LOOP(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1)
+ } break;
+ case BCdec_BC5: {
+ DECOMPRESS_LOOP(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2)
+ } break;
+ case BCdec_BC6U: {
+ DECOMPRESS_LOOP(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
+ } break;
+ case BCdec_BC6S: {
+ DECOMPRESS_LOOP(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
+ } break;
+ case BCdec_BC7: {
+ DECOMPRESS_LOOP(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4)
+ } break;
+ }
+
+#undef DECOMPRESS_LOOP
+}
+
+void image_decompress_bcdec(Image *p_image) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
+ int width = p_image->get_width();
+ int height = p_image->get_height();
+
+ // Compressed images' dimensions should be padded to the upper multiple of 4.
+ // If they aren't, they need to be realigned (the actual data is correctly padded though).
+ if (width % 4 != 0 || height % 4 != 0) {
+ int new_width = width + (4 - (width % 4));
+ int new_height = height + (4 - (height % 4));
+
+ print_verbose(vformat("Compressed image's dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", width, height, new_width, new_height));
+
+ width = new_width;
+ height = new_height;
+ }
+
+ Image::Format source_format = p_image->get_format();
+ Image::Format target_format = Image::FORMAT_MAX;
+
+ BCdecFormat bcdec_format = BCdec_BC1;
+
+ switch (source_format) {
+ case Image::FORMAT_DXT1:
+ bcdec_format = BCdec_BC1;
+ target_format = Image::FORMAT_RGBA8;
+ break;
+
+ case Image::FORMAT_DXT3:
+ bcdec_format = BCdec_BC2;
+ target_format = Image::FORMAT_RGBA8;
+ break;
+
+ case Image::FORMAT_DXT5:
+ case Image::FORMAT_DXT5_RA_AS_RG:
+ bcdec_format = BCdec_BC3;
+ target_format = Image::FORMAT_RGBA8;
+ break;
+
+ case Image::FORMAT_RGTC_R:
+ bcdec_format = BCdec_BC4;
+ target_format = Image::FORMAT_R8;
+ break;
+
+ case Image::FORMAT_RGTC_RG:
+ bcdec_format = BCdec_BC5;
+ target_format = Image::FORMAT_RG8;
+ break;
+
+ case Image::FORMAT_BPTC_RGBFU:
+ bcdec_format = BCdec_BC6U;
+ target_format = Image::FORMAT_RGBH;
+ break;
+
+ case Image::FORMAT_BPTC_RGBF:
+ bcdec_format = BCdec_BC6S;
+ target_format = Image::FORMAT_RGBH;
+ break;
+
+ case Image::FORMAT_BPTC_RGBA:
+ bcdec_format = BCdec_BC7;
+ target_format = Image::FORMAT_RGBA8;
+ break;
+
+ default:
+ ERR_FAIL_MSG("bcdec: Can't decompress unknown format: " + Image::get_format_name(source_format) + ".");
+ break;
+ }
+
+ int mm_count = p_image->get_mipmap_count();
+ int64_t target_size = Image::get_image_data_size(width, height, target_format, p_image->has_mipmaps());
+
+ // Decompressed data.
+ Vector<uint8_t> data;
+ data.resize(target_size);
+ uint8_t *wb = data.ptrw();
+
+ // Source data.
+ const uint8_t *rb = p_image->get_data().ptr();
+
+ // Decompress mipmaps.
+ for (int i = 0; i <= mm_count; i++) {
+ int mipmap_w = 0, mipmap_h = 0;
+ int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, source_format, i, mipmap_w, mipmap_h);
+ int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
+ decompress_image(bcdec_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h);
+ }
+
+ p_image->set_data(width, height, p_image->has_mipmaps(), target_format, data);
+
+ // Swap channels if the format is using a channel swizzle.
+ if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
+ p_image->convert_ra_rgba8_to_rg();
+ }
+
+ print_verbose(vformat("bcdec: Decompression of a %dx%d %s image with %d mipmaps took %d ms.",
+ p_image->get_width(), p_image->get_height(), Image::get_format_name(source_format), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
+}
diff --git a/modules/bcdec/image_decompress_bcdec.h b/modules/bcdec/image_decompress_bcdec.h
new file mode 100644
index 0000000000..b82ceed9a4
--- /dev/null
+++ b/modules/bcdec/image_decompress_bcdec.h
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* image_decompress_bcdec.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef IMAGE_DECOMPRESS_BCDEC_H
+#define IMAGE_DECOMPRESS_BCDEC_H
+
+#include "core/io/image.h"
+
+enum BCdecFormat {
+ BCdec_BC1,
+ BCdec_BC2,
+ BCdec_BC3,
+ BCdec_BC4,
+ BCdec_BC5,
+ BCdec_BC6S,
+ BCdec_BC6U,
+ BCdec_BC7,
+};
+
+void image_decompress_bcdec(Image *p_image);
+
+#endif // IMAGE_DECOMPRESS_BCDEC_H
diff --git a/modules/squish/register_types.cpp b/modules/bcdec/register_types.cpp
index af7cf8f4f1..cbf9c0d383 100644
--- a/modules/squish/register_types.cpp
+++ b/modules/bcdec/register_types.cpp
@@ -30,17 +30,18 @@
#include "register_types.h"
-#include "image_decompress_squish.h"
+#include "image_decompress_bcdec.h"
-void initialize_squish_module(ModuleInitializationLevel p_level) {
+void initialize_bcdec_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
- Image::_image_decompress_bc = image_decompress_squish;
+ Image::_image_decompress_bc = image_decompress_bcdec;
+ Image::_image_decompress_bptc = image_decompress_bcdec;
}
-void uninitialize_squish_module(ModuleInitializationLevel p_level) {
+void uninitialize_bcdec_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
diff --git a/modules/squish/register_types.h b/modules/bcdec/register_types.h
index 1786b28ed3..eb721e3f2a 100644
--- a/modules/squish/register_types.h
+++ b/modules/bcdec/register_types.h
@@ -28,12 +28,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef SQUISH_REGISTER_TYPES_H
-#define SQUISH_REGISTER_TYPES_H
+#ifndef BCDEC_REGISTER_TYPES_H
+#define BCDEC_REGISTER_TYPES_H
#include "modules/register_module_types.h"
-void initialize_squish_module(ModuleInitializationLevel p_level);
-void uninitialize_squish_module(ModuleInitializationLevel p_level);
+void initialize_bcdec_module(ModuleInitializationLevel p_level);
+void uninitialize_bcdec_module(ModuleInitializationLevel p_level);
-#endif // SQUISH_REGISTER_TYPES_H
+#endif // BCDEC_REGISTER_TYPES_H
diff --git a/modules/betsy/SCsub b/modules/betsy/SCsub
index 2735116cc3..621ba58ae3 100644
--- a/modules/betsy/SCsub
+++ b/modules/betsy/SCsub
@@ -7,6 +7,7 @@ Import("env_modules")
env_betsy = env_modules.Clone()
env_betsy.GLSL_HEADER("bc6h.glsl")
env_betsy.GLSL_HEADER("bc1.glsl")
+env_betsy.GLSL_HEADER("bc4.glsl")
env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"])
# Thirdparty source files
diff --git a/modules/betsy/bc4.glsl b/modules/betsy/bc4.glsl
new file mode 100644
index 0000000000..b7a5f6a686
--- /dev/null
+++ b/modules/betsy/bc4.glsl
@@ -0,0 +1,151 @@
+#[versions]
+
+unsigned = "";
+signed = "#define SNORM";
+
+#[compute]
+#version 450
+
+#include "CrossPlatformSettings_piece_all.glsl"
+#include "UavCrossPlatform_piece_all.glsl"
+
+#VERSION_DEFINES
+
+shared float2 g_minMaxValues[4u * 4u * 4u];
+shared uint2 g_mask[4u * 4u];
+
+layout(binding = 0) uniform sampler2D srcTex;
+layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture;
+
+layout(push_constant, std430) uniform Params {
+ uint p_channelIdx;
+ uint p_padding[3];
+}
+params;
+
+layout(local_size_x = 4, //
+ local_size_y = 4, //
+ local_size_z = 4) in;
+
+/// Each block is 16 pixels
+/// Each thread works on 4 pixels
+/// Therefore each block needs 4 threads, generating 8 masks
+/// At the end these 8 masks get merged into 2 and results written to output
+///
+/// **Q: Why 4 pixels per thread? Why not 1 pixel per thread? Why not 2? Why not 16?**
+///
+/// A: It's a sweetspot.
+/// - Very short threads cannot fill expensive GPUs with enough work (dispatch bound)
+/// - Lots of threads means lots of synchronization (e.g. evaluating min/max, merging masks)
+/// overhead, and also more LDS usage which reduces occupancy.
+/// - Long threads (e.g. 1 thread per block) misses parallelism opportunities
+void main() {
+ float minVal, maxVal;
+ float4 srcPixel;
+
+ const uint blockThreadId = gl_LocalInvocationID.x;
+
+ const uint2 pixelsToLoadBase = gl_GlobalInvocationID.yz << 2u;
+
+ for (uint i = 0u; i < 4u; ++i) {
+ const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i, blockThreadId);
+
+ const float4 value = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyzw;
+ srcPixel[i] = params.p_channelIdx == 0 ? value.x : (params.p_channelIdx == 1 ? value.y : value.w);
+ srcPixel[i] *= 255.0f;
+ }
+
+ minVal = min3(srcPixel.x, srcPixel.y, srcPixel.z);
+ maxVal = max3(srcPixel.x, srcPixel.y, srcPixel.z);
+ minVal = min(minVal, srcPixel.w);
+ maxVal = max(maxVal, srcPixel.w);
+
+ const uint minMaxIdxBase = (gl_LocalInvocationID.z << 4u) + (gl_LocalInvocationID.y << 2u);
+ const uint maskIdxBase = (gl_LocalInvocationID.z << 2u) + gl_LocalInvocationID.y;
+
+ g_minMaxValues[minMaxIdxBase + blockThreadId] = float2(minVal, maxVal);
+ g_mask[maskIdxBase] = uint2(0u, 0u);
+
+ memoryBarrierShared();
+ barrier();
+
+ // Have all 4 threads in the block grab the min/max value by comparing what all 4 threads uploaded
+ for (uint i = 0u; i < 4u; ++i) {
+ minVal = min(g_minMaxValues[minMaxIdxBase + i].x, minVal);
+ maxVal = max(g_minMaxValues[minMaxIdxBase + i].y, maxVal);
+ }
+
+ // determine bias and emit color indices
+ // given the choice of maxVal/minVal, these indices are optimal:
+ // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/
+ float dist = maxVal - minVal;
+ float dist4 = dist * 4.0f;
+ float dist2 = dist * 2.0f;
+ float bias = (dist < 8) ? (dist - 1) : (trunc(dist * 0.5f) + 2);
+ bias -= minVal * 7;
+
+ uint mask0 = 0u, mask1 = 0u;
+
+ for (uint i = 0u; i < 4u; ++i) {
+ float a = srcPixel[i] * 7.0f + bias;
+
+ int ind = 0;
+
+ // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max).
+ if (a >= dist4) {
+ ind = 4;
+ a -= dist4;
+ }
+
+ if (a >= dist2) {
+ ind += 2;
+ a -= dist2;
+ }
+
+ if (a >= dist)
+ ind += 1;
+
+ // turn linear scale into DXT index (0/1 are extremal pts)
+ ind = -ind & 7;
+ ind ^= (2 > ind) ? 1 : 0;
+
+ // write index
+ const uint bits = 16u + ((blockThreadId << 2u) + i) * 3u;
+ if (bits < 32u) {
+ mask0 |= uint(ind) << bits;
+ if (bits + 3u > 32u) {
+ mask1 |= uint(ind) >> (32u - bits);
+ }
+ } else {
+ mask1 |= uint(ind) << (bits - 32u);
+ }
+ }
+
+ if (mask0 != 0u)
+ atomicOr(g_mask[maskIdxBase].x, mask0);
+ if (mask1 != 0u)
+ atomicOr(g_mask[maskIdxBase].y, mask1);
+
+ memoryBarrierShared();
+ barrier();
+
+ if (blockThreadId == 0u) {
+ // Save data
+ uint2 outputBytes;
+
+#ifdef SNORM
+ outputBytes.x =
+ packSnorm4x8(float4(maxVal * (1.0f / 255.0f) * 2.0f - 1.0f,
+ minVal * (1.0f / 255.0f) * 2.0f - 1.0f, 0.0f, 0.0f));
+#else
+ outputBytes.x = packUnorm4x8(
+ float4(maxVal * (1.0f / 255.0f), minVal * (1.0f / 255.0f), 0.0f, 0.0f));
+#endif
+
+ outputBytes.x |= g_mask[maskIdxBase].x;
+ outputBytes.y = g_mask[maskIdxBase].y;
+
+ uint2 dstUV = gl_GlobalInvocationID.yz;
+ imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u));
+ }
+}
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 7b4d8b3dfb..6bfe01f65c 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -35,6 +35,7 @@
#include "betsy_bc1.h"
#include "bc1.glsl.gen.h"
+#include "bc4.glsl.gen.h"
#include "bc6h.glsl.gen.h"
static Mutex betsy_mutex;
@@ -165,6 +166,10 @@ static String get_shader_name(BetsyFormat p_format) {
case BETSY_FORMAT_BC3:
return "BC3";
+ case BETSY_FORMAT_BC4_SIGNED:
+ case BETSY_FORMAT_BC4_UNSIGNED:
+ return "BC4";
+
case BETSY_FORMAT_BC6_SIGNED:
case BETSY_FORMAT_BC6_UNSIGNED:
return "BC6";
@@ -202,6 +207,12 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
dest_format = Image::FORMAT_DXT1;
break;
+ case BETSY_FORMAT_BC4_UNSIGNED:
+ version = "unsigned";
+ dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
+ dest_format = Image::FORMAT_RGTC_R;
+ break;
+
case BETSY_FORMAT_BC6_SIGNED:
version = "signed";
dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT;
@@ -235,8 +246,13 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
err = source->parse_versions_from_text(bc1_shader_glsl);
break;
- case BETSY_FORMAT_BC6_UNSIGNED:
+ case BETSY_FORMAT_BC4_SIGNED:
+ case BETSY_FORMAT_BC4_UNSIGNED:
+ err = source->parse_versions_from_text(bc4_shader_glsl);
+ break;
+
case BETSY_FORMAT_BC6_SIGNED:
+ case BETSY_FORMAT_BC6_UNSIGNED:
err = source->parse_versions_from_text(bc6h_shader_glsl);
break;
@@ -430,26 +446,45 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
- if (dest_format == Image::FORMAT_BPTC_RGBFU || dest_format == Image::FORMAT_BPTC_RGBF) {
- BC6PushConstant push_constant;
- push_constant.sizeX = 1.0f / width;
- push_constant.sizeY = 1.0f / height;
- push_constant.padding[0] = 0;
- push_constant.padding[1] = 0;
-
- compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
-
- } else {
- BC1PushConstant push_constant;
- push_constant.num_refines = 2;
- push_constant.padding[0] = 0;
- push_constant.padding[1] = 0;
- push_constant.padding[2] = 0;
-
- compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
+ switch (dest_format) {
+ case Image::FORMAT_BPTC_RGBFU:
+ case Image::FORMAT_BPTC_RGBF: {
+ BC6PushConstant push_constant;
+ push_constant.sizeX = 1.0f / width;
+ push_constant.sizeY = 1.0f / height;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
+ compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+ } break;
+
+ case Image::FORMAT_DXT1: {
+ BC1PushConstant push_constant;
+ push_constant.num_refines = 2;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+ push_constant.padding[2] = 0;
+
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
+ compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+ } break;
+
+ case Image::FORMAT_RGTC_R: {
+ BC4PushConstant push_constant;
+ push_constant.channel_idx = 0;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+ push_constant.padding[2] = 0;
+
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
+ compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
+ } break;
+
+ default: {
+ } break;
}
- compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
compress_rd->compute_list_end();
compress_rd->submit();
@@ -511,13 +546,14 @@ Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
switch (p_channels) {
case Image::USED_CHANNELS_RGB:
- result = betsy->compress(BETSY_FORMAT_BC1_DITHER, r_img);
- break;
-
case Image::USED_CHANNELS_L:
result = betsy->compress(BETSY_FORMAT_BC1, r_img);
break;
+ case Image::USED_CHANNELS_R:
+ result = betsy->compress(BETSY_FORMAT_BC4_UNSIGNED, r_img);
+ break;
+
default:
break;
}
diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h
index 70e4ae85ed..4e0bf0538f 100644
--- a/modules/betsy/image_compress_betsy.h
+++ b/modules/betsy/image_compress_betsy.h
@@ -50,6 +50,8 @@ enum BetsyFormat {
BETSY_FORMAT_BC1,
BETSY_FORMAT_BC1_DITHER,
BETSY_FORMAT_BC3,
+ BETSY_FORMAT_BC4_SIGNED,
+ BETSY_FORMAT_BC4_UNSIGNED,
BETSY_FORMAT_BC6_SIGNED,
BETSY_FORMAT_BC6_UNSIGNED,
};
@@ -65,6 +67,11 @@ struct BC1PushConstant {
uint32_t padding[3];
};
+struct BC4PushConstant {
+ uint32_t channel_idx;
+ uint32_t padding[3];
+};
+
void free_device();
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels);
diff --git a/modules/camera/buffer_decoder.cpp b/modules/camera/buffer_decoder.cpp
index 024a68f080..85cfea242c 100644
--- a/modules/camera/buffer_decoder.cpp
+++ b/modules/camera/buffer_decoder.cpp
@@ -105,7 +105,7 @@ void SeparateYuyvBufferDecoder::decode(StreamingBuffer p_buffer) {
cbcr_image.instantiate(width, height, false, Image::FORMAT_RGB8, cbcr_image_data);
}
- camera_feed->set_YCbCr_imgs(y_image, cbcr_image);
+ camera_feed->set_ycbcr_images(y_image, cbcr_image);
}
YuyvToGrayscaleBufferDecoder::YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed) :
@@ -133,7 +133,7 @@ void YuyvToGrayscaleBufferDecoder::decode(StreamingBuffer p_buffer) {
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
}
- camera_feed->set_RGB_img(image);
+ camera_feed->set_rgb_image(image);
}
YuyvToRgbBufferDecoder::YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed) :
@@ -176,7 +176,7 @@ void YuyvToRgbBufferDecoder::decode(StreamingBuffer p_buffer) {
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
}
- camera_feed->set_RGB_img(image);
+ camera_feed->set_rgb_image(image);
}
CopyBufferDecoder::CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba) :
@@ -195,7 +195,7 @@ void CopyBufferDecoder::decode(StreamingBuffer p_buffer) {
image.instantiate(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
}
- camera_feed->set_RGB_img(image);
+ camera_feed->set_rgb_image(image);
}
JpegBufferDecoder::JpegBufferDecoder(CameraFeed *p_camera_feed) :
@@ -207,6 +207,6 @@ void JpegBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *dst = (uint8_t *)image_data.ptrw();
memcpy(dst, p_buffer.start, p_buffer.length);
if (image->load_jpg_from_buffer(image_data) == OK) {
- camera_feed->set_RGB_img(image);
+ camera_feed->set_rgb_image(image);
}
}
diff --git a/modules/camera/camera_feed_linux.cpp b/modules/camera/camera_feed_linux.cpp
index 9ed8eb0d0a..94bb2b6ad3 100644
--- a/modules/camera/camera_feed_linux.cpp
+++ b/modules/camera/camera_feed_linux.cpp
@@ -232,6 +232,7 @@ String CameraFeedLinux::get_device_name() const {
}
bool CameraFeedLinux::activate_feed() {
+ ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
file_descriptor = open(device_name.ascii(), O_RDWR | O_NONBLOCK, 0);
if (_request_buffers() && _start_capturing()) {
buffer_decoder = _create_buffer_decoder();
@@ -302,16 +303,14 @@ Array CameraFeedLinux::get_formats() const {
}
CameraFeed::FeedFormat CameraFeedLinux::get_format() const {
- return formats[selected_format];
+ FeedFormat feed_format = {};
+ return selected_format == -1 ? feed_format : formats[selected_format];
}
bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) {
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
- parameters = p_parameters.duplicate();
- selected_format = p_index;
-
FeedFormat feed_format = formats[p_index];
file_descriptor = open(device_name.ascii(), O_RDWR | O_NONBLOCK, 0);
@@ -344,6 +343,8 @@ bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) {
}
close(file_descriptor);
+ parameters = p_parameters.duplicate();
+ selected_format = p_index;
emit_signal(SNAME("format_changed"));
return true;
@@ -353,7 +354,6 @@ CameraFeedLinux::CameraFeedLinux(const String &p_device_name) :
CameraFeed() {
device_name = p_device_name;
_query_device(device_name);
- set_format(0, Dictionary());
}
CameraFeedLinux::~CameraFeedLinux() {
diff --git a/modules/camera/camera_macos.mm b/modules/camera/camera_macos.mm
index 578a1d6325..de4f814846 100644
--- a/modules/camera/camera_macos.mm
+++ b/modules/camera/camera_macos.mm
@@ -182,7 +182,7 @@
}
// set our texture...
- feed->set_YCbCr_imgs(img[0], img[1]);
+ feed->set_ycbcr_images(img[0], img[1]);
}
// and unlock
diff --git a/modules/cvtt/register_types.cpp b/modules/cvtt/register_types.cpp
index 211d419349..80e3062d04 100644
--- a/modules/cvtt/register_types.cpp
+++ b/modules/cvtt/register_types.cpp
@@ -40,7 +40,6 @@ void initialize_cvtt_module(ModuleInitializationLevel p_level) {
}
Image::set_compress_bptc_func(image_compress_cvtt);
- Image::_image_decompress_bptc = image_decompress_cvtt;
}
void uninitialize_cvtt_module(ModuleInitializationLevel p_level) {
diff --git a/modules/enet/doc_classes/ENetPacketPeer.xml b/modules/enet/doc_classes/ENetPacketPeer.xml
index 3171da1f6d..659cea974c 100644
--- a/modules/enet/doc_classes/ENetPacketPeer.xml
+++ b/modules/enet/doc_classes/ENetPacketPeer.xml
@@ -18,6 +18,12 @@
Returns the number of channels allocated for communication with peer.
</description>
</method>
+ <method name="get_packet_flags" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the ENet flags of the next packet in the received queue. See [code]FLAG_*[/code] constants for available packet flags. Note that not all flags are replicated from the sending peer to the receiving peer.
+ </description>
+ </method>
<method name="get_remote_address" qualifiers="const">
<return type="String" />
<description>
diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp
index edb33fc96b..9ec68465a5 100644
--- a/modules/enet/enet_packet_peer.cpp
+++ b/modules/enet/enet_packet_peer.cpp
@@ -175,6 +175,11 @@ int ENetPacketPeer::get_channels() const {
return peer->channelCount;
}
+int ENetPacketPeer::get_packet_flags() const {
+ ERR_FAIL_COND_V(packet_queue.is_empty(), 0);
+ return packet_queue.front()->get()->flags;
+}
+
void ENetPacketPeer::_on_disconnect() {
if (peer) {
peer->data = nullptr;
@@ -206,6 +211,7 @@ void ENetPacketPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("send", "channel", "packet", "flags"), &ENetPacketPeer::_send);
ClassDB::bind_method(D_METHOD("throttle_configure", "interval", "acceleration", "deceleration"), &ENetPacketPeer::throttle_configure);
ClassDB::bind_method(D_METHOD("set_timeout", "timeout", "timeout_min", "timeout_max"), &ENetPacketPeer::set_timeout);
+ ClassDB::bind_method(D_METHOD("get_packet_flags"), &ENetPacketPeer::get_packet_flags);
ClassDB::bind_method(D_METHOD("get_remote_address"), &ENetPacketPeer::get_remote_address);
ClassDB::bind_method(D_METHOD("get_remote_port"), &ENetPacketPeer::get_remote_port);
ClassDB::bind_method(D_METHOD("get_statistic", "statistic"), &ENetPacketPeer::get_statistic);
diff --git a/modules/enet/enet_packet_peer.h b/modules/enet/enet_packet_peer.h
index fe40d06188..b41d67e86b 100644
--- a/modules/enet/enet_packet_peer.h
+++ b/modules/enet/enet_packet_peer.h
@@ -113,6 +113,7 @@ public:
double get_statistic(PeerStatistic p_stat);
PeerState get_state() const;
int get_channels() const;
+ int get_packet_flags() const;
// Extras
IPAddress get_remote_address() const;
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index f539f27848..5fe47d69df 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -669,6 +669,41 @@
[b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
</description>
</annotation>
+ <annotation name="@export_tool_button">
+ <return type="void" />
+ <param index="0" name="text" type="String" />
+ <param index="1" name="icon" type="String" default="&quot;&quot;" />
+ <description>
+ Export a [Callable] property as a clickable button with the label [param text]. When the button is pressed, the callable is called.
+ If [param icon] is specified, it is used to fetch an icon for the button via [method Control.get_theme_icon], from the [code]"EditorIcons"[/code] theme type. If [param icon] is omitted, the default [code]"Callable"[/code] icon is used instead.
+ Consider using the [EditorUndoRedoManager] to allow the action to be reverted safely.
+ See also [constant PROPERTY_HINT_TOOL_BUTTON].
+ [codeblock]
+ @tool
+ extends Sprite2D
+
+ @export_tool_button("Hello") var hello_action = hello
+ @export_tool_button("Randomize the color!", "ColorRect")
+ var randomize_color_action = randomize_color
+
+ func hello():
+ print("Hello world!")
+
+ func randomize_color():
+ var undo_redo = EditorInterface.get_editor_undo_redo()
+ undo_redo.create_action("Randomized Sprite2D Color")
+ undo_redo.add_do_property(self, &amp;"self_modulate", Color(randf(), randf(), randf()))
+ undo_redo.add_undo_property(self, &amp;"self_modulate", self_modulate)
+ undo_redo.commit_action()
+ [/codeblock]
+ [b]Note:[/b] The property is exported without the [constant PROPERTY_USAGE_STORAGE] flag because a [Callable] cannot be properly serialized and stored in a file.
+ [b]Note:[/b] In an exported project neither [EditorInterface] nor [EditorUndoRedoManager] exist, which may cause some scripts to break. To prevent this, you can use [method Engine.get_singleton] and omit the static type from the variable declaration:
+ [codeblock]
+ var undo_redo = Engine.get_singleton(&amp;"EditorInterface").get_editor_undo_redo()
+ [/codeblock]
+ [b]Note:[/b] Avoid storing lambda callables in member variables of [RefCounted]-based classes (e.g. resources), as this can lead to memory leaks. Use only method callables and optionally [method Callable.bind] or [method Callable.unbind].
+ </description>
+ </annotation>
<annotation name="@icon">
<return type="void" />
<param index="0" name="icon_path" type="String" />
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 7f0d5005cb..4a3a3a4b61 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
return type;
}
+static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
+
+ type.class_type = p_class;
+ type.script_path = p_script_path;
+
+ return type;
+}
+
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
StringName native_base = p_native_class;
@@ -1101,7 +1110,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
member.m_enum->set_datatype(resolving_datatype);
- GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
+ GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
const GDScriptParser::EnumNode *prev_enum = current_enum;
current_enum = member.m_enum;
@@ -1194,7 +1203,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Also update the original references.
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
- member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
+ member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
} break;
case GDScriptParser::ClassNode::Member::CLASS:
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
@@ -4249,7 +4258,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
if (element.identifier->name == p_identifier->name) {
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
- GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
+ GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
if (element.parent_enum->identifier) {
type.enum_type = element.parent_enum->identifier->name;
}
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index b3c0744bdf..3c022412bd 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -297,6 +297,7 @@ Vector<uint8_t> GDScriptCache::get_binary_tokens(const String &p_path) {
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
MutexLock lock(singleton->mutex);
+
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
}
@@ -307,7 +308,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
return singleton->shallow_gdscript_cache[p_path];
}
- String remapped_path = ResourceLoader::path_remap(p_path);
+ const String remapped_path = ResourceLoader::path_remap(p_path);
Ref<GDScript> script;
script.instantiate();
@@ -332,6 +333,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
}
singleton->shallow_gdscript_cache[p_path] = script;
+
return script;
}
@@ -359,16 +361,18 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
}
}
+ const String remapped_path = ResourceLoader::path_remap(p_path);
+
if (p_update_from_disk) {
- if (p_path.get_extension().to_lower() == "gdc") {
- Vector<uint8_t> buffer = get_binary_tokens(p_path);
+ if (remapped_path.get_extension().to_lower() == "gdc") {
+ Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;
return script;
}
script->set_binary_tokens_source(buffer);
} else {
- r_error = script->load_source_code(p_path);
+ r_error = script->load_source_code(remapped_path);
if (r_error) {
return script;
}
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 73f2b1d618..3de1decc18 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -864,7 +864,8 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap<St
}
}
-static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
+static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, String &r_arghint) {
+ r_arghint = _make_arguments_hint(p_annotation->info->info, p_argument, true);
if (p_annotation->name == SNAME("@export_range")) {
if (p_argument == 3 || p_argument == 4 || p_argument == 5) {
// Slider hint.
@@ -2975,11 +2976,6 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
} break;
case GDScriptParser::Node::IDENTIFIER: {
- if (p_subscript->base->datatype.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT) {
- // Annotated type takes precedence.
- return false;
- }
-
const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base);
switch (identifier_node->source) {
@@ -3017,6 +3013,14 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co
if (get_node != nullptr) {
const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path));
if (node != nullptr) {
+ GDScriptParser::DataType assigned_type = _type_from_variant(node, p_context).type;
+ GDScriptParser::DataType base_type = p_subscript->base->datatype;
+
+ if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && base_type.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT && (assigned_type.kind != base_type.kind || assigned_type.script_path != base_type.script_path || assigned_type.native_type != base_type.native_type)) {
+ // Annotated type takes precedence.
+ return false;
+ }
+
if (r_base != nullptr) {
*r_base = node;
}
@@ -3183,7 +3187,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
break;
}
const GDScriptParser::AnnotationNode *annotation = static_cast<const GDScriptParser::AnnotationNode *>(completion_context.node);
- _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options);
+ _find_annotation_arguments(annotation, completion_context.current_argument, quote_style, options, r_call_hint);
r_forced = true;
} break;
case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD: {
@@ -3778,7 +3782,19 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
} break;
case GDScriptParser::DataType::ENUM: {
- if (base_type.enum_values.has(p_symbol)) {
+ if (base_type.class_type && base_type.class_type->has_member(base_type.enum_type)) {
+ GDScriptParser::EnumNode *base_enum = base_type.class_type->get_member(base_type.enum_type).m_enum;
+ for (const GDScriptParser::EnumNode::Value &value : base_enum->values) {
+ if (value.identifier && value.identifier->name == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION;
+ r_result.class_path = base_type.script_path;
+ r_result.location = value.line;
+ Error err = OK;
+ r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err);
+ return err;
+ }
+ }
+ } else if (base_type.enum_values.has(p_symbol)) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = String(base_type.native_type).get_slicec('.', 0);
r_result.class_member = p_symbol;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 65aa150be3..111a39d730 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -122,6 +122,7 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation);
register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT));
+ register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray(""));
// Export grouping annotations.
register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
@@ -1639,23 +1640,29 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
advance();
// Arguments.
push_completion_call(annotation);
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0);
int argument_index = 0;
do {
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
+ set_last_completion_call_arg(argument_index);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
}
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
- set_last_completion_call_arg(argument_index++);
ExpressionNode *argument = parse_expression(false);
+
if (argument == nullptr) {
push_error("Expected expression as the annotation argument.");
valid = false;
- continue;
+ } else {
+ annotation->arguments.push_back(argument);
+
+ if (argument->type == Node::LITERAL) {
+ override_completion_context(argument, COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
+ }
}
- annotation->arguments.push_back(argument);
+
+ argument_index++;
} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
pop_multiline();
@@ -4618,10 +4625,10 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
// or handle array exports in a special way, so they are implemented as separate methods.
-bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
- ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
+bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
- VariableNode *variable = static_cast<VariableNode *>(p_node);
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
if (variable->is_static) {
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
return false;
@@ -4640,11 +4647,11 @@ bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Nod
return true;
}
-bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
- ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
+bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");
- VariableNode *variable = static_cast<VariableNode *>(p_node);
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
if (variable->is_static) {
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
return false;
@@ -4668,12 +4675,56 @@ bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node
return true;
}
-template <PropertyUsageFlags t_usage>
-bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
- if (p_annotation->resolved_arguments.is_empty()) {
+bool GDScriptParser::export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+#ifdef TOOLS_ENABLED
+ ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
+
+ if (!is_tool()) {
+ push_error(R"(Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).)", p_annotation);
+ return false;
+ }
+
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
+
+ if (variable->is_static) {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
+ return false;
+ }
+ if (variable->exported) {
+ push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
return false;
}
+ const DataType variable_type = variable->get_datatype();
+ if (!variable_type.is_variant() && variable_type.is_hard_type()) {
+ if (variable_type.kind != DataType::BUILTIN || variable_type.builtin_type != Variant::CALLABLE) {
+ push_error(vformat(R"("@export_tool_button" annotation requires a variable of type "Callable", but type "%s" was given instead.)", variable_type.to_string()), p_annotation);
+ return false;
+ }
+ }
+
+ variable->exported = true;
+
+ // Build the hint string (format: `<text>[,<icon>]`).
+ String hint_string = p_annotation->resolved_arguments[0].operator String(); // Button text.
+ if (p_annotation->resolved_arguments.size() > 1) {
+ hint_string += "," + p_annotation->resolved_arguments[1].operator String(); // Button icon.
+ }
+
+ variable->export_info.type = Variant::CALLABLE;
+ variable->export_info.hint = PROPERTY_HINT_TOOL_BUTTON;
+ variable->export_info.hint_string = hint_string;
+ variable->export_info.usage = PROPERTY_USAGE_EDITOR;
+#endif // TOOLS_ENABLED
+
+ return true; // Only available in editor.
+}
+
+template <PropertyUsageFlags t_usage>
+bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
+
p_annotation->export_info.name = p_annotation->resolved_arguments[0];
switch (t_usage) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 7840474a89..7f64ae902b 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1507,6 +1507,7 @@ private:
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyUsageFlags t_usage>
bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 4617a0dbb9..d8139d913a 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -227,7 +227,7 @@ void (*type_init_function_table[])(Variant *) = {
&VariantInitializer<PackedVector4Array>::init, // PACKED_VECTOR4_ARRAY.
};
-#if defined(__GNUC__)
+#if defined(__GNUC__) || defined(__clang__)
#define OPCODES_TABLE \
static const void *switch_table_ops[] = { \
&&OPCODE_OPERATOR, \
diff --git a/modules/gdscript/tests/scripts/.editorconfig b/modules/gdscript/tests/scripts/.editorconfig
index da1efefe3c..34fff8d1de 100644
--- a/modules/gdscript/tests/scripts/.editorconfig
+++ b/modules/gdscript/tests/scripts/.editorconfig
@@ -1,5 +1,3 @@
-# This file is required to workaround `.editorconfig` autogeneration (see #96845).
-
# Some tests handle invalid syntax deliberately; exclude relevant attributes.
[parser/features/mixed_indentation_on_blank_lines.gd]
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd
new file mode 100644
index 0000000000..48be5b2541
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.gd
@@ -0,0 +1 @@
+@export_tool_button("Click me!") var action
diff --git a/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out
new file mode 100644
index 0000000000..fb148308e4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/export_tool_button_requires_tool_mode.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Tool buttons can only be used in tool scripts (add "@tool" to the top of the script).
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 1e134d0e0e..8aa449f602 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,3 +1,4 @@
+@tool
class_name ExportVariableTest
extends Node
@@ -47,6 +48,10 @@ const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_weak_int = 5
@export_custom(PROPERTY_HINT_ENUM, "A,B,C") var test_export_custom_hard_int: int = 6
+# `@export_tool_button`.
+@export_tool_button("Click me!") var test_tool_button_1: Callable
+@export_tool_button("Click me!", "ColorRect") var test_tool_button_2: Callable
+
func test():
for property in get_property_list():
if str(property.name).begins_with("test_"):
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index d10462bb8d..0d915e00e6 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -55,3 +55,7 @@ var test_export_custom_weak_int: int = 5
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_export_custom_hard_int: int = 6
hint=ENUM hint_string="A,B,C" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+var test_tool_button_1: Callable = Callable()
+ hint=TOOL_BUTTON hint_string="Click me!" usage=EDITOR|SCRIPT_VARIABLE class_name=&""
+var test_tool_button_2: Callable = Callable()
+ hint=TOOL_BUTTON hint_string="Click me!,ColorRect" usage=EDITOR|SCRIPT_VARIABLE class_name=&""
diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot
index c9035ecab9..0757bec5c4 100644
--- a/modules/gdscript/tests/scripts/project.godot
+++ b/modules/gdscript/tests/scripts/project.godot
@@ -12,6 +12,6 @@ config/name="GDScript Integration Test Suite"
[input]
test_input_action={
-"deadzone": 0.5,
+"deadzone": 0.2,
"events": []
}
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 1e2788f765..fa289e442f 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -205,6 +205,9 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
PROPERTY_HINT_PASSWORD:
return "PROPERTY_HINT_PASSWORD"
+ PROPERTY_HINT_TOOL_BUTTON:
+ return "PROPERTY_HINT_TOOL_BUTTON"
+
printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
return "<invalid hint>"
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 992075e980..0a487430a3 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -613,12 +613,8 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
if (n.has("scale")) {
node->set_scale(_arr_to_vec3(n["scale"]));
}
-
- Transform3D godot_rest_transform;
- godot_rest_transform.basis.set_quaternion_scale(node->transform.basis.get_rotation_quaternion(), node->transform.basis.get_scale());
- godot_rest_transform.origin = node->transform.origin;
- node->set_additional_data("GODOT_rest_transform", godot_rest_transform);
}
+ node->set_additional_data("GODOT_rest_transform", node->transform);
if (n.has("extensions")) {
Dictionary extensions = n["extensions"];
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
index 053ae36ae7..5654060d38 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
@@ -395,6 +395,11 @@ public class MustBeVariantAnnotatedMethods
public void MethodWithWrongAttribute()
{
}
+
+ [NestedGenericTypeAttributeContainer.NestedGenericTypeAttribute<bool>()]
+ public void MethodWithNestedAttribute()
+ {
+ }
}
[GenericTypeAttribute<bool>()]
@@ -657,3 +662,11 @@ public class ClassNonVariantAnnotated
public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
{
}
+
+public class NestedGenericTypeAttributeContainer
+{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public class NestedGenericTypeAttribute<[MustBeVariant] T> : Attribute
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
index e894e7a86c..a72a8c5880 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MustBeVariantAnalyzer.cs
@@ -135,7 +135,7 @@ namespace Godot.SourceGenerators
{
ITypeParameterSymbol? typeParamSymbol = parentSymbol switch
{
- IMethodSymbol methodSymbol when parentSyntax.Parent is AttributeSyntax &&
+ IMethodSymbol methodSymbol when parentSyntax.Ancestors().Any(s => s is AttributeSyntax) &&
methodSymbol.ContainingType.TypeParameters.Length > 0
=> methodSymbol.ContainingType.TypeParameters[typeArgumentIndex],
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 89655e0b56..946e997c1b 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -2348,9 +2348,17 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
int method_bind_count = 0;
for (const MethodInterface &imethod : itype.methods) {
- Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output);
+ Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output, false);
ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
+ if (imethod.is_internal) {
+ // No need to generate span overloads for internal methods.
+ continue;
+ }
+
+ method_err = _generate_cs_method(itype, imethod, method_bind_count, output, true);
+ ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
+ "Failed to generate span overload method '" + imethod.name + "' for class '" + itype.name + "'.");
}
// Signals
@@ -2776,7 +2784,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
return OK;
}
-Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) {
+Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span) {
const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type);
ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found.");
@@ -2789,6 +2797,35 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
"' from the editor API. Core API cannot have dependencies on the editor API.");
}
+ if (p_imethod.is_virtual && p_use_span) {
+ return OK;
+ }
+
+ bool has_span_argument = false;
+
+ if (p_use_span) {
+ if (p_imethod.is_vararg) {
+ has_span_argument = true;
+ } else {
+ for (const ArgumentInterface &iarg : p_imethod.arguments) {
+ const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type);
+ ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
+
+ if (arg_type->is_span_compatible) {
+ has_span_argument = true;
+ break;
+ }
+ }
+ }
+
+ if (has_span_argument) {
+ // Span overloads use the same method bind as the array overloads.
+ // Since both overloads are generated one after the other, we can decrease the count here
+ // to ensure the span overload uses the same method bind.
+ p_method_bind_count--;
+ }
+ }
+
String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count);
String arguments_sig;
@@ -2835,6 +2872,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters);
+ bool use_span_for_arg = p_use_span && arg_type->is_span_compatible;
+
// Add the current arguments to the signature
// If the argument has a default value which is not a constant, we will make it Nullable
{
@@ -2846,7 +2885,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += "Nullable<";
}
- arguments_sig += arg_cs_type;
+ if (use_span_for_arg) {
+ arguments_sig += arg_type->c_type_in;
+ } else {
+ arguments_sig += arg_cs_type;
+ }
if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
arguments_sig += "> ";
@@ -2856,7 +2899,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
arguments_sig += iarg.name;
- if (!p_imethod.is_compat && iarg.default_argument.size()) {
+ if (!p_use_span && !p_imethod.is_compat && iarg.default_argument.size()) {
if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {
arguments_sig += " = null";
} else {
@@ -2867,7 +2910,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
icall_params += ", ";
- if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT) {
+ if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT && !use_span_for_arg) {
// The default value of an argument must be constant. Otherwise we make it Nullable and do the following:
// Type arg_in = arg.HasValue ? arg.Value : <non-const default value>;
String arg_or_defval_local = iarg.name;
@@ -2927,6 +2970,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe;
}
+ if (p_use_span && !has_span_argument) {
+ return OK;
+ }
+
// Collect caller name for MethodBind
if (p_imethod.is_vararg) {
icall_params += ", (godot_string_name)MethodName." + p_imethod.proxy_name + ".NativeValue";
@@ -2934,7 +2981,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
// Generate method
{
- if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
+ if (!p_imethod.is_virtual && !p_imethod.requires_object_call && !p_use_span) {
p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n"
<< INDENT1 "private static readonly IntPtr " << method_bind_field << " = ";
@@ -4734,13 +4781,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype = TypeInterface();
itype.name = "VarArg";
itype.cname = itype.name;
- itype.proxy_name = "Variant[]";
+ itype.proxy_name = "ReadOnlySpan<Variant>";
itype.cs_type = "params Variant[]";
- itype.cs_in_expr = "%0 ?? Array.Empty<Variant>()";
+ itype.cs_in_expr = "%0";
// c_type, c_in and c_arg_in are hard-coded in the generator.
// c_out and c_type_out are not applicable to VarArg.
itype.c_arg_in = "&%s_in";
- itype.c_type_in = "Variant[]";
+ itype.c_type_in = "ReadOnlySpan<Variant>";
+ itype.is_span_compatible = true;
builtin_types.insert(itype.cname, itype);
#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \
@@ -4754,9 +4802,10 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \
itype.c_arg_in = "&%s_in"; \
itype.c_type = #m_managed_type; \
- itype.c_type_in = itype.proxy_name; \
+ itype.c_type_in = "ReadOnlySpan<" #m_proxy_t ">"; \
itype.c_type_out = itype.proxy_name; \
itype.c_type_is_disposable_struct = true; \
+ itype.is_span_compatible = true; \
builtin_types.insert(itype.name, itype); \
}
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index 556d287af4..1670aca4b3 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -265,6 +265,7 @@ class BindingsGenerator {
bool is_singleton = false;
bool is_singleton_instance = false;
bool is_ref_counted = false;
+ bool is_span_compatible = false;
/**
* Class is a singleton, but can't be declared as a static class as that would
@@ -840,7 +841,7 @@ class BindingsGenerator {
Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file);
Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
- Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
+ Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output, bool p_use_span);
Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output);
Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 864815866a..db6961fd12 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -174,7 +174,7 @@ namespace Godot.Collections
var keys = Array.CreateTakingOwnershipOfDisposableValue(keysArray);
godot_array valuesArray;
- NativeFuncs.godotsharp_dictionary_keys(ref self, out valuesArray);
+ NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray);
var values = Array.CreateTakingOwnershipOfDisposableValue(valuesArray);
int count = NativeFuncs.godotsharp_dictionary_count(ref self);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index 15b7ce7c73..fc68b11932 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -394,7 +394,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array)
+ public static godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array)
+ {
+ return ConvertSystemArrayToNativePackedByteArray((ReadOnlySpan<byte>)p_array);
+ }
+
+ public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(ReadOnlySpan<byte> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_byte_array();
@@ -417,7 +422,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array)
+ public static godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array)
+ {
+ return ConvertSystemArrayToNativePackedInt32Array((ReadOnlySpan<int>)p_array);
+ }
+
+ public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(ReadOnlySpan<int> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int32_array();
@@ -440,7 +450,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array)
+ public static godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array)
+ {
+ return ConvertSystemArrayToNativePackedInt64Array((ReadOnlySpan<long>)p_array);
+ }
+
+ public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(ReadOnlySpan<long> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_int64_array();
@@ -463,8 +478,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(Span<float> p_array)
+ {
+ return ConvertSystemArrayToNativePackedFloat32Array((ReadOnlySpan<float>)p_array);
+ }
+
public static unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array(
- Span<float> p_array)
+ ReadOnlySpan<float> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float32_array();
@@ -487,8 +507,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(Span<double> p_array)
+ {
+ return ConvertSystemArrayToNativePackedFloat64Array((ReadOnlySpan<double>)p_array);
+ }
+
public static unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array(
- Span<double> p_array)
+ ReadOnlySpan<double> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_float64_array();
@@ -512,6 +537,11 @@ namespace Godot.NativeInterop
public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(Span<string> p_array)
{
+ return ConvertSystemArrayToNativePackedStringArray((ReadOnlySpan<string>)p_array);
+ }
+
+ public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(ReadOnlySpan<string> p_array)
+ {
godot_packed_string_array dest = new godot_packed_string_array();
if (p_array.IsEmpty)
@@ -544,8 +574,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(Span<Vector2> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector2Array((ReadOnlySpan<Vector2>)p_array);
+ }
+
public static unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array(
- Span<Vector2> p_array)
+ ReadOnlySpan<Vector2> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector2_array();
@@ -568,8 +603,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(Span<Vector3> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector3Array((ReadOnlySpan<Vector3>)p_array);
+ }
+
public static unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array(
- Span<Vector3> p_array)
+ ReadOnlySpan<Vector3> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector3_array();
@@ -592,8 +632,13 @@ namespace Godot.NativeInterop
return array;
}
+ public static godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(Span<Vector4> p_array)
+ {
+ return ConvertSystemArrayToNativePackedVector4Array((ReadOnlySpan<Vector4>)p_array);
+ }
+
public static unsafe godot_packed_vector4_array ConvertSystemArrayToNativePackedVector4Array(
- Span<Vector4> p_array)
+ ReadOnlySpan<Vector4> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_vector4_array();
@@ -616,7 +661,12 @@ namespace Godot.NativeInterop
return array;
}
- public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array)
+ public static godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array)
+ {
+ return ConvertSystemArrayToNativePackedColorArray((ReadOnlySpan<Color>)p_array);
+ }
+
+ public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(ReadOnlySpan<Color> p_array)
{
if (p_array.IsEmpty)
return new godot_packed_color_array();
diff --git a/modules/multiplayer/SCsub b/modules/multiplayer/SCsub
index 97f91c5674..f9f4e579e8 100644
--- a/modules/multiplayer/SCsub
+++ b/modules/multiplayer/SCsub
@@ -13,3 +13,10 @@ if env.editor_build:
env_mp.add_source_files(module_obj, "editor/*.cpp")
env.modules_sources += module_obj
+
+if env["tests"]:
+ env_mp.Append(CPPDEFINES=["TESTS_ENABLED"])
+ env_mp.add_source_files(env.modules_sources, "./tests/*.cpp")
+
+ if env["disable_exceptions"]:
+ env_mp.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"])
diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
index 42f32d4848..3277f1ff3e 100644
--- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml
+++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
@@ -65,7 +65,7 @@
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution.
</member>
<member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback" default="Callable()">
- The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
+ The callback to execute when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
</member>
<member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0">
If set to a value greater than [code]0.0[/code], the maximum duration in seconds peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
diff --git a/modules/multiplayer/tests/test_scene_multiplayer.h b/modules/multiplayer/tests/test_scene_multiplayer.h
new file mode 100644
index 0000000000..5e526c9be6
--- /dev/null
+++ b/modules/multiplayer/tests/test_scene_multiplayer.h
@@ -0,0 +1,284 @@
+/**************************************************************************/
+/* test_scene_multiplayer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_SCENE_MULTIPLAYER_H
+#define TEST_SCENE_MULTIPLAYER_H
+
+#include "tests/test_macros.h"
+#include "tests/test_utils.h"
+
+#include "../scene_multiplayer.h"
+
+namespace TestSceneMultiplayer {
+
+static inline Array build_array() {
+ return Array();
+}
+template <typename... Targs>
+static inline Array build_array(Variant item, Targs... Fargs) {
+ Array a = build_array(Fargs...);
+ a.push_front(item);
+ return a;
+}
+
+TEST_CASE("[Multiplayer][SceneMultiplayer] Defaults") {
+ Ref<SceneMultiplayer> scene_multiplayer;
+ scene_multiplayer.instantiate();
+
+ REQUIRE(scene_multiplayer->has_multiplayer_peer());
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ REQUIRE_MESSAGE(Object::cast_to<OfflineMultiplayerPeer>(multiplayer_peer.ptr()) != nullptr, "By default it must be an OfflineMultiplayerPeer instance.");
+ CHECK_EQ(scene_multiplayer->poll(), Error::OK);
+ CHECK_EQ(scene_multiplayer->get_unique_id(), MultiplayerPeer::TARGET_PEER_SERVER);
+ CHECK_EQ(scene_multiplayer->get_peer_ids(), Vector<int>());
+ CHECK_EQ(scene_multiplayer->get_remote_sender_id(), 0);
+ CHECK_EQ(scene_multiplayer->get_root_path(), NodePath());
+ CHECK(scene_multiplayer->get_connected_peers().is_empty());
+ CHECK_FALSE(scene_multiplayer->is_refusing_new_connections());
+ CHECK_FALSE(scene_multiplayer->is_object_decoding_allowed());
+ CHECK(scene_multiplayer->is_server_relay_enabled());
+ CHECK_EQ(scene_multiplayer->get_max_sync_packet_size(), 1350);
+ CHECK_EQ(scene_multiplayer->get_max_delta_packet_size(), 65535);
+ CHECK(scene_multiplayer->is_server());
+}
+
+TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] SceneTree has a OfflineMultiplayerPeer by default") {
+ Ref<SceneMultiplayer> scene_multiplayer = SceneTree::get_singleton()->get_multiplayer();
+ REQUIRE(scene_multiplayer->has_multiplayer_peer());
+
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ REQUIRE_MESSAGE(Object::cast_to<OfflineMultiplayerPeer>(multiplayer_peer.ptr()) != nullptr, "By default it must be an OfflineMultiplayerPeer instance.");
+}
+
+TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Object configuration add/remove") {
+ Ref<SceneMultiplayer> scene_multiplayer;
+ scene_multiplayer.instantiate();
+
+ SUBCASE("Returns invalid parameter") {
+ CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, "ImInvalid"), Error::ERR_INVALID_PARAMETER);
+ CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, "ImInvalid"), Error::ERR_INVALID_PARAMETER);
+
+ NodePath foo_path("/Foo");
+ NodePath bar_path("/Bar");
+ CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK);
+ ERR_PRINT_OFF;
+ CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, bar_path), Error::ERR_INVALID_PARAMETER);
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("Sets root path") {
+ NodePath foo_path("/Foo");
+ CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK);
+
+ CHECK_EQ(scene_multiplayer->get_root_path(), foo_path);
+ }
+
+ SUBCASE("Unsets root path") {
+ NodePath foo_path("/Foo");
+ CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK);
+
+ CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, foo_path), Error::OK);
+ CHECK_EQ(scene_multiplayer->get_root_path(), NodePath());
+ }
+
+ SUBCASE("Add/Remove a MultiplayerSpawner") {
+ Node2D *node = memnew(Node2D);
+ MultiplayerSpawner *spawner = memnew(MultiplayerSpawner);
+
+ CHECK_EQ(scene_multiplayer->object_configuration_add(node, spawner), Error::OK);
+ CHECK_EQ(scene_multiplayer->object_configuration_remove(node, spawner), Error::OK);
+
+ memdelete(spawner);
+ memdelete(node);
+ }
+
+ SUBCASE("Add/Remove a MultiplayerSynchronizer") {
+ Node2D *node = memnew(Node2D);
+ MultiplayerSynchronizer *synchronizer = memnew(MultiplayerSynchronizer);
+
+ CHECK_EQ(scene_multiplayer->object_configuration_add(node, synchronizer), Error::OK);
+ CHECK_EQ(scene_multiplayer->object_configuration_remove(node, synchronizer), Error::OK);
+
+ memdelete(synchronizer);
+ memdelete(node);
+ }
+}
+
+TEST_CASE("[Multiplayer][SceneMultiplayer] Root Path") {
+ Ref<SceneMultiplayer> scene_multiplayer;
+ scene_multiplayer.instantiate();
+
+ SUBCASE("Is set") {
+ NodePath foo_path("/Foo");
+ scene_multiplayer->set_root_path(foo_path);
+
+ CHECK_EQ(scene_multiplayer->get_root_path(), foo_path);
+ }
+
+ SUBCASE("Fails when path is empty") {
+ ERR_PRINT_OFF;
+ scene_multiplayer->set_root_path(NodePath());
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("Fails when path is relative") {
+ NodePath foo_path("Foo");
+ ERR_PRINT_OFF;
+ scene_multiplayer->set_root_path(foo_path);
+ ERR_PRINT_ON;
+
+ CHECK_EQ(scene_multiplayer->get_root_path(), NodePath());
+ }
+}
+
+// This one could be a dummy callback because the current set of test is not actually testing the full auth flow.
+static Variant auth_callback(Variant sv, Variant pvav) {
+ return Variant();
+}
+
+TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Send Authentication") {
+ Ref<SceneMultiplayer> scene_multiplayer;
+ scene_multiplayer.instantiate();
+ SceneTree::get_singleton()->set_multiplayer(scene_multiplayer);
+ scene_multiplayer->set_auth_callback(callable_mp_static(auth_callback));
+
+ SUBCASE("Is properly sent") {
+ SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authenticating");
+
+ // Adding a peer to MultiplayerPeer.
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ int peer_id = 42;
+ multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
+ SIGNAL_CHECK("peer_authenticating", build_array(build_array(peer_id)));
+
+ CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK);
+
+ Vector<int> expected_peer_ids = { peer_id };
+ CHECK_EQ(scene_multiplayer->get_authenticating_peer_ids(), expected_peer_ids);
+
+ SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authenticating");
+ }
+
+ SUBCASE("peer_authentication_failed is emitted when a peer is deleted before authentication is completed") {
+ SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
+
+ // Adding a peer to MultiplayerPeer.
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ int peer_id = 42;
+ multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
+ multiplayer_peer->emit_signal(SNAME("peer_disconnected"), peer_id);
+ SIGNAL_CHECK("peer_authentication_failed", build_array(build_array(peer_id)));
+
+ SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
+ }
+
+ SUBCASE("peer_authentication_failed is emitted when authentication timeout") {
+ SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
+ scene_multiplayer->set_auth_timeout(0.01);
+ CHECK_EQ(scene_multiplayer->get_auth_timeout(), 0.01);
+
+ // Adding two peesr to MultiplayerPeer.
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ int first_peer_id = 42;
+ int second_peer_id = 84;
+ multiplayer_peer->emit_signal(SNAME("peer_connected"), first_peer_id);
+ multiplayer_peer->emit_signal(SNAME("peer_connected"), second_peer_id);
+
+ // Let timeout happens.
+ OS::get_singleton()->delay_usec(500000);
+
+ CHECK_EQ(scene_multiplayer->poll(), Error::OK);
+
+ SIGNAL_CHECK("peer_authentication_failed", build_array(build_array(first_peer_id), build_array(second_peer_id)));
+
+ SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
+ }
+
+ SUBCASE("Fails when there is no MultiplayerPeer configured") {
+ scene_multiplayer->set_multiplayer_peer(nullptr);
+
+ ERR_PRINT_OFF;
+ CHECK_EQ(scene_multiplayer->send_auth(42, Vector<uint8_t>()), Error::ERR_UNCONFIGURED);
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("Fails when the peer to send the auth is not pending") {
+ ERR_PRINT_OFF;
+ CHECK_EQ(scene_multiplayer->send_auth(42, String("It's me").to_ascii_buffer()), Error::ERR_INVALID_PARAMETER);
+ ERR_PRINT_ON;
+ }
+}
+
+TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Complete Authentication") {
+ Ref<SceneMultiplayer> scene_multiplayer;
+ scene_multiplayer.instantiate();
+ SceneTree::get_singleton()->set_multiplayer(scene_multiplayer);
+ scene_multiplayer->set_auth_callback(callable_mp_static(auth_callback));
+
+ SUBCASE("Is properly completed") {
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ int peer_id = 42;
+ multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
+ CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK);
+
+ CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::OK);
+ }
+
+ SUBCASE("Fails when there is no MultiplayerPeer configured") {
+ scene_multiplayer->set_multiplayer_peer(nullptr);
+
+ ERR_PRINT_OFF;
+ CHECK_EQ(scene_multiplayer->complete_auth(42), Error::ERR_UNCONFIGURED);
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("Fails when the peer to complete the auth is not pending") {
+ ERR_PRINT_OFF;
+ CHECK_EQ(scene_multiplayer->complete_auth(42), Error::ERR_INVALID_PARAMETER);
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("Fails to send auth or completed for a second time") {
+ Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
+ int peer_id = 42;
+ multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
+ CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK);
+ CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::OK);
+
+ ERR_PRINT_OFF;
+ CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::ERR_FILE_CANT_WRITE);
+ CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::ERR_FILE_CANT_WRITE);
+ ERR_PRINT_ON;
+ }
+}
+
+} // namespace TestSceneMultiplayer
+
+#endif // TEST_SCENE_MULTIPLAYER_H
diff --git a/modules/noise/doc_classes/FastNoiseLite.xml b/modules/noise/doc_classes/FastNoiseLite.xml
index f2a6c60376..6f6a637893 100644
--- a/modules/noise/doc_classes/FastNoiseLite.xml
+++ b/modules/noise/doc_classes/FastNoiseLite.xml
@@ -91,10 +91,10 @@
Cellular includes both Worley noise and Voronoi diagrams which creates various regions of the same value.
</constant>
<constant name="TYPE_SIMPLEX" value="0" enum="NoiseType">
- As opposed to [constant TYPE_PERLIN], gradients exist in a simplex lattice rather than a grid lattice, avoiding directional artifacts.
+ As opposed to [constant TYPE_PERLIN], gradients exist in a simplex lattice rather than a grid lattice, avoiding directional artifacts. Internally uses FastNoiseLite's OpenSimplex2 noise type.
</constant>
<constant name="TYPE_SIMPLEX_SMOOTH" value="1" enum="NoiseType">
- Modified, higher quality version of [constant TYPE_SIMPLEX], but slower.
+ Modified, higher quality version of [constant TYPE_SIMPLEX], but slower. Internally uses FastNoiseLite's OpenSimplex2S noise type.
</constant>
<constant name="FRACTAL_NONE" value="0" enum="FractalType">
No fractal noise.
diff --git a/modules/squish/SCsub b/modules/squish/SCsub
deleted file mode 100644
index d8e7fbc142..0000000000
--- a/modules/squish/SCsub
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-from misc.utility.scons_hints import *
-
-Import("env")
-Import("env_modules")
-
-env_squish = env_modules.Clone()
-
-# Thirdparty source files
-
-thirdparty_obj = []
-
-if env["builtin_squish"]:
- thirdparty_dir = "#thirdparty/squish/"
- thirdparty_sources = [
- "alpha.cpp",
- "clusterfit.cpp",
- "colourblock.cpp",
- "colourfit.cpp",
- "colourset.cpp",
- "maths.cpp",
- "rangefit.cpp",
- "singlecolourfit.cpp",
- "squish.cpp",
- ]
-
- thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
-
- env_squish.Prepend(CPPPATH=[thirdparty_dir])
-
- env_thirdparty = env_squish.Clone()
- env_thirdparty.disable_warnings()
- env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
- env.modules_sources += thirdparty_obj
-
-
-# Godot source files
-
-module_obj = []
-
-env_squish.add_source_files(module_obj, "*.cpp")
-env.modules_sources += module_obj
-
-# Needed to force rebuilding the module files when the thirdparty library is updated.
-env.Depends(module_obj, thirdparty_obj)
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index 304a09515c..f1dcef6667 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -126,6 +126,7 @@ if env["builtin_harfbuzz"]:
"src/hb-ucd.cc",
"src/hb-unicode.cc",
# "src/hb-uniscribe.cc",
+ "src/OT/Var/VARC/VARC.cc",
]
if freetype_enabled:
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index 8f4f2cba40..4c305f3b3f 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -387,6 +387,7 @@ thirdparty_harfbuzz_sources = [
"src/hb-ucd.cc",
"src/hb-unicode.cc",
# "src/hb-uniscribe.cc",
+ "src/OT/Var/VARC/VARC.cc",
]
if env["freetype_enabled"]:
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 3322300dda..1c6e62650a 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -442,6 +442,8 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) {
}
#else
if (!icu_data_loaded) {
+ UErrorCode err = U_ZERO_ERROR;
+#ifdef ICU_DATA_NAME
String filename = (p_filename.is_empty()) ? String("res://") + _MKSTR(ICU_DATA_NAME) : p_filename;
Ref<FileAccess> f = FileAccess::open(filename, FileAccess::READ);
@@ -451,13 +453,13 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) {
uint64_t len = f->get_length();
icu_data = f->get_buffer(len);
- UErrorCode err = U_ZERO_ERROR;
udata_setCommonData(icu_data.ptr(), &err);
if (U_FAILURE(err)) {
ERR_FAIL_V_MSG(false, u_errorName(err));
}
err = U_ZERO_ERROR;
+#endif
u_init(&err);
if (U_FAILURE(err)) {
ERR_FAIL_V_MSG(false, u_errorName(err));
@@ -1357,7 +1359,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
return false;
}
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const {
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size, bool p_silent) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size);
@@ -1378,7 +1380,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
error = FT_Init_FreeType(&ft_library);
if (error != 0) {
memdelete(fd);
- ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+ }
}
#ifdef MODULE_SVG_ENABLED
FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
@@ -1412,7 +1418,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
FT_Done_Face(fd->face);
fd->face = nullptr;
memdelete(fd);
- ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
+ }
}
}
@@ -1847,7 +1857,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
}
#else
memdelete(fd);
- ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ }
#endif
} else {
// Init bitmap font.
@@ -1858,6 +1872,16 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
return true;
}
+_FORCE_INLINE_ bool TextServerAdvanced::_font_validate(const RID &p_font_rid) const {
+ FontAdvanced *fd = _get_font_data(p_font_rid);
+ ERR_FAIL_NULL_V(fd, false);
+
+ MutexLock lock(fd->mutex);
+ Vector2i size = _get_size(fd, 16);
+ FontForSizeAdvanced *ffsd = nullptr;
+ return _ensure_cache_for_size(fd, size, ffsd, true);
+}
+
_FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_data) {
MutexLock ftlock(ft_mutex);
@@ -5106,6 +5130,10 @@ RID TextServerAdvanced::_find_sys_font_for_text(const RID &p_fdef, const String
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
+ if (!_font_validate(sysf.rid)) {
+ _free_rid(sysf.rid);
+ continue;
+ }
Dictionary var = dvar;
// Select matching style from collection.
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index c63389b1c6..9c8d75b358 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -365,7 +365,8 @@ class TextServerAdvanced : public TextServerExtension {
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
_FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size, bool p_silent = false) const;
+ _FORCE_INLINE_ bool _font_validate(const RID &p_font_rid) const;
_FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 540ba19cac..ce95622f09 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -791,7 +791,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
return false;
}
-_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const {
+_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size, bool p_silent) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size);
@@ -813,7 +813,11 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
error = FT_Init_FreeType(&ft_library);
if (error != 0) {
memdelete(fd);
- ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+ }
}
#ifdef MODULE_SVG_ENABLED
FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
@@ -847,7 +851,11 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
FT_Done_Face(fd->face);
fd->face = nullptr;
memdelete(fd);
- ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
+ }
}
}
@@ -980,7 +988,11 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
}
#else
memdelete(fd);
- ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ }
#endif
}
@@ -989,6 +1001,16 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
return true;
}
+_FORCE_INLINE_ bool TextServerFallback::_font_validate(const RID &p_font_rid) const {
+ FontFallback *fd = _get_font_data(p_font_rid);
+ ERR_FAIL_NULL_V(fd, false);
+
+ MutexLock lock(fd->mutex);
+ Vector2i size = _get_size(fd, 16);
+ FontForSizeFallback *ffsd = nullptr;
+ return _ensure_cache_for_size(fd, size, ffsd, true);
+}
+
_FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontFallback *p_font_data) {
MutexLock ftlock(ft_mutex);
@@ -3920,6 +3942,10 @@ RID TextServerFallback::_find_sys_font_for_text(const RID &p_fdef, const String
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
+ if (!_font_validate(sysf.rid)) {
+ _free_rid(sysf.rid);
+ continue;
+ }
Dictionary var = dvar;
// Select matching style from collection.
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 7f12ad593b..56626c1f6c 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -314,7 +314,8 @@ class TextServerFallback : public TextServerExtension {
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
_FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size, bool p_silent = false) const;
+ _FORCE_INLINE_ bool _font_validate(const RID &p_font_rid) const;
_FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp
index 7b8d14741b..729a6f5561 100644
--- a/modules/vorbis/resource_importer_ogg_vorbis.cpp
+++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp
@@ -155,7 +155,6 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect
char *sync_buf = ogg_sync_buffer(&sync_state, OGG_SYNC_BUFFER_SIZE);
err = ogg_sync_check(&sync_state);
ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg sync error " + itos(err));
- ERR_FAIL_COND_V(cursor > size_t(file_data.size()), Ref<AudioStreamOggVorbis>());
size_t copy_size = file_data.size() - cursor;
if (copy_size > OGG_SYNC_BUFFER_SIZE) {
copy_size = OGG_SYNC_BUFFER_SIZE;
diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp
index a28fc53440..344a0356c5 100644
--- a/modules/websocket/editor/editor_debugger_server_websocket.cpp
+++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp
@@ -77,8 +77,8 @@ Error EditorDebuggerServerWebSocket::start(const String &p_uri) {
// Optionally override
if (!p_uri.is_empty() && p_uri != "ws://") {
- String scheme, path;
- Error err = p_uri.parse_url(scheme, bind_host, bind_port, path);
+ String scheme, path, fragment;
+ Error err = p_uri.parse_url(scheme, bind_host, bind_port, path, fragment);
ERR_FAIL_COND_V(err != OK, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!bind_host.is_valid_ip_address() && bind_host != "*", ERR_INVALID_PARAMETER);
}
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 03a530909b..c5768c9f0b 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -68,8 +68,9 @@ Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_option
String host;
String path;
String scheme;
+ String fragment;
int port = 0;
- Error err = p_url.parse_url(scheme, host, port, path);
+ Error err = p_url.parse_url(scheme, host, port, path, fragment);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
if (scheme.is_empty()) {
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index 0a9a4053e3..0c0a046805 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -482,8 +482,9 @@ Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) {
String host;
String path;
String scheme;
+ String fragment;
int port = 0;
- Error err = p_url.parse_url(scheme, host, port, path);
+ Error err = p_url.parse_url(scheme, host, port, path, fragment);
ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url);
if (scheme.is_empty()) {
scheme = "ws://";
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index bd7192520a..829279e7bb 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -283,7 +283,7 @@
</signals>
<constants>
<constant name="TARGET_RAY_MODE_UNKNOWN" value="0" enum="TargetRayMode">
- We don't know the the target ray mode.
+ We don't know the target ray mode.
</constant>
<constant name="TARGET_RAY_MODE_GAZE" value="1" enum="TargetRayMode">
Target ray originates at the viewer's eyes and points in the direction they are looking.
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 6920f801e5..078b9ab748 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -41,13 +41,11 @@ static JavaClassWrapper *java_class_wrapper = nullptr;
void register_android_api() {
#if !defined(ANDROID_ENABLED)
- // On Android platforms, the `java_class_wrapper` instantiation and the
- // `JNISingleton` registration occurs in
+ // On Android platforms, the `java_class_wrapper` instantiation occurs in
// `platform/android/java_godot_lib_jni.cpp#Java_org_godotengine_godot_GodotLib_setup`
- java_class_wrapper = memnew(JavaClassWrapper); // Dummy
- GDREGISTER_CLASS(JNISingleton);
+ java_class_wrapper = memnew(JavaClassWrapper);
#endif
-
+ GDREGISTER_CLASS(JNISingleton);
GDREGISTER_CLASS(JavaClass);
GDREGISTER_CLASS(JavaObject);
GDREGISTER_CLASS(JavaClassWrapper);
@@ -108,7 +106,7 @@ Ref<JavaClass> JavaObject::get_java_class() const {
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
-Ref<JavaClass> JavaClassWrapper::wrap(const String &) {
+Ref<JavaClass> JavaClassWrapper::_wrap(const String &, bool) {
return Ref<JavaClass>();
}
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index 71f9c32318..c74cef8dd0 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -262,6 +262,8 @@ class JavaClassWrapper : public Object {
bool _get_type_sig(JNIEnv *env, jobject obj, uint32_t &sig, String &strsig);
#endif
+ Ref<JavaClass> _wrap(const String &p_class, bool p_allow_private_methods_access);
+
static JavaClassWrapper *singleton;
protected:
@@ -270,15 +272,14 @@ protected:
public:
static JavaClassWrapper *get_singleton() { return singleton; }
- Ref<JavaClass> wrap(const String &p_class);
+ Ref<JavaClass> wrap(const String &p_class) {
+ return _wrap(p_class, false);
+ }
#ifdef ANDROID_ENABLED
- Ref<JavaClass> wrap_jclass(jclass p_class);
-
- JavaClassWrapper(jobject p_activity = nullptr);
-#else
- JavaClassWrapper();
+ Ref<JavaClass> wrap_jclass(jclass p_class, bool p_allow_private_methods_access = false);
#endif
+ JavaClassWrapper();
};
#endif // JAVA_CLASS_WRAPPER_H
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index 06afc4eb78..5e940819bc 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -31,193 +31,53 @@
#ifndef JNI_SINGLETON_H
#define JNI_SINGLETON_H
+#include "java_class_wrapper.h"
+
#include "core/config/engine.h"
#include "core/variant/variant.h"
-#ifdef ANDROID_ENABLED
-#include "jni_utils.h"
-#endif
-
class JNISingleton : public Object {
GDCLASS(JNISingleton, Object);
-#ifdef ANDROID_ENABLED
struct MethodData {
- jmethodID method;
Variant::Type ret_type;
Vector<Variant::Type> argtypes;
};
- jobject instance;
RBMap<StringName, MethodData> method_map;
-#endif
+ Ref<JavaObject> wrapped_object;
public:
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
-#ifdef ANDROID_ENABLED
- RBMap<StringName, MethodData>::Element *E = method_map.find(p_method);
-
- // Check the method we're looking for is in the JNISingleton map and that
- // the arguments match.
- bool call_error = !E || E->get().argtypes.size() != p_argcount;
- if (!call_error) {
- for (int i = 0; i < p_argcount; i++) {
- if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
- call_error = true;
- break;
+ if (wrapped_object.is_valid()) {
+ RBMap<StringName, MethodData>::Element *E = method_map.find(p_method);
+
+ // Check the method we're looking for is in the JNISingleton map and that
+ // the arguments match.
+ bool call_error = !E || E->get().argtypes.size() != p_argcount;
+ if (!call_error) {
+ for (int i = 0; i < p_argcount; i++) {
+ if (!Variant::can_convert(p_args[i]->get_type(), E->get().argtypes[i])) {
+ call_error = true;
+ break;
+ }
}
}
- }
-
- if (call_error) {
- // The method is not in this map, defaulting to the regular instance calls.
- return Object::callp(p_method, p_args, p_argcount, r_error);
- }
-
- ERR_FAIL_NULL_V(instance, Variant());
-
- r_error.error = Callable::CallError::CALL_OK;
-
- jvalue *v = nullptr;
- if (p_argcount) {
- v = (jvalue *)alloca(sizeof(jvalue) * p_argcount);
- }
-
- JNIEnv *env = get_jni_env();
-
- int res = env->PushLocalFrame(16);
-
- ERR_FAIL_COND_V(res != 0, Variant());
-
- List<jobject> to_erase;
- for (int i = 0; i < p_argcount; i++) {
- jvalret vr = _variant_to_jvalue(env, E->get().argtypes[i], p_args[i]);
- v[i] = vr.val;
- if (vr.obj) {
- to_erase.push_back(vr.obj);
+ if (!call_error) {
+ return wrapped_object->callp(p_method, p_args, p_argcount, r_error);
}
}
- Variant ret;
-
- switch (E->get().ret_type) {
- case Variant::NIL: {
- env->CallVoidMethodA(instance, E->get().method, v);
- } break;
- case Variant::BOOL: {
- ret = env->CallBooleanMethodA(instance, E->get().method, v) == JNI_TRUE;
- } break;
- case Variant::INT: {
- ret = env->CallIntMethodA(instance, E->get().method, v);
- } break;
- case Variant::FLOAT: {
- ret = env->CallFloatMethodA(instance, E->get().method, v);
- } break;
- case Variant::STRING: {
- jobject o = env->CallObjectMethodA(instance, E->get().method, v);
- ret = jstring_to_string((jstring)o, env);
- env->DeleteLocalRef(o);
- } break;
- case Variant::PACKED_STRING_ARRAY: {
- jobjectArray arr = (jobjectArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- ret = _jobject_to_variant(env, arr);
-
- env->DeleteLocalRef(arr);
- } break;
- case Variant::PACKED_INT32_ARRAY: {
- jintArray arr = (jintArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- Vector<int> sarr;
- sarr.resize(fCount);
-
- int *w = sarr.ptrw();
- env->GetIntArrayRegion(arr, 0, fCount, w);
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
- case Variant::PACKED_INT64_ARRAY: {
- jlongArray arr = (jlongArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- Vector<int64_t> sarr;
- sarr.resize(fCount);
-
- int64_t *w = sarr.ptrw();
- env->GetLongArrayRegion(arr, 0, fCount, w);
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
- case Variant::PACKED_FLOAT32_ARRAY: {
- jfloatArray arr = (jfloatArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- Vector<float> sarr;
- sarr.resize(fCount);
-
- float *w = sarr.ptrw();
- env->GetFloatArrayRegion(arr, 0, fCount, w);
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
- case Variant::PACKED_FLOAT64_ARRAY: {
- jdoubleArray arr = (jdoubleArray)env->CallObjectMethodA(instance, E->get().method, v);
-
- int fCount = env->GetArrayLength(arr);
- Vector<double> sarr;
- sarr.resize(fCount);
-
- double *w = sarr.ptrw();
- env->GetDoubleArrayRegion(arr, 0, fCount, w);
- ret = sarr;
- env->DeleteLocalRef(arr);
- } break;
- case Variant::DICTIONARY: {
- jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
- ret = _jobject_to_variant(env, obj);
- env->DeleteLocalRef(obj);
-
- } break;
- case Variant::OBJECT: {
- jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
- ret = _jobject_to_variant(env, obj);
- env->DeleteLocalRef(obj);
- } break;
- default: {
- env->PopLocalFrame(nullptr);
- ERR_FAIL_V(Variant());
- } break;
- }
-
- while (to_erase.size()) {
- env->DeleteLocalRef(to_erase.front()->get());
- to_erase.pop_front();
- }
-
- env->PopLocalFrame(nullptr);
-
- return ret;
-#else // ANDROID_ENABLED
-
- // Defaulting to the regular instance calls.
return Object::callp(p_method, p_args, p_argcount, r_error);
-#endif
}
-#ifdef ANDROID_ENABLED
- jobject get_instance() const {
- return instance;
+ Ref<JavaObject> get_wrapped_object() const {
+ return wrapped_object;
}
- void set_instance(jobject p_instance) {
- instance = p_instance;
- }
-
- void add_method(const StringName &p_name, jmethodID p_method, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
+ void add_method(const StringName &p_name, const Vector<Variant::Type> &p_args, Variant::Type p_ret_type) {
MethodData md;
- md.method = p_method;
md.argtypes = p_args;
md.ret_type = p_ret_type;
method_map[p_name] = md;
@@ -232,24 +92,15 @@ public:
ADD_SIGNAL(mi);
}
-#endif
+ JNISingleton() {}
- JNISingleton() {
-#ifdef ANDROID_ENABLED
- instance = nullptr;
-#endif
+ JNISingleton(const Ref<JavaObject> &p_wrapped_object) {
+ wrapped_object = p_wrapped_object;
}
~JNISingleton() {
-#ifdef ANDROID_ENABLED
method_map.clear();
- if (instance) {
- JNIEnv *env = get_jni_env();
- ERR_FAIL_NULL(env);
-
- env->DeleteGlobalRef(instance);
- }
-#endif
+ wrapped_object.unref();
}
};
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index c1053215c6..fa5b970a96 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -304,6 +304,13 @@ int DisplayServerAndroid::virtual_keyboard_get_height() const {
return godot_io_java->get_vk_height();
}
+bool DisplayServerAndroid::has_hardware_keyboard() const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_NULL_V(godot_io_java, false);
+
+ return godot_io_java->has_hardware_keyboard();
+}
+
void DisplayServerAndroid::window_set_window_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window) {
window_event_callback = p_callable;
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 90bda18cfa..65c6a53446 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -138,6 +138,7 @@ public:
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), VirtualKeyboardType p_type = KEYBOARD_TYPE_DEFAULT, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override;
virtual void virtual_keyboard_hide() override;
virtual int virtual_keyboard_get_height() const override;
+ virtual bool has_hardware_keyboard() const override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index cfd258cddc..41f460ca8f 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -3263,8 +3263,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
cmdline.push_back(apk_build_command);
}
+ String addons_directory = ProjectSettings::get_singleton()->globalize_path("res://addons");
+
cmdline.push_back("-p"); // argument to specify the start directory.
cmdline.push_back(build_path); // start directory.
+ cmdline.push_back("-Paddons_directory=" + addons_directory); // path to the addon directory as it may contain jar or aar dependencies
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index fdc5753798..308f126d5d 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -63,6 +63,12 @@ dependencies {
implementation files(pluginsBinaries)
}
+ // Automatically pick up local dependencies in res://addons
+ String addonsDirectory = getAddonsDirectory()
+ if (addonsDirectory != null && !addonsDirectory.isBlank()) {
+ implementation fileTree(dir: "$addonsDirectory", include: ['*.jar', '*.aar'])
+ }
+
// .NET dependencies
String jar = '../../../../modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar'
if (file(jar).exists()) {
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 597a4d5c14..e8921e1bb1 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -408,3 +408,8 @@ ext.shouldUseLegacyPackaging = { ->
// Default behavior for minSdk >= 23
return false
}
+
+ext.getAddonsDirectory = { ->
+ String addonsDirectory = project.hasProperty("addons_directory") ? project.property("addons_directory") : ""
+ return addonsDirectory
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 5b1d09e749..567b134234 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -58,6 +58,8 @@ import org.godotengine.godot.input.GodotEditText
import org.godotengine.godot.input.GodotInputHandler
import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
+import org.godotengine.godot.plugin.AndroidRuntimePlugin
+import org.godotengine.godot.plugin.GodotPlugin
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.godotengine.godot.tts.GodotTTS
import org.godotengine.godot.utils.CommandLineFileParser
@@ -228,7 +230,9 @@ class Godot(private val context: Context) {
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
Log.v(TAG, "Initializing Godot plugin registry")
- GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this))
+ val runtimePlugins = mutableSetOf<GodotPlugin>(AndroidRuntimePlugin(this))
+ runtimePlugins.addAll(primaryHost.getHostPlugins(this))
+ GodotPluginRegistry.initializePluginRegistry(this, runtimePlugins)
if (io == null) {
io = GodotIO(activity)
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 219631284a..f060c7aaff 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -216,6 +216,14 @@ public class GodotIO {
return result;
}
+ public boolean hasHardwareKeyboard() {
+ if (edit != null) {
+ return edit.hasHardwareKeyboard();
+ } else {
+ return false;
+ }
+ }
+
public void showKeyboard(String p_existing_text, int p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null) {
edit.showKeyboard(p_existing_text, GodotEditText.VirtualKeyboardType.values()[p_type], p_max_input_length, p_cursor_start, p_cursor_end);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index c085bb8886..cacc1643e3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -264,7 +264,7 @@ public class GodotEditText extends EditText {
isModifiedKey;
}
- boolean hasHardwareKeyboard() {
+ public boolean hasHardwareKeyboard() {
Configuration config = getResources().getConfiguration();
boolean hasHardwareKeyboardConfig = config.keyboard != Configuration.KEYBOARD_NOKEYS &&
config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt b/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt
new file mode 100644
index 0000000000..edb4e7c357
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/AndroidRuntimePlugin.kt
@@ -0,0 +1,63 @@
+/**************************************************************************/
+/* AndroidRuntimePlugin.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.plugin
+
+import org.godotengine.godot.Godot
+
+/**
+ * Provides access to the Android runtime capabilities.
+ *
+ * For example, from gdscript, developers can use [getApplicationContext] to access system services
+ * and check if the device supports vibration.
+ *
+ * var android_runtime = Engine.get_singleton("AndroidRuntime")
+ * if android_runtime:
+ * print("Checking if the device supports vibration")
+ * var vibrator_service = android_runtime.getApplicationContext().getSystemService("vibrator")
+ * if vibrator_service:
+ * if vibrator_service.hasVibrator():
+ * print("Vibration is supported on device!")
+ * else:
+ * printerr("Vibration is not supported on device")
+ * else:
+ * printerr("Unable to retrieve the vibrator service")
+ * else:
+ * printerr("Couldn't find AndroidRuntime singleton")
+ */
+class AndroidRuntimePlugin(godot: Godot) : GodotPlugin(godot) {
+ override fun getPluginName() = "AndroidRuntime"
+
+ @UsedByGodot
+ fun getApplicationContext() = activity?.applicationContext
+
+ @UsedByGodot
+ override fun getActivity() = super.getActivity()
+}
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index c92717e922..6bedbfd157 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -1120,7 +1120,7 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return false;
}
-Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
+Ref<JavaClass> JavaClassWrapper::_wrap(const String &p_class, bool p_allow_private_methods_access) {
String class_name_dots = p_class.replace("/", ".");
if (class_cache.has(class_name_dots)) {
return class_cache[class_name_dots];
@@ -1175,7 +1175,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);
- if (!(mods & 0x0001)) {
+ if (!(mods & 0x0001) && (is_constructor || !p_allow_private_methods_access)) {
env->DeleteLocalRef(obj);
continue; //not public bye
}
@@ -1336,7 +1336,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
return java_class;
}
-Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) {
+Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class, bool p_allow_private_methods_access) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, Ref<JavaClass>());
@@ -1344,12 +1344,12 @@ Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) {
String class_name_string = jstring_to_string(class_name, env);
env->DeleteLocalRef(class_name);
- return wrap(class_name_string);
+ return _wrap(class_name_string, p_allow_private_methods_access);
}
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
-JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
+JavaClassWrapper::JavaClassWrapper() {
singleton = this;
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 49913b9c30..623db39985 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -63,6 +63,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;IIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
+ _has_hardware_keyboard = p_env->GetMethodID(cls, "hasHardwareKeyboard", "()Z");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
@@ -220,6 +221,16 @@ bool GodotIOJavaWrapper::has_vk() {
return (_show_keyboard != nullptr) && (_hide_keyboard != nullptr);
}
+bool GodotIOJavaWrapper::has_hardware_keyboard() {
+ if (_has_hardware_keyboard) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+ return env->CallBooleanMethod(godot_io_instance, _has_hardware_keyboard);
+ } else {
+ return false;
+ }
+}
+
void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index c113a13040..0a372641cb 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -58,6 +58,7 @@ private:
jmethodID _get_unique_id = 0;
jmethodID _show_keyboard = 0;
jmethodID _hide_keyboard = 0;
+ jmethodID _has_hardware_keyboard = 0;
jmethodID _set_screen_orientation = 0;
jmethodID _get_screen_orientation = 0;
jmethodID _get_system_dir = 0;
@@ -80,6 +81,7 @@ public:
Rect2i get_display_safe_area();
String get_unique_id();
bool has_vk();
+ bool has_hardware_keyboard();
void show_vk(const String &p_existing, int p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end);
void hide_vk();
int get_vk_height();
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 6086f67a1e..1a256959cd 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -32,7 +32,6 @@
#include "android_input_handler.h"
#include "api/java_class_wrapper.h"
-#include "api/jni_singleton.h"
#include "dir_access_jandroid.h"
#include "display_server_android.h"
#include "file_access_android.h"
@@ -209,8 +208,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env
TTS_Android::setup(p_godot_tts);
- java_class_wrapper = memnew(JavaClassWrapper(godot_java->get_activity()));
- GDREGISTER_CLASS(JNISingleton);
+ java_class_wrapper = memnew(JavaClassWrapper);
return true;
}
diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp
index 75c8dd9528..acb18cc5c5 100644
--- a/platform/android/plugin/godot_plugin_jni.cpp
+++ b/platform/android/plugin/godot_plugin_jni.cpp
@@ -30,6 +30,7 @@
#include "godot_plugin_jni.h"
+#include "api/java_class_wrapper.h"
#include "api/jni_singleton.h"
#include "jni_utils.h"
#include "string_android.h"
@@ -57,11 +58,15 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeR
ERR_FAIL_COND_V(jni_singletons.has(singname), false);
- JNISingleton *s = (JNISingleton *)ClassDB::instantiate("JNISingleton");
- s->set_instance(env->NewGlobalRef(obj));
- jni_singletons[singname] = s;
+ jclass java_class = env->GetObjectClass(obj);
+ Ref<JavaClass> java_class_wrapped = JavaClassWrapper::get_singleton()->wrap_jclass(java_class, true);
+ env->DeleteLocalRef(java_class);
- Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s));
+ Ref<JavaObject> plugin_object = memnew(JavaObject(java_class_wrapped, obj));
+ JNISingleton *plugin_singleton = memnew(JNISingleton(plugin_object));
+ jni_singletons[singname] = plugin_singleton;
+
+ Engine::get_singleton()->add_singleton(Engine::Singleton(singname, plugin_singleton));
return true;
}
@@ -75,7 +80,6 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
String mname = jstring_to_string(name, env);
String retval = jstring_to_string(ret, env);
Vector<Variant::Type> types;
- String cs = "(";
int stringCount = env->GetArrayLength(args);
@@ -83,18 +87,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegis
jstring string = (jstring)env->GetObjectArrayElement(args, i);
const String rawString = jstring_to_string(string, env);
types.push_back(get_jni_type(rawString));
- cs += get_jni_sig(rawString);
- }
-
- cs += ")";
- cs += get_jni_sig(retval);
- jclass cls = env->GetObjectClass(s->get_instance());
- jmethodID mid = env->GetMethodID(cls, mname.ascii().get_data(), cs.ascii().get_data());
- if (!mid) {
- print_line("Failed getting method ID " + mname);
}
- s->add_method(mname, mid, types, get_jni_type(retval));
+ s->add_method(mname, types, get_jni_type(retval));
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterSignal(JNIEnv *env, jclass clazz, jstring j_plugin_name, jstring j_signal_name, jobjectArray j_signal_param_types) {
diff --git a/platform/android/rendering_context_driver_vulkan_android.cpp b/platform/android/rendering_context_driver_vulkan_android.cpp
index a306a121f8..51fb1ca18f 100644
--- a/platform/android/rendering_context_driver_vulkan_android.cpp
+++ b/platform/android/rendering_context_driver_vulkan_android.cpp
@@ -32,11 +32,7 @@
#ifdef VULKAN_ENABLED
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
const char *RenderingContextDriverVulkanAndroid::_get_platform_surface_extension() const {
return VK_KHR_ANDROID_SURFACE_EXTENSION_NAME;
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 989a7f21f3..20a3a996bc 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -2,7 +2,7 @@ import os
import sys
from typing import TYPE_CHECKING
-from methods import detect_darwin_sdk_path, print_error
+from methods import detect_darwin_sdk_path, print_error, print_warning
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -156,7 +156,7 @@ def configure(env: "SConsEnvironment"):
env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"])
if env["metal"] and env["arch"] != "arm64":
- # Only supported on arm64, so skip it for x86_64 builds.
+ print_warning("Target architecture '{}' does not support the Metal rendering driver".format(env["arch"]))
env["metal"] = False
if env["metal"]:
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index bbb758074d..7f199db997 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -41,11 +41,7 @@
#if defined(VULKAN_ENABLED)
#import "rendering_context_driver_vulkan_ios.h"
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
#endif // VULKAN_ENABLED
#if defined(METAL_ENABLED)
@@ -228,6 +224,7 @@ public:
void virtual_keyboard_set_height(int height);
virtual int virtual_keyboard_get_height() const override;
+ virtual bool has_hardware_keyboard() const override;
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index e51d43bd89..dcc6ce9218 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -45,6 +45,8 @@
#import <sys/utsname.h>
+#import <GameController/GameController.h>
+
static const float kDisplayServerIOSAcceleration = 1.f;
DisplayServerIOS *DisplayServerIOS::get_singleton() {
@@ -756,6 +758,14 @@ int DisplayServerIOS::virtual_keyboard_get_height() const {
return virtual_keyboard_height;
}
+bool DisplayServerIOS::has_hardware_keyboard() const {
+ if (@available(iOS 14.0, *)) {
+ return [GCKeyboard coalescedKeyboard];
+ } else {
+ return false;
+ }
+}
+
void DisplayServerIOS::clipboard_set(const String &p_text) {
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
}
diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
index 1d4a944dc4..9e6f191faa 100644
--- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml
+++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml
@@ -96,39 +96,156 @@
<member name="icons/app_store_1024x1024" type="String" setter="" getter="">
App Store application icon file. If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
- <member name="icons/ipad_76x76" type="String" setter="" getter="">
- Home screen application icon file on iPad (1x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ <member name="icons/app_store_1024x1024_dark" type="String" setter="" getter="">
+ App Store application icon file, dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/app_store_1024x1024_tinted" type="String" setter="" getter="">
+ App Store application icon file, tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/icon_1024x1024" type="String" setter="" getter="">
+ Base application icon used to generate other icons. If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/icon_1024x1024_dark" type="String" setter="" getter="">
+ Base application icon used to generate other icons, dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/icon_1024x1024_tinted" type="String" setter="" getter="">
+ Base application icon used to generate other icons, tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_128x128" type="String" setter="" getter="">
+ iOS application 64x64 icon file (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_128x128_dark" type="String" setter="" getter="">
+ iOS application 64x64 icon file (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_128x128_tinted" type="String" setter="" getter="">
+ iOS application 64x64 icon file (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_136x136" type="String" setter="" getter="">
+ iOS application 68x68 icon file (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_136x136_dark" type="String" setter="" getter="">
+ iOS application 68x68 icon file (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_136x136_tinted" type="String" setter="" getter="">
+ iOS application 68x68 icon file (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_192x192" type="String" setter="" getter="">
+ iOS application 64x64 icon file (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_192x192_dark" type="String" setter="" getter="">
+ iOS application 64x64 icon file (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ios_192x192_tinted" type="String" setter="" getter="">
+ iOS application 64x64 icon file (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/ipad_152x152" type="String" setter="" getter="">
Home screen application icon file on iPad (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/ipad_152x152_dark" type="String" setter="" getter="">
+ Home screen application icon file on iPad (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ipad_152x152_tinted" type="String" setter="" getter="">
+ Home screen application icon file on iPad (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/ipad_167x167" type="String" setter="" getter="">
Home screen application icon file on iPad (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/ipad_167x167_dark" type="String" setter="" getter="">
+ Home screen application icon file on iPad (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/ipad_167x167_tinted" type="String" setter="" getter="">
+ Home screen application icon file on iPad (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/iphone_120x120" type="String" setter="" getter="">
Home screen application icon file on iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/iphone_120x120_dark" type="String" setter="" getter="">
+ Home screen application icon file on iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/iphone_120x120_tinted" type="String" setter="" getter="">
+ Home screen application icon file on iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/iphone_180x180" type="String" setter="" getter="">
Home screen application icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/iphone_180x180_dark" type="String" setter="" getter="">
+ Home screen application icon file on iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/iphone_180x180_tinted" type="String" setter="" getter="">
+ Home screen application icon file on iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/notification_40x40" type="String" setter="" getter="">
Notification icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/notification_40x40_dark" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_40x40_tinted" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/notification_60x60" type="String" setter="" getter="">
Notification icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/notification_60x60_dark" type="String" setter="" getter="">
+ Notification icon file on iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_60x60_tinted" type="String" setter="" getter="">
+ Notification icon file on iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_76x76" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_76x76_dark" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_76x76_tinted" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_114x114" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_114x114_dark" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/notification_114x114_tinted" type="String" setter="" getter="">
+ Notification icon file on iPad and iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/settings_58x58" type="String" setter="" getter="">
Application settings icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/settings_58x58_dark" type="String" setter="" getter="">
+ Application settings icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/settings_58x58_tinted" type="String" setter="" getter="">
+ Application settings icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="icons/settings_87x87" type="String" setter="" getter="">
Application settings icon file on iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
- <member name="icons/spotlight_40x40" type="String" setter="" getter="">
- Spotlight icon file on iPad (1x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ <member name="icons/settings_87x87_dark" type="String" setter="" getter="">
+ Application settings icon file on iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/settings_87x87_tinted" type="String" setter="" getter="">
+ Application settings icon file on iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
<member name="icons/spotlight_80x80" type="String" setter="" getter="">
Spotlight icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
</member>
+ <member name="icons/spotlight_80x80_dark" type="String" setter="" getter="">
+ Spotlight icon file on iPad and iPhone (2x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/spotlight_80x80_tinted" type="String" setter="" getter="">
+ Spotlight icon file on iPad and iPhone (2x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/spotlight_120x120" type="String" setter="" getter="">
+ Spotlight icon file on iPad and iPhone (3x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/spotlight_120x120_dark" type="String" setter="" getter="">
+ Spotlight icon file on iPad and iPhone (3x DPI), dark version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
+ <member name="icons/spotlight_120x120_tinted" type="String" setter="" getter="">
+ Spotlight icon file on iPad and iPhone (3x DPI), tinted version. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url].
+ </member>
<member name="privacy/active_keyboard_access_reasons" type="int" setter="" getter="">
The reasons your app use active keyboard API. See [url=https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api]Describing use of required reason API[/url].
</member>
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index b99e825540..d6cd2e0f3c 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -79,33 +79,37 @@ struct IconInfo {
};
static const IconInfo icon_infos[] = {
- // Home screen on iPhone
- { PNAME("icons/iphone_120x120"), "iphone", "Icon-120.png", "120", "2x", "60x60", false },
- { PNAME("icons/iphone_120x120"), "iphone", "Icon-120.png", "120", "3x", "40x40", false },
- { PNAME("icons/iphone_180x180"), "iphone", "Icon-180.png", "180", "3x", "60x60", false },
+ // Settings on iPhone, iPad Pro, iPad, iPad mini
+ { PNAME("icons/settings_58x58"), "universal", "Icon-58", "58", "2x", "29x29", false },
+ { PNAME("icons/settings_87x87"), "universal", "Icon-87", "87", "3x", "29x29", false },
- // Home screen on iPad
- { PNAME("icons/ipad_76x76"), "ipad", "Icon-76.png", "76", "1x", "76x76", false },
- { PNAME("icons/ipad_152x152"), "ipad", "Icon-152.png", "152", "2x", "76x76", false },
- { PNAME("icons/ipad_167x167"), "ipad", "Icon-167.png", "167", "2x", "83.5x83.5", false },
+ // Notifications on iPhone, iPad Pro, iPad, iPad mini
+ { PNAME("icons/notification_40x40"), "universal", "Icon-40", "40", "2x", "20x20", false },
+ { PNAME("icons/notification_60x60"), "universal", "Icon-60", "60", "3x", "20x20", false },
+ { PNAME("icons/notification_76x76"), "universal", "Icon-76", "76", "2x", "38x38", false },
+ { PNAME("icons/notification_114x114"), "universal", "Icon-114", "114", "3x", "38x38", false },
+
+ // Spotlight on iPhone, iPad Pro, iPad, iPad mini
+ { PNAME("icons/spotlight_80x80"), "universal", "Icon-80", "80", "2x", "40x40", false },
+ { PNAME("icons/spotlight_120x120"), "universal", "Icon-120", "120", "3x", "40x40", false },
+
+ // Home Screen on iPhone
+ { PNAME("icons/iphone_120x120"), "universal", "Icon-120-1", "120", "2x", "60x60", false },
+ { PNAME("icons/iphone_180x180"), "universal", "Icon-180", "180", "3x", "60x60", false },
+
+ // Home Screen on iPad Pro
+ { PNAME("icons/ipad_167x167"), "universal", "Icon-167", "167", "2x", "83.5x83.5", false },
+
+ // Home Screen on iPad, iPad mini
+ { PNAME("icons/ipad_152x152"), "universal", "Icon-152", "152", "2x", "76x76", false },
+
+ { PNAME("icons/ios_128x128"), "universal", "Icon-128", "128", "2x", "64x64", false },
+ { PNAME("icons/ios_192x192"), "universal", "Icon-192", "192", "3x", "64x64", false },
+
+ { PNAME("icons/ios_136x136"), "universal", "Icon-136", "136", "2x", "68x68", false },
// App Store
- { PNAME("icons/app_store_1024x1024"), "ios-marketing", "Icon-1024.png", "1024", "1x", "1024x1024", true },
-
- // Spotlight
- { PNAME("icons/spotlight_40x40"), "ipad", "Icon-40.png", "40", "1x", "40x40", false },
- { PNAME("icons/spotlight_80x80"), "iphone", "Icon-80.png", "80", "2x", "40x40", false },
- { PNAME("icons/spotlight_80x80"), "ipad", "Icon-80.png", "80", "2x", "40x40", false },
-
- // Settings
- { PNAME("icons/settings_58x58"), "iphone", "Icon-58.png", "58", "2x", "29x29", false },
- { PNAME("icons/settings_58x58"), "ipad", "Icon-58.png", "58", "2x", "29x29", false },
- { PNAME("icons/settings_87x87"), "iphone", "Icon-87.png", "87", "3x", "29x29", false },
-
- // Notification
- { PNAME("icons/notification_40x40"), "iphone", "Icon-40.png", "40", "2x", "20x20", false },
- { PNAME("icons/notification_40x40"), "ipad", "Icon-40.png", "40", "2x", "20x20", false },
- { PNAME("icons/notification_60x60"), "iphone", "Icon-60.png", "60", "3x", "20x20", false }
+ { PNAME("icons/app_store_1024x1024"), "universal", "Icon-1024", "1024", "1x", "1024x1024", true },
};
struct APIAccessInfo {
@@ -250,7 +254,7 @@ bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPre
}
bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
- if (p_option.begins_with("privacy") || p_option == "application/generate_simulator_library_if_missing") {
+ if (p_option.begins_with("privacy") || p_option == "application/generate_simulator_library_if_missing" || (p_option.begins_with("icons/") && !p_option.begins_with("icons/icon") && !p_option.begins_with("icons/app_store"))) {
return advanced_options_enabled;
}
@@ -368,11 +372,17 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
}
}
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_dark", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "icons/icon_1024x1024_tinted", PROPERTY_HINT_FILE, "*.svg,*.png,*.webp,*.jpg,*.jpeg"), ""));
+
HashSet<String> used_names;
for (uint64_t i = 0; i < sizeof(icon_infos) / sizeof(icon_infos[0]); ++i) {
if (!used_names.has(icon_infos[i].preset_key)) {
used_names.insert(icon_infos[i].preset_key);
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, icon_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key), PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_dark", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, String(icon_infos[i].preset_key) + "_tinted", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), ""));
}
}
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0));
@@ -883,72 +893,127 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
Color boot_bg_color = GLOBAL_GET("application/boot_splash/bg_color");
+ enum IconColorMode {
+ ICON_NORMAL,
+ ICON_DARK,
+ ICON_TINTED,
+ ICON_MAX,
+ };
+
+ bool first_icon = true;
for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
- IconInfo info = icon_infos[i];
- int side_size = String(info.actual_size_side).to_int();
- String icon_path = p_preset->get(info.preset_key);
- if (icon_path.length() == 0) {
- // Resize main app icon
- icon_path = GLOBAL_GET("application/config/icon");
- Ref<Image> img = memnew(Image);
- Error err = ImageLoader::load_image(icon_path, img);
- if (err != OK) {
- add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
- return ERR_UNCONFIGURED;
- } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
- img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
- Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8);
- new_img->fill(boot_bg_color);
- _blend_and_rotate(new_img, img, false);
- err = new_img->save_png(p_iconset_dir + info.export_name);
- } else {
- img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
- err = img->save_png(p_iconset_dir + info.export_name);
+ for (int color_mode = ICON_NORMAL; color_mode < ICON_MAX; color_mode++) {
+ IconInfo info = icon_infos[i];
+ int side_size = String(info.actual_size_side).to_int();
+ String key = info.preset_key;
+ String exp_name = info.export_name;
+ if (color_mode == ICON_DARK) {
+ key += "_dark";
+ exp_name += "_dark";
+ } else if (color_mode == ICON_TINTED) {
+ key += "_tinted";
+ exp_name += "_tinted";
+ }
+ exp_name += ".png";
+ String icon_path = p_preset->get(key);
+ bool resize_waning = true;
+ if (icon_path.is_empty()) {
+ // Load and resize base icon.
+ key = "icons/icon_1024x1024";
+ if (color_mode == ICON_DARK) {
+ key += "_dark";
+ } else if (color_mode == ICON_TINTED) {
+ key += "_tinted";
+ }
+ icon_path = p_preset->get(key);
+ resize_waning = false;
}
- if (err) {
- add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
- return err;
+ if (icon_path.is_empty()) {
+ if (color_mode != ICON_NORMAL) {
+ continue;
+ }
+ // Resize main app icon.
+ icon_path = GLOBAL_GET("application/config/icon");
+ Ref<Image> img = memnew(Image);
+ Error err = ImageLoader::load_image(icon_path, img);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
+ return ERR_UNCONFIGURED;
+ } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
+ img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8);
+ new_img->fill(boot_bg_color);
+ _blend_and_rotate(new_img, img, false);
+ err = new_img->save_png(p_iconset_dir + exp_name);
+ } else {
+ img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ err = img->save_png(p_iconset_dir + exp_name);
+ }
+ if (err) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
+ return err;
+ }
+ } else {
+ // Load custom icon and resize if required.
+ Ref<Image> img = memnew(Image);
+ Error err = ImageLoader::load_image(icon_path, img);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
+ return ERR_UNCONFIGURED;
+ } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
+ if (resize_waning) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
+ }
+ img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8);
+ new_img->fill(boot_bg_color);
+ _blend_and_rotate(new_img, img, false);
+ err = new_img->save_png(p_iconset_dir + exp_name);
+ } else if (img->get_width() != side_size || img->get_height() != side_size) {
+ if (resize_waning) {
+ add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(side_size, side_size)));
+ }
+ img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
+ err = img->save_png(p_iconset_dir + exp_name);
+ } else if (!icon_path.ends_with(".png")) {
+ err = img->save_png(p_iconset_dir + exp_name);
+ } else {
+ err = da->copy(icon_path, p_iconset_dir + exp_name);
+ }
+
+ if (err) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
+ return err;
+ }
}
- } else {
- // Load custom icon and resize if required
- Ref<Image> img = memnew(Image);
- Error err = ImageLoader::load_image(icon_path, img);
- if (err != OK) {
- add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
- return ERR_UNCONFIGURED;
- } else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
- img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
- Ref<Image> new_img = Image::create_empty(side_size, side_size, false, Image::FORMAT_RGBA8);
- new_img->fill(boot_bg_color);
- _blend_and_rotate(new_img, img, false);
- err = new_img->save_png(p_iconset_dir + info.export_name);
- } else if (img->get_width() != side_size || img->get_height() != side_size) {
- add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(side_size, side_size)));
- img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int()));
- err = img->save_png(p_iconset_dir + info.export_name);
+ sizes += String(info.actual_size_side) + "\n";
+ if (first_icon) {
+ first_icon = false;
} else {
- err = da->copy(icon_path, p_iconset_dir + info.export_name);
+ json_description += ",";
+ }
+ json_description += String("{");
+ if (color_mode != ICON_NORMAL) {
+ json_description += String("\"appearances\":[{");
+ json_description += String("\"appearance\":\"luminosity\",");
+ if (color_mode == ICON_DARK) {
+ json_description += String("\"value\":\"dark\"");
+ } else if (color_mode == ICON_TINTED) {
+ json_description += String("\"value\":\"tinted\"");
+ }
+ json_description += String("}],");
}
-
- if (err) {
- add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
- return err;
+ json_description += String("\"idiom\":") + "\"" + info.idiom + "\",";
+ json_description += String("\"platform\":\"ios\",");
+ json_description += String("\"size\":") + "\"" + info.unscaled_size + "\",";
+ if (String(info.scale) != "1x") {
+ json_description += String("\"scale\":") + "\"" + info.scale + "\",";
}
+ json_description += String("\"filename\":") + "\"" + exp_name + "\"";
+ json_description += String("}");
}
- sizes += String(info.actual_size_side) + "\n";
- if (i > 0) {
- json_description += ",";
- }
- json_description += String("{");
- json_description += String("\"idiom\":") + "\"" + info.idiom + "\",";
- json_description += String("\"size\":") + "\"" + info.unscaled_size + "\",";
- json_description += String("\"scale\":") + "\"" + info.scale + "\",";
- json_description += String("\"filename\":") + "\"" + info.export_name + "\"";
- json_description += String("}");
}
- json_description += "]}";
+ json_description += "],\"info\":{\"author\":\"xcode\",\"version\":1}}";
Ref<FileAccess> json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE);
if (json_file.is_null()) {
diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm
index 35b87ea647..590238be77 100644
--- a/platform/ios/os_ios.mm
+++ b/platform/ios/os_ios.mm
@@ -56,11 +56,7 @@
#import <QuartzCore/CAMetalLayer.h>
#if defined(VULKAN_ENABLED)
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
#endif // VULKAN_ENABLED
#endif
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index d1de760f34..a67434527c 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -256,10 +256,6 @@ def configure(env: "SConsEnvironment"):
if not env["builtin_enet"]:
env.ParseConfig("pkg-config libenet --cflags --libs")
- if not env["builtin_squish"]:
- # libsquish doesn't reliably install its .pc file, so some distros lack it.
- env.Append(LIBS=["libsquish"])
-
if not env["builtin_zstd"]:
env.ParseConfig("pkg-config libzstd --cflags --libs")
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 3fbbc263a0..71c721ca1d 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -1479,12 +1479,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
driver_found = true;
}
}
+#endif // GLES3_ENABLED
if (!driver_found) {
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Video driver not found.");
}
-#endif // GLES3_ENABLED
cursor_set_shape(CURSOR_BUSY);
diff --git a/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp b/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp
index 0417ba95eb..8abcc464ba 100644
--- a/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp
+++ b/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp
@@ -32,11 +32,7 @@
#include "rendering_context_driver_vulkan_wayland.h"
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
const char *RenderingContextDriverVulkanWayland::_get_platform_surface_extension() const {
return VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 97ea1147ec..293623e594 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -2055,7 +2055,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window
return;
}
- if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN) {
+ if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) {
Point2i position = screen_get_position(p_screen);
Size2i size = screen_get_size(p_screen);
diff --git a/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp b/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp
index 3f505d000c..cbcf07852b 100644
--- a/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp
+++ b/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp
@@ -32,11 +32,7 @@
#include "rendering_context_driver_vulkan_x11.h"
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
const char *RenderingContextDriverVulkanX11::_get_platform_surface_extension() const {
return VK_KHR_XLIB_SURFACE_EXTENSION_NAME;
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index e35423d41f..a8968b592e 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -2,12 +2,16 @@ import os
import sys
from typing import TYPE_CHECKING
-from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error
+from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error, print_warning
from platform_methods import detect_arch, detect_mvk
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
+# To match other platforms
+STACK_SIZE = 8388608
+STACK_SIZE_SANITIZERS = 30 * 1024 * 1024
+
def get_name():
return "macOS"
@@ -183,6 +187,10 @@ def configure(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-fsanitize=thread"])
env.Append(LINKFLAGS=["-fsanitize=thread"])
+ env.Append(LINKFLAGS=["-Wl,-stack_size," + hex(STACK_SIZE_SANITIZERS)])
+ else:
+ env.Append(LINKFLAGS=["-Wl,-stack_size," + hex(STACK_SIZE)])
+
if env["use_coverage"]:
env.Append(CCFLAGS=["-ftest-coverage", "-fprofile-arcs"])
env.Append(LINKFLAGS=["-ftest-coverage", "-fprofile-arcs"])
@@ -241,7 +249,7 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"])
if env["metal"] and env["arch"] != "arm64":
- # Only supported on arm64, so skip it for x86_64 builds.
+ print_warning("Target architecture '{}' does not support the Metal rendering driver".format(env["arch"]))
env["metal"] = False
extra_frameworks = set()
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index f6c1d11028..48cc7bbba3 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -1893,6 +1893,12 @@ void DisplayServerMacOS::window_set_current_screen(int p_screen, WindowID p_wind
was_fullscreen = true;
}
+ bool was_maximized = false;
+ if (!was_fullscreen && NSEqualRects([wd.window_object frame], [[wd.window_object screen] visibleFrame])) {
+ [wd.window_object zoom:nil];
+ was_maximized = true;
+ }
+
Rect2i srect = screen_get_usable_rect(p_screen);
Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
Size2i wsize = window_get_size(p_window);
@@ -1901,6 +1907,10 @@ void DisplayServerMacOS::window_set_current_screen(int p_screen, WindowID p_wind
wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3);
window_set_position(wpos, p_window);
+ if (was_maximized) {
+ [wd.window_object zoom:nil];
+ }
+
if (was_fullscreen) {
// Re-enter fullscreen mode.
[wd.window_object toggleFullScreen:nil];
diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
index c261c252ba..dcaba9bbd2 100644
--- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
+++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml
@@ -75,6 +75,13 @@
<member name="codesign/custom_options" type="PackedStringArray" setter="" getter="">
Array of the additional command line arguments passed to the code signing tool.
</member>
+ <member name="codesign/entitlements/additional" type="String" setter="" getter="">
+ Additional data added to the root [code]&lt;dict&gt;[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/entitlements].entitlements[/url] file. The value should be an XML section with pairs of key-value elements, e.g.:
+ [codeblock lang=text]
+ &lt;key&gt;key_name&lt;/key&gt;
+ &lt;string&gt;value&lt;/string&gt;
+ [/codeblock]
+ </member>
<member name="codesign/entitlements/address_book" type="bool" setter="" getter="">
Enable to allow access to contacts in the user's address book, if it's enabled you should also provide usage message in the [member privacy/address_book_usage_description] option. See [url=https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_personal-information_addressbook]com.apple.security.personal-information.addressbook[/url].
</member>
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index c99e9cdd0c..7887e5d0ab 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -327,7 +327,7 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP
}
bool advanced_options_enabled = p_preset->are_advanced_options_enabled();
- if (p_option.begins_with("privacy")) {
+ if (p_option.begins_with("privacy") || p_option == "codesign/entitlements/additional") {
return advanced_options_enabled;
}
}
@@ -501,6 +501,7 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_user_selected", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements/additional", PROPERTY_HINT_MULTILINE_TEXT), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
#ifdef MACOS_ENABLED
@@ -2126,6 +2127,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
+ const String &additional_entitlements = p_preset->get("codesign/entitlements/additional");
+ if (!additional_entitlements.is_empty()) {
+ ent_f->store_line(additional_entitlements);
+ }
+
ent_f->store_line("</dict>");
ent_f->store_line("</plist>");
} else {
@@ -2288,6 +2294,14 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p
}
}
+ if (FileAccess::exists(ent_path)) {
+ print_verbose("entitlements:\n" + FileAccess::get_file_as_string(ent_path));
+ }
+
+ if (FileAccess::exists(hlp_ent_path)) {
+ print_verbose("helper entitlements:\n" + FileAccess::get_file_as_string(hlp_ent_path));
+ }
+
// Clean up temporary entitlements files.
if (FileAccess::exists(hlp_ent_path)) {
DirAccess::remove_file_or_error(hlp_ent_path);
diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm
index 3f7dfac3de..16fdd0d189 100644
--- a/platform/macos/godot_menu_delegate.mm
+++ b/platform/macos/godot_menu_delegate.mm
@@ -102,7 +102,11 @@
} else {
// Otherwise redirect event to the engine.
if (DisplayServer::get_singleton()) {
- [[[NSApplication sharedApplication] keyWindow] sendEvent:event];
+ if ([[NSApplication sharedApplication] keyWindow].sheet) {
+ [[[[NSApplication sharedApplication] keyWindow] sheetParent] sendEvent:event];
+ } else {
+ [[[NSApplication sharedApplication] keyWindow] sendEvent:event];
+ }
}
}
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index 5faab74d7b..efe3c95496 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -214,6 +214,9 @@ Error EditorExportPlatformWeb::_add_manifest_icon(const String &p_path, const St
}
Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects) {
+ List<String> preset_features;
+ get_preset_features(p_preset, &preset_features);
+
String proj_name = GLOBAL_GET("application/config/name");
if (proj_name.is_empty()) {
proj_name = "Godot Game";
@@ -239,7 +242,10 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese
cache_files.push_back(name + ".icon.png");
cache_files.push_back(name + ".apple-touch-icon.png");
}
- cache_files.push_back(name + ".worker.js");
+
+ if (preset_features.find("threads")) {
+ cache_files.push_back(name + ".worker.js");
+ }
cache_files.push_back(name + ".audio.worklet.js");
cache_files.push_back(name + ".audio.position.worklet.js");
replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string();
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index 40fb0c356c..aaf986b966 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -868,7 +868,10 @@ class Bus {
* @returns {void}
*/
static move(fromIndex, toIndex) {
- const movedBus = GodotAudio.Bus.getBus(fromIndex);
+ const movedBus = GodotAudio.Bus.getBusOrNull(fromIndex);
+ if (movedBus == null) {
+ return;
+ }
const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
// Inserts at index.
buses.splice(toIndex - 1, 0, movedBus);
@@ -1424,7 +1427,10 @@ const _GodotAudio = {
* @returns {void}
*/
remove_sample_bus: function (index) {
- const bus = GodotAudio.Bus.getBus(index);
+ const bus = GodotAudio.Bus.getBusOrNull(index);
+ if (bus == null) {
+ return;
+ }
bus.clear();
},
@@ -1454,8 +1460,17 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_send: function (busIndex, sendIndex) {
- const bus = GodotAudio.Bus.getBus(busIndex);
- bus.setSend(GodotAudio.Bus.getBus(sendIndex));
+ const bus = GodotAudio.Bus.getBusOrNull(busIndex);
+ if (bus == null) {
+ // Cannot send from an invalid bus.
+ return;
+ }
+ let targetBus = GodotAudio.Bus.getBusOrNull(sendIndex);
+ if (targetBus == null) {
+ // Send to master.
+ targetBus = GodotAudio.Bus.getBus(0);
+ }
+ bus.setSend(targetBus);
},
/**
@@ -1465,7 +1480,10 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_volume_db: function (busIndex, volumeDb) {
- const bus = GodotAudio.Bus.getBus(busIndex);
+ const bus = GodotAudio.Bus.getBusOrNull(busIndex);
+ if (bus == null) {
+ return;
+ }
bus.setVolumeDb(volumeDb);
},
@@ -1476,7 +1494,10 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_solo: function (busIndex, enable) {
- const bus = GodotAudio.Bus.getBus(busIndex);
+ const bus = GodotAudio.Bus.getBusOrNull(busIndex);
+ if (bus == null) {
+ return;
+ }
bus.solo(enable);
},
@@ -1487,7 +1508,10 @@ const _GodotAudio = {
* @returns {void}
*/
set_sample_bus_mute: function (busIndex, enable) {
- const bus = GodotAudio.Bus.getBus(busIndex);
+ const bus = GodotAudio.Bus.getBusOrNull(busIndex);
+ if (bus == null) {
+ return;
+ }
bus.mute(enable);
},
},
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 4043f3a8c2..0ee52a09a7 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -662,7 +662,7 @@ def get_ar_version(env):
print_warning("Couldn't check version of `ar`.")
return ret
- match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(?:\.(\d+))?", output)
+ match = re.search(r"GNU ar(?: \(GNU Binutils\)| version) (\d+)\.(\d+)(?:\.(\d+))?", output)
if match:
ret["major"] = int(match[1])
ret["minor"] = int(match[2])
@@ -788,8 +788,9 @@ def configure_mingw(env: "SConsEnvironment"):
env["CXX"] = mingw_bin_prefix + "g++"
if try_cmd("as --version", env["mingw_prefix"], env["arch"]):
env["AS"] = mingw_bin_prefix + "as"
- if try_cmd("gcc-ar --version", env["mingw_prefix"], env["arch"]):
- env["AR"] = mingw_bin_prefix + "gcc-ar"
+ ar = "ar" if os.name == "nt" else "gcc-ar"
+ if try_cmd(f"{ar} --version", env["mingw_prefix"], env["arch"]):
+ env["AR"] = mingw_bin_prefix + ar
if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]):
env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib"
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 5b166d0075..ffa3840181 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -1520,6 +1520,7 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
rendering_device->screen_create(window_id);
}
#endif
+ wd.initialized = true;
return window_id;
}
@@ -1813,6 +1814,13 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi
Size2 size = screen_get_size(p_screen);
MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
+ } else if (wd.maximized) {
+ Point2 pos = screen_get_position(p_screen) + _get_screens_origin();
+ Size2 size = screen_get_size(p_screen);
+
+ ShowWindow(wd.hWnd, SW_RESTORE);
+ MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
+ ShowWindow(wd.hWnd, SW_MAXIMIZE);
} else {
Rect2i srect = screen_get_usable_rect(p_screen);
Point2i wpos = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window));
@@ -2058,7 +2066,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window)
return Size2();
}
-void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
+void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
// Windows docs for window styles:
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
@@ -2067,12 +2075,16 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style_ex = WS_EX_WINDOWEDGE;
if (p_main_window) {
r_style_ex |= WS_EX_APPWINDOW;
- r_style |= WS_VISIBLE;
+ if (p_initialized) {
+ r_style |= WS_VISIBLE;
+ }
}
if (p_fullscreen || p_borderless) {
r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past.
- if (p_maximized) {
+ if (p_minimized) {
+ r_style |= WS_MINIMIZE;
+ } else if (p_maximized) {
r_style |= WS_MAXIMIZE;
}
if (!p_fullscreen) {
@@ -2087,13 +2099,19 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
}
} else {
if (p_resizable) {
- if (p_maximized) {
+ if (p_minimized) {
+ r_style = WS_OVERLAPPEDWINDOW | WS_MINIMIZE;
+ } else if (p_maximized) {
r_style = WS_OVERLAPPEDWINDOW | WS_MAXIMIZE;
} else {
r_style = WS_OVERLAPPEDWINDOW;
}
} else {
- r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
+ if (p_minimized) {
+ r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MINIMIZE;
+ } else {
+ r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
+ }
}
}
@@ -2101,7 +2119,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
}
- if (!p_borderless && !p_no_activate_focus) {
+ if (!p_borderless && !p_no_activate_focus && p_initialized) {
r_style |= WS_VISIBLE;
}
@@ -2118,7 +2136,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
DWORD style = 0;
DWORD style_ex = 0;
- _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex);
+ _get_window_style(p_window == MAIN_WINDOW_ID, wd.initialized, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.minimized, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex);
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
@@ -5529,7 +5547,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
DWORD dwExStyle;
DWORD dwStyle;
- _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle);
+ _get_window_style(window_id_counter == MAIN_WINDOW_ID, false, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MINIMIZED, p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle);
RECT WindowRect;
@@ -6361,6 +6379,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
}
+ windows[MAIN_WINDOW_ID].initialized = true;
show_window(MAIN_WINDOW_ID);
#if defined(RD_ENABLED)
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 54e1c9681d..7d6a3e96a6 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -524,6 +524,8 @@ class DisplayServerWindows : public DisplayServer {
bool is_popup = false;
Rect2i parent_safe_rect;
+
+ bool initialized = false;
};
JoypadWindows *joypad = nullptr;
@@ -591,7 +593,7 @@ class DisplayServerWindows : public DisplayServer {
HashMap<int64_t, Vector2> pointer_last_pos;
void _send_window_event(const WindowData &wd, WindowEvent p_event);
- void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
+ void _get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
MouseMode mouse_mode;
int restore_mouse_trails = 0;
diff --git a/platform/windows/rendering_context_driver_vulkan_windows.cpp b/platform/windows/rendering_context_driver_vulkan_windows.cpp
index 445388af89..8ca677fe64 100644
--- a/platform/windows/rendering_context_driver_vulkan_windows.cpp
+++ b/platform/windows/rendering_context_driver_vulkan_windows.cpp
@@ -34,11 +34,7 @@
#include "rendering_context_driver_vulkan_windows.h"
-#ifdef USE_VOLK
-#include <volk.h>
-#else
-#include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
const char *RenderingContextDriverVulkanWindows::_get_platform_surface_extension() const {
return VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp
index 9c9ba93b41..754afb0527 100644
--- a/scene/2d/cpu_particles_2d.cpp
+++ b/scene/2d/cpu_particles_2d.cpp
@@ -1288,7 +1288,7 @@ void CPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
ADD_GROUP("Time", "");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index bfbdb49f22..cfdcbee86a 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -821,19 +821,17 @@ void GPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_amount_ratio", "get_amount_ratio");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_GROUP("Time", "");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,suffix:s"), "set_lifetime", "get_lifetime");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,suffix:s"), "set_pre_process_time", "get_pre_process_time");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio");
ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end");
ADD_GROUP("Collision", "collision_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size");
ADD_GROUP("Drawing", "");
@@ -845,7 +843,9 @@ void GPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "trail_lifetime", PROPERTY_HINT_RANGE, "0.01,10,0.01,or_greater,suffix:s"), "set_trail_lifetime", "get_trail_lifetime");
ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections");
ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions");
-
+ ADD_GROUP("Process Material", "");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material");
BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX);
BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME);
BIND_ENUM_CONSTANT(DRAW_ORDER_REVERSE_LIFETIME);
diff --git a/scene/2d/mesh_instance_2d.cpp b/scene/2d/mesh_instance_2d.cpp
index ae45d431fe..1031a67343 100644
--- a/scene/2d/mesh_instance_2d.cpp
+++ b/scene/2d/mesh_instance_2d.cpp
@@ -54,7 +54,20 @@ void MeshInstance2D::_bind_methods() {
}
void MeshInstance2D::set_mesh(const Ref<Mesh> &p_mesh) {
+ if (mesh == p_mesh) {
+ return;
+ }
+
+ if (mesh.is_valid()) {
+ mesh->disconnect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
+ }
+
mesh = p_mesh;
+
+ if (mesh.is_valid()) {
+ mesh->connect_changed(callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
+ }
+
queue_redraw();
}
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 90bfb4c84c..39cfccf983 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -159,7 +159,7 @@ void Bone2D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
if (skeleton) {
- for (int i = 0; i < skeleton->bones.size(); i++) {
+ for (uint32_t i = 0; i < skeleton->bones.size(); i++) {
if (skeleton->bones[i].bone == this) {
skeleton->bones.remove_at(i);
break;
@@ -555,17 +555,17 @@ void Skeleton2D::_update_bone_setup() {
bones.sort(); //sorting so that they are always in the same order/index
- for (int i = 0; i < bones.size(); i++) {
- bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
- bones.write[i].bone->skeleton_index = i;
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ bones[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
+ bones[i].bone->skeleton_index = i;
Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent());
if (parent_bone) {
- bones.write[i].parent_index = parent_bone->skeleton_index;
+ bones[i].parent_index = parent_bone->skeleton_index;
} else {
- bones.write[i].parent_index = -1;
+ bones[i].parent_index = -1;
}
- bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest();
+ bones[i].local_pose_override = bones[i].bone->get_skeleton_rest();
}
transform_dirty = true;
@@ -594,16 +594,16 @@ void Skeleton2D::_update_transform() {
transform_dirty = false;
- for (int i = 0; i < bones.size(); i++) {
- ERR_CONTINUE(bones[i].parent_index >= i);
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ ERR_CONTINUE(bones[i].parent_index >= (int)i);
if (bones[i].parent_index >= 0) {
- bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
+ bones[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
} else {
- bones.write[i].accum_transform = bones[i].bone->get_transform();
+ bones[i].accum_transform = bones[i].bone->get_transform();
}
}
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse;
RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform);
}
@@ -621,7 +621,7 @@ int Skeleton2D::get_bone_count() const {
Bone2D *Skeleton2D::get_bone(int p_idx) {
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
- ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr);
+ ERR_FAIL_INDEX_V(p_idx, (int)bones.size(), nullptr);
return bones[p_idx].bone;
}
@@ -733,14 +733,14 @@ RID Skeleton2D::get_skeleton() const {
}
void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent) {
- ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!");
- bones.write[p_bone_idx].local_pose_override = p_override;
- bones.write[p_bone_idx].local_pose_override_amount = p_amount;
- bones.write[p_bone_idx].local_pose_override_persistent = p_persistent;
+ ERR_FAIL_INDEX_MSG(p_bone_idx, (int)bones.size(), "Bone index is out of range!");
+ bones[p_bone_idx].local_pose_override = p_override;
+ bones[p_bone_idx].local_pose_override_amount = p_amount;
+ bones[p_bone_idx].local_pose_override_persistent = p_persistent;
}
Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) {
- ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!");
+ ERR_FAIL_INDEX_V_MSG(p_bone_idx, (int)bones.size(), Transform2D(), "Bone index is out of range!");
return bones[p_bone_idx].local_pose_override;
}
@@ -771,7 +771,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
}
// Do not cache the transform changes caused by the modifications!
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
bones[i].bone->copy_transform_to_cache = false;
}
@@ -783,7 +783,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
// Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform.
if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) {
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
if (bones[i].local_pose_override_amount > 0) {
bones[i].bone->set_meta("_local_pose_override_enabled_", true);
@@ -793,7 +793,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
bones[i].bone->propagate_call("force_update_transform");
if (bones[i].local_pose_override_persistent) {
- bones.write[i].local_pose_override_amount = 0.0;
+ bones[i].local_pose_override_amount = 0.0;
}
} else {
// TODO: see if there is a way to undo the override without having to resort to setting every bone's transform.
@@ -804,7 +804,7 @@ void Skeleton2D::execute_modifications(real_t p_delta, int p_execution_mode) {
}
// Cache any future transform changes
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
bones[i].bone->copy_transform_to_cache = true;
}
diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h
index 033bdff41d..16bd6fd34b 100644
--- a/scene/2d/skeleton_2d.h
+++ b/scene/2d/skeleton_2d.h
@@ -123,7 +123,7 @@ class Skeleton2D : public Node2D {
bool local_pose_override_persistent = false;
};
- Vector<Bone> bones;
+ LocalVector<Bone> bones;
bool bone_setup_dirty = true;
void _make_bone_setup_dirty();
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index acbc443a93..5b84bf903f 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -1479,6 +1479,7 @@ void CPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles3D::get_mesh);
ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart);
+ ClassDB::bind_method(D_METHOD("capture_aabb"), &CPUParticles3D::capture_aabb);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index 26a574cd26..a1f32fccbe 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1602,8 +1602,17 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
PackedStringArray LightmapGI::get_configuration_warnings() const {
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
- if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
- warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail."));
+#ifndef MODULE_LIGHTMAPPER_RD_ENABLED
+#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+ warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
+#else
+ warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
+#endif
+ return warnings;
+#endif
+
+ if (!DisplayServer::get_singleton()->can_create_rendering_device()) {
+ warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
return warnings;
}
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 86ce8a881a..85de85a9a6 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -1181,15 +1181,16 @@ void Node3D::_validate_property(PropertyInfo &p_property) const {
}
bool Node3D::_property_can_revert(const StringName &p_name) const {
- if (p_name == "basis") {
+ const String sname = p_name;
+ if (sname == "basis") {
return true;
- } else if (p_name == "scale") {
+ } else if (sname == "scale") {
return true;
- } else if (p_name == "quaternion") {
+ } else if (sname == "quaternion") {
return true;
- } else if (p_name == "rotation") {
+ } else if (sname == "rotation") {
return true;
- } else if (p_name == "position") {
+ } else if (sname == "position") {
return true;
}
return false;
@@ -1198,35 +1199,36 @@ bool Node3D::_property_can_revert(const StringName &p_name) const {
bool Node3D::_property_get_revert(const StringName &p_name, Variant &r_property) const {
bool valid = false;
- if (p_name == "basis") {
+ const String sname = p_name;
+ if (sname == "basis") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Transform3D(variant).get_basis();
} else {
r_property = Basis();
}
- } else if (p_name == "scale") {
+ } else if (sname == "scale") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Transform3D(variant).get_basis().get_scale();
} else {
r_property = Vector3(1.0, 1.0, 1.0);
}
- } else if (p_name == "quaternion") {
+ } else if (sname == "quaternion") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion());
} else {
r_property = Quaternion();
}
- } else if (p_name == "rotation") {
+ } else if (sname == "rotation") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) {
r_property = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order);
} else {
r_property = Vector3();
}
- } else if (p_name == "position") {
+ } else if (sname == "position") {
Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid);
if (valid) {
r_property = Transform3D(variant).get_origin();
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index db9c4db30d..9e4c9b1832 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -81,7 +81,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
return false;
}
- int which = path.get_slicec('/', 1).to_int();
+ uint32_t which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
if (which == bones.size() && what == "name") {
@@ -89,7 +89,7 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
return true;
}
- ERR_FAIL_INDEX_V(which, bones.size(), false);
+ ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false);
if (what == "parent") {
set_bone_parent(which, p_value);
@@ -153,10 +153,10 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
return false;
}
- int which = path.get_slicec('/', 1).to_int();
+ uint32_t which = path.get_slicec('/', 1).to_int();
String what = path.get_slicec('/', 2);
- ERR_FAIL_INDEX_V(which, bones.size(), false);
+ ERR_FAIL_UNSIGNED_INDEX_V(which, bones.size(), false);
if (what == "name") {
r_ret = get_bone_name(which);
@@ -182,7 +182,7 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
}
void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
const String prep = vformat("%s/%d/", PNAME("bones"), i);
p_list->push_back(PropertyInfo(Variant::STRING, prep + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::INT, prep + PNAME("parent"), PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NO_EDITOR));
@@ -241,7 +241,7 @@ void Skeleton3D::_update_process_order() {
return;
}
- Bone *bonesptr = bones.ptrw();
+ Bone *bonesptr = bones.ptr();
int len = bones.size();
parentless_bones.clear();
@@ -276,6 +276,8 @@ void Skeleton3D::_update_process_order() {
concatenated_bone_names = StringName();
+ _update_bones_nested_set();
+
process_order_dirty = false;
emit_signal("bone_list_changed");
@@ -283,7 +285,7 @@ void Skeleton3D::_update_process_order() {
void Skeleton3D::_update_bone_names() const {
String names;
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
if (i > 0) {
names += ",";
}
@@ -331,16 +333,21 @@ void Skeleton3D::_notification(int p_what) {
updating = true;
- Bone *bonesptr = bones.ptrw();
+ Bone *bonesptr = bones.ptr();
int len = bones.size();
+ thread_local LocalVector<bool> bone_global_pose_dirty_backup;
+
// Process modifiers.
_find_modifiers();
if (!modifiers.is_empty()) {
// Store unmodified bone poses.
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
bones_backup[i].save(bones[i]);
}
+ // Store dirty flags for global bone poses.
+ bone_global_pose_dirty_backup = bone_global_pose_dirty;
+
_process_modifiers();
}
@@ -412,9 +419,11 @@ void Skeleton3D::_notification(int p_what) {
if (!modifiers.is_empty()) {
// Restore unmodified bone poses.
- for (int i = 0; i < bones.size(); i++) {
- bones_backup[i].restore(bones.write[i]);
+ for (uint32_t i = 0; i < bones.size(); i++) {
+ bones_backup[i].restore(bones[i]);
}
+ // Restore dirty flags for global bone poses.
+ bone_global_pose_dirty = bone_global_pose_dirty_backup;
}
updating = false;
@@ -457,10 +466,111 @@ void Skeleton3D::_make_modifiers_dirty() {
_update_deferred(UPDATE_FLAG_MODIFIER);
}
+void Skeleton3D::_update_bones_nested_set() {
+ nested_set_offset_to_bone_index.resize(bones.size());
+ bone_global_pose_dirty.resize(bones.size());
+ _make_bone_global_poses_dirty();
+
+ int offset = 0;
+ for (int bone : parentless_bones) {
+ offset += _update_bone_nested_set(bone, offset);
+ }
+}
+
+int Skeleton3D::_update_bone_nested_set(int p_bone, int p_offset) {
+ Bone &bone = bones[p_bone];
+ int offset = p_offset + 1;
+ int span = 1;
+
+ for (int child_bone : bone.child_bones) {
+ int subspan = _update_bone_nested_set(child_bone, offset);
+ offset += subspan;
+ span += subspan;
+ }
+
+ nested_set_offset_to_bone_index[p_offset] = p_bone;
+ bone.nested_set_offset = p_offset;
+ bone.nested_set_span = span;
+
+ return span;
+}
+
+void Skeleton3D::_make_bone_global_poses_dirty() {
+ for (uint32_t i = 0; i < bone_global_pose_dirty.size(); i++) {
+ bone_global_pose_dirty[i] = true;
+ }
+}
+
+void Skeleton3D::_make_bone_global_pose_subtree_dirty(int p_bone) {
+ if (process_order_dirty) {
+ return;
+ }
+
+ const Bone &bone = bones[p_bone];
+ int span_offset = bone.nested_set_offset;
+ // No need to make subtree dirty when bone is already dirty.
+ if (bone_global_pose_dirty[span_offset]) {
+ return;
+ }
+
+ // Make global poses of subtree dirty.
+ int span_end = span_offset + bone.nested_set_span;
+ for (int i = span_offset; i < span_end; i++) {
+ bone_global_pose_dirty[i] = true;
+ }
+}
+
+void Skeleton3D::_update_bone_global_pose(int p_bone) {
+ const int bone_size = bones.size();
+ ERR_FAIL_INDEX(p_bone, bone_size);
+
+ _update_process_order();
+
+ // Global pose is already calculated.
+ int nested_set_offset = bones[p_bone].nested_set_offset;
+ if (!bone_global_pose_dirty[nested_set_offset]) {
+ return;
+ }
+
+ thread_local LocalVector<int> bone_list;
+ bone_list.clear();
+ Transform3D global_pose;
+
+ // Create list of parent bones for which the global pose needs to be recalculated.
+ for (int bone = p_bone; bone >= 0; bone = bones[bone].parent) {
+ int offset = bones[bone].nested_set_offset;
+ // Stop searching when global pose is not dirty.
+ if (!bone_global_pose_dirty[offset]) {
+ global_pose = bones[bone].global_pose;
+ break;
+ }
+
+ bone_list.push_back(bone);
+ }
+
+ // Calculate global poses for all parent bones and the current bone.
+ for (int i = bone_list.size() - 1; i >= 0; i--) {
+ int bone_idx = bone_list[i];
+ Bone &bone = bones[bone_idx];
+ bool bone_enabled = bone.enabled && !show_rest_only;
+ Transform3D bone_pose = bone_enabled ? get_bone_pose(bone_idx) : get_bone_rest(bone_idx);
+
+ global_pose *= bone_pose;
+#ifndef DISABLE_DEPRECATED
+ if (bone.global_pose_override_amount >= CMP_EPSILON) {
+ global_pose = global_pose.interpolate_with(bone.global_pose_override, bone.global_pose_override_amount);
+ }
+#endif // _DISABLE_DEPRECATED
+
+ bone.global_pose = global_pose;
+ bone_global_pose_dirty[bone.nested_set_offset] = false;
+ }
+}
+
Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
- const_cast<Skeleton3D *>(this)->force_update_all_dirty_bones();
+ const_cast<Skeleton3D *>(this)->_update_bone_global_pose(p_bone);
return bones[p_bone].global_pose;
}
@@ -534,7 +644,7 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) {
}
name_to_bone_index.erase(bones[p_bone].name);
- bones.write[p_bone].name = p_name;
+ bones[p_bone].name = p_name;
name_to_bone_index.insert(p_name, p_bone);
version++;
@@ -582,13 +692,13 @@ void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Varian
ERR_FAIL_INDEX(p_bone, bone_size);
if (p_value.get_type() == Variant::NIL) {
- if (bones.write[p_bone].metadata.has(p_key)) {
- bones.write[p_bone].metadata.erase(p_key);
+ if (bones[p_bone].metadata.has(p_key)) {
+ bones[p_bone].metadata.erase(p_key);
}
return;
}
- bones.write[p_bone].metadata.insert(p_key, p_value, false);
+ bones[p_bone].metadata.insert(p_key, p_value, false);
}
bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const {
@@ -615,7 +725,7 @@ void Skeleton3D::set_bone_parent(int p_bone, int p_parent) {
ERR_FAIL_COND(p_parent != -1 && (p_parent < 0));
ERR_FAIL_COND(p_bone == p_parent);
- bones.write[p_bone].parent = p_parent;
+ bones[p_bone].parent = p_parent;
process_order_dirty = true;
rest_dirty = true;
_make_dirty();
@@ -629,11 +739,11 @@ void Skeleton3D::unparent_bone_and_rest(int p_bone) {
int parent = bones[p_bone].parent;
while (parent >= 0) {
- bones.write[p_bone].rest = bones[parent].rest * bones[p_bone].rest;
+ bones[p_bone].rest = bones[parent].rest * bones[p_bone].rest;
parent = bones[parent].parent;
}
- bones.write[p_bone].parent = -1;
+ bones[p_bone].parent = -1;
process_order_dirty = true;
rest_dirty = true;
@@ -669,9 +779,10 @@ void Skeleton3D::set_bone_rest(int p_bone, const Transform3D &p_rest) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].rest = p_rest;
+ bones[p_bone].rest = p_rest;
rest_dirty = true;
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
Transform3D Skeleton3D::get_bone_rest(int p_bone) const {
const int bone_size = bones.size();
@@ -692,9 +803,10 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].enabled = p_enabled;
+ bones[p_bone].enabled = p_enabled;
emit_signal(SceneStringName(bone_enabled_changed), p_bone);
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
bool Skeleton3D::is_bone_enabled(int p_bone) const {
@@ -707,6 +819,7 @@ void Skeleton3D::set_show_rest_only(bool p_enabled) {
show_rest_only = p_enabled;
emit_signal(SceneStringName(show_rest_only_changed));
_make_dirty();
+ _make_bone_global_poses_dirty();
}
bool Skeleton3D::is_show_rest_only() const {
@@ -727,12 +840,13 @@ void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_position = p_pose.origin;
- bones.write[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
- bones.write[p_bone].pose_scale = p_pose.basis.get_scale();
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_position = p_pose.origin;
+ bones[p_bone].pose_rotation = p_pose.basis.get_rotation_quaternion();
+ bones[p_bone].pose_scale = p_pose.basis.get_scale();
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
@@ -740,30 +854,33 @@ void Skeleton3D::set_bone_pose_position(int p_bone, const Vector3 &p_position) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_position = p_position;
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_position = p_position;
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
void Skeleton3D::set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_rotation = p_rotation;
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_rotation = p_rotation;
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
void Skeleton3D::set_bone_pose_scale(int p_bone, const Vector3 &p_scale) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].pose_scale = p_scale;
- bones.write[p_bone].pose_cache_dirty = true;
+ bones[p_bone].pose_scale = p_scale;
+ bones[p_bone].pose_cache_dirty = true;
if (is_inside_tree()) {
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
}
@@ -794,7 +911,7 @@ void Skeleton3D::reset_bone_pose(int p_bone) {
}
void Skeleton3D::reset_bone_poses() {
- for (int i = 0; i < bones.size(); i++) {
+ for (uint32_t i = 0; i < bones.size(); i++) {
reset_bone_pose(i);
}
}
@@ -802,7 +919,7 @@ void Skeleton3D::reset_bone_poses() {
Transform3D Skeleton3D::get_bone_pose(int p_bone) const {
const int bone_size = bones.size();
ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D());
- const_cast<Skeleton3D *>(this)->bones.write[p_bone].update_pose_cache();
+ const_cast<Skeleton3D *>(this)->bones[p_bone].update_pose_cache();
return bones[p_bone].pose_cache;
}
@@ -853,7 +970,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
// Pose changed, rebuild cache of inverses.
const Bone *bonesptr = bones.ptr();
- int len = bones.size();
+ uint32_t len = bones.size();
// Calculate global rests and invert them.
LocalVector<int> bones_to_process;
@@ -877,7 +994,7 @@ Ref<Skin> Skeleton3D::create_skin_from_rest_transforms() {
}
}
- for (int i = 0; i < len; i++) {
+ for (uint32_t i = 0; i < len; i++) {
// The inverse is what is actually required.
skin->set_bind_bone(i, i);
skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
@@ -937,15 +1054,15 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone_idx, bone_size);
- Bone *bonesptr = bones.ptrw();
- thread_local LocalVector<int> bones_to_process;
- bones_to_process.clear();
- bones_to_process.push_back(p_bone_idx);
+ Bone *bonesptr = bones.ptr();
- uint32_t index = 0;
- while (index < bones_to_process.size()) {
- int current_bone_idx = bones_to_process[index];
+ // Loop through nested set.
+ for (int offset = 0; offset < bone_size; offset++) {
+ if (!bone_global_pose_dirty[offset]) {
+ continue;
+ }
+ int current_bone_idx = nested_set_offset_to_bone_index[offset];
Bone &b = bonesptr[current_bone_idx];
bool bone_enabled = b.enabled && !show_rest_only;
@@ -992,13 +1109,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
}
#endif // _DISABLE_DEPRECATED
- // Add the bone's children to the list of bones to be processed.
- int child_bone_size = b.child_bones.size();
- for (int i = 0; i < child_bone_size; i++) {
- bones_to_process.push_back(b.child_bones[i]);
- }
-
- index++;
+ bone_global_pose_dirty[offset] = false;
}
}
@@ -1171,20 +1282,22 @@ void Skeleton3D::_bind_methods() {
#ifndef DISABLE_DEPRECATED
void Skeleton3D::clear_bones_global_pose_override() {
- for (int i = 0; i < bones.size(); i += 1) {
- bones.write[i].global_pose_override_amount = 0;
- bones.write[i].global_pose_override_reset = true;
+ for (uint32_t i = 0; i < bones.size(); i += 1) {
+ bones[i].global_pose_override_amount = 0;
+ bones[i].global_pose_override_reset = true;
}
_make_dirty();
+ _make_bone_global_poses_dirty();
}
void Skeleton3D::set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) {
const int bone_size = bones.size();
ERR_FAIL_INDEX(p_bone, bone_size);
- bones.write[p_bone].global_pose_override_amount = p_amount;
- bones.write[p_bone].global_pose_override = p_pose;
- bones.write[p_bone].global_pose_override_reset = !p_persistent;
+ bones[p_bone].global_pose_override_amount = p_amount;
+ bones[p_bone].global_pose_override = p_pose;
+ bones[p_bone].global_pose_override_reset = !p_persistent;
_make_dirty();
+ _make_bone_global_pose_subtree_dirty(p_bone);
}
Transform3D Skeleton3D::get_bone_global_pose_override(int p_bone) const {
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 07bdeccf2f..0db12600c3 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -107,6 +107,8 @@ private:
Quaternion pose_rotation;
Vector3 pose_scale = Vector3(1, 1, 1);
Transform3D global_pose;
+ int nested_set_offset = 0; // Offset in nested set of bone hierarchy.
+ int nested_set_span = 0; // Subtree span in nested set of bone hierarchy.
void update_pose_cache() {
if (pose_cache_dirty) {
@@ -153,7 +155,7 @@ private:
HashSet<SkinReference *> skin_bindings;
void _skin_changed();
- Vector<Bone> bones;
+ LocalVector<Bone> bones;
bool process_order_dirty = false;
Vector<int> parentless_bones;
@@ -183,6 +185,15 @@ private:
void _make_modifiers_dirty();
LocalVector<BonePoseBackup> bones_backup;
+ // Global bone pose calculation.
+ LocalVector<int> nested_set_offset_to_bone_index; // Map from Bone::nested_set_offset to bone index.
+ LocalVector<bool> bone_global_pose_dirty; // Indexable with Bone::nested_set_offset.
+ void _update_bones_nested_set();
+ int _update_bone_nested_set(int p_bone, int p_offset);
+ void _make_bone_global_poses_dirty();
+ void _make_bone_global_pose_subtree_dirty(int p_bone);
+ void _update_bone_global_pose(int p_bone);
+
#ifndef DISABLE_DEPRECATED
void _add_bone_bind_compat_88791(const String &p_name);
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index a96417738f..a2aef60417 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -245,6 +245,8 @@ AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixe
if (!p_test_only) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.start = 0.0;
+ pi.end = cur_len;
if (play_mode == PLAY_MODE_FORWARD) {
pi.time = cur_playback_time;
pi.delta = cur_delta;
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 664302d45e..eb8bc8c382 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -1117,6 +1117,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
Ref<Animation> a = ai.animation_data.animation;
double time = ai.playback_info.time;
double delta = ai.playback_info.delta;
+ double start = ai.playback_info.start;
+ double end = ai.playback_info.end;
bool seeked = ai.playback_info.seeked;
Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag;
bool is_external_seeking = ai.playback_info.is_external_seeking;
@@ -1168,32 +1170,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (track->root_motion && calc_root) {
double prev_time = time - delta;
if (!backward) {
- if (Animation::is_less_approx(prev_time, 0)) {
+ if (Animation::is_less_approx(prev_time, start)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = 0;
+ prev_time = start;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a_length);
+ prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a_length);
+ prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a_length)) {
+ if (Animation::is_greater_approx(prev_time, end)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a_length;
+ prev_time = end;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a_length);
+ prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a_length);
+ prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
@@ -1208,10 +1210,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
- a->try_position_track_interpolate(i, (double)a_length, &loc[1]);
+ a->try_position_track_interpolate(i, end, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = 0;
+ prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
@@ -1220,10 +1222,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
- a->try_position_track_interpolate(i, 0, &loc[1]);
+ a->try_position_track_interpolate(i, start, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = (double)a_length;
+ prev_time = end;
}
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
@@ -1234,7 +1236,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = !backward ? 0 : (double)a_length;
+ prev_time = !backward ? start : end;
}
{
Vector3 loc;
@@ -1256,32 +1258,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (track->root_motion && calc_root) {
double prev_time = time - delta;
if (!backward) {
- if (Animation::is_less_approx(prev_time, 0)) {
+ if (Animation::is_less_approx(prev_time, start)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = 0;
+ prev_time = start;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a_length);
+ prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a_length);
+ prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a_length)) {
+ if (Animation::is_greater_approx(prev_time, end)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a_length;
+ prev_time = end;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a_length);
+ prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a_length);
+ prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
@@ -1296,10 +1298,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
- a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]);
+ a->try_rotation_track_interpolate(i, end, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = 0;
+ prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
@@ -1308,9 +1310,9 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
- a->try_rotation_track_interpolate(i, 0, &rot[1]);
+ a->try_rotation_track_interpolate(i, start, &rot[1]);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = (double)a_length;
+ prev_time = end;
}
}
Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]);
@@ -1321,7 +1323,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_rotation_track_interpolate(i, time, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = !backward ? 0 : (double)a_length;
+ prev_time = !backward ? start : end;
}
{
Quaternion rot;
@@ -1343,32 +1345,32 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (track->root_motion && calc_root) {
double prev_time = time - delta;
if (!backward) {
- if (Animation::is_less_approx(prev_time, 0)) {
+ if (Animation::is_less_approx(prev_time, start)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = 0;
+ prev_time = start;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a_length);
+ prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a_length);
+ prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a_length)) {
+ if (Animation::is_greater_approx(prev_time, end)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a_length;
+ prev_time = end;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a_length);
+ prev_time = Math::fposmod(prev_time - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a_length);
+ prev_time = Math::pingpong(prev_time - start, end - start) + start;
} break;
default:
break;
@@ -1383,10 +1385,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
- a->try_scale_track_interpolate(i, (double)a_length, &scale[1]);
+ a->try_scale_track_interpolate(i, end, &scale[1]);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
- prev_time = 0;
+ prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
@@ -1395,10 +1397,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
- a->try_scale_track_interpolate(i, 0, &scale[1]);
+ a->try_scale_track_interpolate(i, start, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = (double)a_length;
+ prev_time = end;
}
}
Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]);
@@ -1409,7 +1411,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, time, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = !backward ? 0 : (double)a_length;
+ prev_time = !backward ? start : end;
}
{
Vector3 scale;
@@ -1671,6 +1673,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (!player2) {
continue;
}
+ // TODO: Make it possible to embed section info in animation track keys.
if (seeked) {
// Seek.
int idx = a->track_find_key(i, time, Animation::FIND_MODE_NEAREST, true);
@@ -1683,19 +1686,19 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
Ref<Animation> anim = player2->get_animation(anim_name);
- double at_anim_pos = 0.0;
+ double at_anim_pos = start;
switch (anim->get_loop_mode()) {
case Animation::LOOP_NONE: {
- if (!is_external_seeking && ((!backward && Animation::is_greater_or_equal_approx(time, pos + (double)anim->get_length())) || (backward && Animation::is_less_or_equal_approx(time, pos)))) {
+ if (!is_external_seeking && ((!backward && Animation::is_greater_or_equal_approx(time, pos + end)) || (backward && Animation::is_less_or_equal_approx(time, pos + start)))) {
continue; // Do nothing if current time is outside of length when started.
}
- at_anim_pos = MIN((double)anim->get_length(), time - pos); // Seek to end.
+ at_anim_pos = MIN(end, time - pos); // Seek to end.
} break;
case Animation::LOOP_LINEAR: {
- at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop.
+ at_anim_pos = Math::fposmod(time - pos - start, end - start) + start; // Seek to loop.
} break;
case Animation::LOOP_PINGPONG: {
- at_anim_pos = Math::pingpong(time - pos, (double)a_length);
+ at_anim_pos = Math::pingpong(time - pos - start, end - start) + start;
} break;
default:
break;
@@ -2092,6 +2095,8 @@ Ref<AnimatedValuesBackup> AnimationMixer::make_backup() {
PlaybackInfo pi;
pi.time = 0;
pi.delta = 0;
+ pi.start = 0;
+ pi.end = reset_anim->get_length();
pi.seeked = true;
pi.weight = 1.0;
make_animation_instance(SceneStringName(RESET), pi);
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index 5482197fbd..27c9a00a9c 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -85,6 +85,8 @@ public:
struct PlaybackInfo {
double time = 0.0;
double delta = 0.0;
+ double start = 0.0;
+ double end = 0.0;
bool seeked = false;
bool is_external_seeking = false;
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index a4aa383a9d..8a2ca47920 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -164,39 +164,41 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
double delta = p_started ? 0 : p_delta * speed;
double next_pos = cd.pos + delta;
- double len = cd.from->animation->get_length();
+ double start = get_section_start_time();
+ double end = get_section_end_time();
+
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
switch (cd.from->animation->get_loop_mode()) {
case Animation::LOOP_NONE: {
- if (Animation::is_less_approx(next_pos, 0)) {
- next_pos = 0;
- } else if (Animation::is_greater_approx(next_pos, len)) {
- next_pos = len;
+ if (Animation::is_less_approx(next_pos, start)) {
+ next_pos = start;
+ } else if (Animation::is_greater_approx(next_pos, end)) {
+ next_pos = end;
}
delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here).
} break;
case Animation::LOOP_LINEAR: {
- if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) {
+ if (Animation::is_less_approx(next_pos, start) && Animation::is_greater_or_equal_approx(cd.pos, start)) {
looped_flag = Animation::LOOPED_FLAG_START;
}
- if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) {
+ if (Animation::is_greater_approx(next_pos, end) && Animation::is_less_or_equal_approx(cd.pos, end)) {
looped_flag = Animation::LOOPED_FLAG_END;
}
- next_pos = Math::fposmod(next_pos, (double)len);
+ next_pos = Math::fposmod(next_pos - start, end - start) + start;
} break;
case Animation::LOOP_PINGPONG: {
- if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) {
+ if (Animation::is_less_approx(next_pos, start) && Animation::is_greater_or_equal_approx(cd.pos, start)) {
cd.speed_scale *= -1.0;
looped_flag = Animation::LOOPED_FLAG_START;
}
- if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) {
+ if (Animation::is_greater_approx(next_pos, end) && Animation::is_less_or_equal_approx(cd.pos, end)) {
cd.speed_scale *= -1.0;
looped_flag = Animation::LOOPED_FLAG_END;
}
- next_pos = Math::pingpong(next_pos, (double)len);
+ next_pos = Math::pingpong(next_pos - start, end - start) + start;
} break;
default:
@@ -208,18 +210,18 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
// End detection.
if (p_is_current) {
if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) {
- if (!backwards && Animation::is_less_or_equal_approx(prev_pos, len) && Math::is_equal_approx(next_pos, len)) {
+ if (!backwards && Animation::is_less_or_equal_approx(prev_pos, end) && Math::is_equal_approx(next_pos, end)) {
// Playback finished.
- next_pos = len; // Snap to the edge.
+ next_pos = end; // Snap to the edge.
end_reached = true;
- end_notify = Animation::is_less_approx(prev_pos, len); // Notify only if not already at the end.
+ end_notify = Animation::is_less_approx(prev_pos, end); // Notify only if not already at the end.
p_blend = 1.0;
}
- if (backwards && Animation::is_greater_or_equal_approx(prev_pos, 0) && Math::is_equal_approx(next_pos, 0)) {
+ if (backwards && Animation::is_greater_or_equal_approx(prev_pos, start) && Math::is_equal_approx(next_pos, start)) {
// Playback finished.
- next_pos = 0; // Snap to the edge.
+ next_pos = start; // Snap to the edge.
end_reached = true;
- end_notify = Animation::is_greater_approx(prev_pos, 0); // Notify only if not already at the beginning.
+ end_notify = Animation::is_greater_approx(prev_pos, start); // Notify only if not already at the beginning.
p_blend = 1.0;
}
}
@@ -231,10 +233,14 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f
if (p_started) {
pi.time = prev_pos;
pi.delta = 0;
+ pi.start = start;
+ pi.end = end;
pi.seeked = true;
} else {
pi.time = next_pos;
pi.delta = delta;
+ pi.start = start;
+ pi.end = end;
pi.seeked = p_seeked;
}
if (Math::is_zero_approx(pi.delta) && backwards) {
@@ -378,6 +384,14 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b
play(p_name, p_custom_blend, -1, true);
}
+void AnimationPlayer::play_section_with_markers_backwards(const StringName &p_name, const StringName &p_start_marker, const StringName &p_end_marker, double p_custom_blend) {
+ play_section_with_markers(p_name, p_start_marker, p_end_marker, p_custom_blend, -1, true);
+}
+
+void AnimationPlayer::play_section_backwards(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend) {
+ play_section(p_name, p_start_time, p_end_time, -1, true);
+}
+
void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
if (auto_capture) {
play_with_capture(p_name, auto_capture_duration, p_custom_blend, p_custom_scale, p_from_end, auto_capture_transition_type, auto_capture_ease_type);
@@ -387,6 +401,10 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
}
void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
+ play_section_with_markers(p_name, StringName(), StringName(), p_custom_blend, p_custom_scale, p_from_end);
+}
+
+void AnimationPlayer::play_section_with_markers(const StringName &p_name, const StringName &p_start_marker, const StringName &p_end_marker, double p_custom_blend, float p_custom_scale, bool p_from_end) {
StringName name = p_name;
if (name == StringName()) {
@@ -395,6 +413,38 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo
ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
+ Ref<Animation> animation = animation_set[name].animation;
+
+ ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker));
+ ERR_FAIL_COND_MSG(p_start_marker && !animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, name));
+ ERR_FAIL_COND_MSG(p_end_marker && !animation->has_marker(p_end_marker), vformat("Marker %s not found in animation: %s.", p_end_marker, name));
+
+ double start_time = p_start_marker ? animation->get_marker_time(p_start_marker) : -1;
+ double end_time = p_end_marker ? animation->get_marker_time(p_end_marker) : -1;
+
+ ERR_FAIL_COND_MSG(p_start_marker && p_end_marker && Animation::is_greater_approx(start_time, end_time), vformat("End marker %s is placed earlier than start marker %s in animation: %s.", p_end_marker, p_start_marker, name));
+
+ if (p_start_marker && Animation::is_less_approx(start_time, 0)) {
+ WARN_PRINT_ED(vformat("Negative time start marker: %s is invalid in the section, so the start of the animation: %s is used instead.", p_start_marker, playback.current.from->animation->get_name()));
+ }
+ if (p_end_marker && Animation::is_less_approx(end_time, 0)) {
+ WARN_PRINT_ED(vformat("Negative time end marker: %s is invalid in the section, so the end of the animation: %s is used instead.", p_end_marker, playback.current.from->animation->get_name()));
+ }
+
+ play_section(name, start_time, end_time, p_custom_blend, p_custom_scale, p_from_end);
+}
+
+void AnimationPlayer::play_section(const StringName &p_name, double p_start_time, double p_end_time, double p_custom_blend, float p_custom_scale, bool p_from_end) {
+ StringName name = p_name;
+
+ if (name == StringName()) {
+ name = playback.assigned;
+ }
+
+ ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
+ ERR_FAIL_COND_MSG(p_start_time >= 0 && p_end_time >= 0 && Math::is_equal_approx(p_start_time, p_end_time), "Start time and end time must not equal to each other.");
+ ERR_FAIL_COND_MSG(p_start_time >= 0 && p_end_time >= 0 && Animation::is_greater_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time));
+
Playback &c = playback;
if (c.current.from) {
@@ -442,22 +492,27 @@ void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, flo
c.current.from = &animation_set[name];
c.current.speed_scale = p_custom_scale;
+ c.current.start_time = p_start_time;
+ c.current.end_time = p_end_time;
+
+ double start = get_section_start_time();
+ double end = get_section_end_time();
if (!end_reached) {
playback_queue.clear();
}
if (c.assigned != name) { // Reset.
- c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0;
+ c.current.pos = p_from_end ? end : start;
c.assigned = name;
emit_signal(SNAME("current_animation_changed"), c.assigned);
} else {
- if (p_from_end && Math::is_zero_approx(c.current.pos)) {
+ if (p_from_end && Math::is_equal_approx(c.current.pos, start)) {
// Animation reset but played backwards, set position to the end.
- seek_internal(c.current.from->animation->get_length(), true, true, true);
- } else if (!p_from_end && Math::is_equal_approx(c.current.pos, (double)c.current.from->animation->get_length())) {
+ seek_internal(end, true, true, true);
+ } else if (!p_from_end && Math::is_equal_approx(c.current.pos, end)) {
// Animation resumed but already ended, set position to the beginning.
- seek_internal(0, true, true, true);
+ seek_internal(start, true, true, true);
} else if (playing) {
return;
}
@@ -551,6 +606,8 @@ void AnimationPlayer::set_assigned_animation(const String &p_animation) {
ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation));
playback.current.pos = 0;
playback.current.from = &animation_set[p_animation];
+ playback.current.start_time = -1;
+ playback.current.end_time = -1;
playback.assigned = p_animation;
emit_signal(SNAME("current_animation_changed"), playback.assigned);
}
@@ -603,6 +660,12 @@ void AnimationPlayer::seek_internal(double p_time, bool p_update, bool p_update_
}
}
+ double start = get_section_start_time();
+ double end = get_section_end_time();
+
+ // Clamp the seek position.
+ p_time = CLAMP(p_time, start, end);
+
playback.seeked = true;
playback.internal_seeked = p_is_internal_seek;
@@ -641,6 +704,55 @@ double AnimationPlayer::get_current_animation_length() const {
return playback.current.from->animation->get_length();
}
+void AnimationPlayer::set_section_with_markers(const StringName &p_start_marker, const StringName &p_end_marker) {
+ ERR_FAIL_NULL_MSG(playback.current.from, "AnimationPlayer has no current animation.");
+ ERR_FAIL_COND_MSG(p_start_marker == p_end_marker && p_start_marker, vformat("Start marker and end marker cannot be the same marker: %s.", p_start_marker));
+ ERR_FAIL_COND_MSG(p_start_marker && !playback.current.from->animation->has_marker(p_start_marker), vformat("Marker %s not found in animation: %s.", p_start_marker, playback.current.from->animation->get_name()));
+ ERR_FAIL_COND_MSG(p_end_marker && !playback.current.from->animation->has_marker(p_end_marker), vformat("Marker %s not found in animation: %s.", p_end_marker, playback.current.from->animation->get_name()));
+ double start_time = p_start_marker ? playback.current.from->animation->get_marker_time(p_start_marker) : -1;
+ double end_time = p_end_marker ? playback.current.from->animation->get_marker_time(p_end_marker) : -1;
+ if (p_start_marker && Animation::is_less_approx(start_time, 0)) {
+ WARN_PRINT_ONCE_ED(vformat("Marker %s time must be positive in animation: %s.", p_start_marker, playback.current.from->animation->get_name()));
+ }
+ if (p_end_marker && Animation::is_less_approx(end_time, 0)) {
+ WARN_PRINT_ONCE_ED(vformat("Marker %s time must be positive in animation: %s.", p_end_marker, playback.current.from->animation->get_name()));
+ }
+ set_section(start_time, end_time);
+}
+
+void AnimationPlayer::set_section(double p_start_time, double p_end_time) {
+ ERR_FAIL_NULL_MSG(playback.current.from, "AnimationPlayer has no current animation.");
+ ERR_FAIL_COND_MSG(Animation::is_greater_or_equal_approx(p_start_time, 0) && Animation::is_greater_or_equal_approx(p_end_time, 0) && Animation::is_greater_or_equal_approx(p_start_time, p_end_time), vformat("Start time %f is greater than end time %f.", p_start_time, p_end_time));
+ playback.current.start_time = p_start_time;
+ playback.current.end_time = p_end_time;
+ playback.current.pos = CLAMP(playback.current.pos, get_section_start_time(), get_section_end_time());
+}
+
+void AnimationPlayer::reset_section() {
+ playback.current.start_time = -1;
+ playback.current.end_time = -1;
+}
+
+double AnimationPlayer::get_section_start_time() const {
+ ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.start_time, "AnimationPlayer has no current animation.");
+ if (Animation::is_less_approx(playback.current.start_time, 0) || playback.current.start_time > playback.current.from->animation->get_length()) {
+ return 0;
+ }
+ return playback.current.start_time;
+}
+
+double AnimationPlayer::get_section_end_time() const {
+ ERR_FAIL_NULL_V_MSG(playback.current.from, playback.current.end_time, "AnimationPlayer has no current animation.");
+ if (Animation::is_less_approx(playback.current.end_time, 0) || playback.current.end_time > playback.current.from->animation->get_length()) {
+ return playback.current.from->animation->get_length();
+ }
+ return playback.current.end_time;
+}
+
+bool AnimationPlayer::has_section() const {
+ return Animation::is_greater_or_equal_approx(playback.current.start_time, 0) || Animation::is_greater_or_equal_approx(playback.current.end_time, 0);
+}
+
void AnimationPlayer::set_autoplay(const String &p_name) {
if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect.");
@@ -665,13 +777,14 @@ void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
_clear_caches();
Playback &c = playback;
// c.blend.clear();
+ double start = c.current.from ? get_section_start_time() : 0;
if (p_reset) {
c.blend.clear();
if (p_keep_state) {
- c.current.pos = 0;
+ c.current.pos = start;
} else {
is_stopping = true;
- seek_internal(0, true, true, true);
+ seek_internal(start, true, true, true);
is_stopping = false;
}
c.current.from = nullptr;
@@ -763,20 +876,6 @@ Tween::EaseType AnimationPlayer::get_auto_capture_ease_type() const {
return auto_capture_ease_type;
}
-#ifdef TOOLS_ENABLED
-void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
- const String pf = p_function;
- if (p_idx == 0 && (pf == "play" || pf == "play_backwards" || pf == "has_animation" || pf == "queue")) {
- List<StringName> al;
- get_animation_list(&al);
- for (const StringName &name : al) {
- r_options->push_back(String(name).quote());
- }
- }
- AnimationMixer::get_argument_options(p_function, p_idx, r_options);
-}
-#endif
-
void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) {
AnimationMixer::_animation_removed(p_name, p_library);
@@ -863,7 +962,11 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_auto_capture_ease_type"), &AnimationPlayer::get_auto_capture_ease_type);
ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("play_section_with_markers", "name", "start_marker", "end_marker", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play_section_with_markers, DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("play_section", "name", "start_time", "end_time", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play_section, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(StringName()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("play_section_with_markers_backwards", "name", "start_marker", "end_marker", "custom_blend"), &AnimationPlayer::play_section_with_markers_backwards, DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(StringName()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("play_section_backwards", "name", "start_time", "end_time", "custom_blend"), &AnimationPlayer::play_section_backwards, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(StringName()), DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause);
ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false));
@@ -893,6 +996,14 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position);
ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length);
+ ClassDB::bind_method(D_METHOD("set_section_with_markers", "start_marker", "end_marker"), &AnimationPlayer::set_section_with_markers, DEFVAL(StringName()), DEFVAL(StringName()));
+ ClassDB::bind_method(D_METHOD("set_section", "start_time", "end_time"), &AnimationPlayer::set_section, DEFVAL(-1), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("reset_section"), &AnimationPlayer::reset_section);
+
+ ClassDB::bind_method(D_METHOD("get_section_start_time"), &AnimationPlayer::get_section_start_time);
+ ClassDB::bind_method(D_METHOD("get_section_end_time"), &AnimationPlayer::get_section_end_time);
+ ClassDB::bind_method(D_METHOD("has_section"), &AnimationPlayer::has_section);
+
ClassDB::bind_method(D_METHOD("seek", "seconds", "update", "update_only"), &AnimationPlayer::seek, DEFVAL(false), DEFVAL(false));
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation");
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index e05a2c9935..3223e2522d 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -68,6 +68,8 @@ private:
AnimationData *from = nullptr;
double pos = 0.0;
float speed_scale = 1.0;
+ double start_time = 0.0;
+ double end_time = 0.0;
};
struct Blend {
@@ -177,7 +179,11 @@ public:
Tween::EaseType get_auto_capture_ease_type() const;
void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
+ void play_section_with_markers(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
+ void play_section(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1);
+ void play_section_with_markers_backwards(const StringName &p_name = StringName(), const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName(), double p_custom_blend = -1);
+ void play_section_backwards(const StringName &p_name = StringName(), double p_start_time = -1, double p_end_time = -1, double p_custom_blend = -1);
void play_with_capture(const StringName &p_name = StringName(), double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
void queue(const StringName &p_name);
Vector<String> get_queue();
@@ -207,9 +213,13 @@ public:
double get_current_animation_position() const;
double get_current_animation_length() const;
-#ifdef TOOLS_ENABLED
- void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
-#endif
+ void set_section_with_markers(const StringName &p_start_marker = StringName(), const StringName &p_end_marker = StringName());
+ void set_section(double p_start_time = -1, double p_end_time = -1);
+ void reset_section();
+
+ double get_section_start_time() const;
+ double get_section_end_time() const;
+ bool has_section() const;
virtual void advance(double p_time) override;
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 07c32eef13..22e5238fae 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -535,7 +535,21 @@ SceneDebuggerTree::SceneDebuggerTree(Node *p_root) {
}
}
}
- nodes.push_back(RemoteNode(count, n->get_name(), n->get_class(), n->get_instance_id(), n->get_scene_file_path(), view_flags));
+
+ String class_name;
+ ScriptInstance *script_instance = n->get_script_instance();
+ if (script_instance) {
+ Ref<Script> script = script_instance->get_script();
+ if (script.is_valid()) {
+ class_name = script->get_global_name();
+
+ if (class_name.is_empty()) {
+ // If there is no class_name in this script we just take the script path.
+ class_name = script->get_path();
+ }
+ }
+ }
+ nodes.push_back(RemoteNode(count, n->get_name(), class_name.is_empty() ? n->get_class() : class_name, n->get_instance_id(), n->get_scene_file_path(), view_flags));
}
}
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 635228670d..7346c9dcd3 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -277,15 +277,17 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
code_completion_force_item_center = -1;
queue_redraw();
}
- code_completion_pan_offset += 1.0f;
+ code_completion_pan_offset = 0;
} else if (code_completion_pan_offset >= +1.0) {
if (code_completion_current_selected < code_completion_options.size() - 1) {
code_completion_current_selected++;
code_completion_force_item_center = -1;
queue_redraw();
}
- code_completion_pan_offset -= 1.0f;
+ code_completion_pan_offset = 0;
}
+ accept_event();
+ return;
}
Ref<InputEventMouseButton> mb = p_gui_input;
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 002a738b83..fe4c91cb56 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -245,7 +245,21 @@ void ColorPicker::finish_shaders() {
}
void ColorPicker::set_focus_on_line_edit() {
- callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
+ bool has_hardware_keyboard = true;
+#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+ has_hardware_keyboard = DisplayServer::get_singleton()->has_hardware_keyboard();
+#endif // ANDROID_ENABLED || IOS_ENABLED
+ if (has_hardware_keyboard) {
+ callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
+ } else {
+ // A hack to avoid showing the virtual keyboard when the ColorPicker window popups and
+ // no hardware keyboard is detected on Android and IOS.
+ // This will only focus the LineEdit without editing, the virtual keyboard will only be visible when
+ // we touch the LineEdit to enter edit mode.
+ callable_mp(c_text, &LineEdit::set_editable).call_deferred(false);
+ callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
+ callable_mp(c_text, &LineEdit::set_editable).call_deferred(true);
+ }
}
void ColorPicker::_update_controls() {
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 6f61295a91..d8e9d1bcc0 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -208,6 +208,7 @@ void FileDialog::_notification(int p_what) {
refresh->set_icon(theme_cache.reload);
show_hidden->set_icon(theme_cache.toggle_hidden);
makedir->set_icon(theme_cache.create_folder);
+ show_filename_filter_button->set_icon(theme_cache.toggle_filename_filter);
dir_up->begin_bulk_theme_override();
dir_up->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
@@ -251,6 +252,13 @@ void FileDialog::_notification(int p_what) {
makedir->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
makedir->end_bulk_theme_override();
+ show_filename_filter_button->begin_bulk_theme_override();
+ show_filename_filter_button->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
+ show_filename_filter_button->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
+ show_filename_filter_button->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
+ show_filename_filter_button->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
+ show_filename_filter_button->end_bulk_theme_override();
+
invalidate();
} break;
@@ -277,6 +285,14 @@ void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
}
} break;
+ case Key::F: {
+ if (k->is_command_or_control_pressed()) {
+ show_filename_filter_button->set_pressed(!show_filename_filter_button->is_pressed());
+ } else {
+ handled = false;
+ }
+
+ } break;
case Key::F5: {
invalidate();
} break;
@@ -694,18 +710,24 @@ void FileDialog::update_file_list() {
dirs.sort_custom<FileNoCaseComparator>();
files.sort_custom<FileNoCaseComparator>();
+ String filename_filter_lower = file_name_filter.to_lower();
+
while (!dirs.is_empty()) {
- String &dir_name = dirs.front()->get();
- TreeItem *ti = tree->create_item(root);
- ti->set_text(0, dir_name);
- ti->set_icon(0, theme_cache.folder);
- ti->set_icon_modulate(0, theme_cache.folder_icon_color);
+ const String &dir_name = dirs.front()->get();
+
+ if (filename_filter_lower.is_empty() || dir_name.to_lower().contains(filename_filter_lower)) {
+ TreeItem *ti = tree->create_item(root);
- Dictionary d;
- d["name"] = dir_name;
- d["dir"] = true;
+ ti->set_text(0, dir_name);
+ ti->set_icon(0, theme_cache.folder);
+ ti->set_icon_modulate(0, theme_cache.folder_icon_color);
- ti->set_metadata(0, d);
+ Dictionary d;
+ d["name"] = dir_name;
+ d["dir"] = true;
+
+ ti->set_metadata(0, d);
+ }
dirs.pop_front();
}
@@ -750,7 +772,7 @@ void FileDialog::update_file_list() {
}
}
- if (match) {
+ if (match && (filename_filter_lower.is_empty() || files.front()->get().to_lower().contains(filename_filter_lower))) {
TreeItem *ti = tree->create_item(root);
ti->set_text(0, files.front()->get());
@@ -792,6 +814,26 @@ void FileDialog::_filter_selected(int) {
update_file_list();
}
+void FileDialog::_filename_filter_changed() {
+ update_filename_filter();
+ update_file_list();
+ callable_mp(this, &FileDialog::_tree_select_first).call_deferred();
+}
+
+void FileDialog::_tree_select_first() {
+ if (tree->get_root() && tree->get_root()->get_first_child()) {
+ tree->get_root()->get_first_child()->select(0);
+ }
+}
+
+void FileDialog::_filename_filter_selected() {
+ TreeItem *item = tree->get_selected();
+ if (item) {
+ file->set_text(item->get_text(0));
+ file->emit_signal("text_submitted", file->get_text());
+ }
+}
+
void FileDialog::update_filters() {
filter->clear();
@@ -827,6 +869,30 @@ void FileDialog::update_filters() {
filter->add_item(atr(ETR("All Files")) + " (*)");
}
+void FileDialog::clear_filename_filter() {
+ set_filename_filter("");
+ update_filename_filter_gui();
+ invalidate();
+}
+
+void FileDialog::update_filename_filter_gui() {
+ filename_filter_box->set_visible(show_filename_filter);
+ if (!show_filename_filter) {
+ file_name_filter.clear();
+ }
+ if (filename_filter->get_text() == file_name_filter) {
+ return;
+ }
+ filename_filter->set_text(file_name_filter);
+}
+
+void FileDialog::update_filename_filter() {
+ if (filename_filter->get_text() == file_name_filter) {
+ return;
+ }
+ set_filename_filter(filename_filter->get_text());
+}
+
void FileDialog::clear_filters() {
filters.clear();
update_filters();
@@ -853,10 +919,24 @@ void FileDialog::set_filters(const Vector<String> &p_filters) {
invalidate();
}
+void FileDialog::set_filename_filter(const String &p_filename_filter) {
+ if (file_name_filter == p_filename_filter) {
+ return;
+ }
+ file_name_filter = p_filename_filter;
+ update_filename_filter_gui();
+ emit_signal(SNAME("filename_filter_changed"), filter);
+ invalidate();
+}
+
Vector<String> FileDialog::get_filters() const {
return filters;
}
+String FileDialog::get_filename_filter() const {
+ return file_name_filter;
+}
+
String FileDialog::get_current_dir() const {
return dir->get_text();
}
@@ -1266,6 +1346,9 @@ void FileDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &FileDialog::add_filter, DEFVAL(""));
ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters);
ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters);
+ ClassDB::bind_method(D_METHOD("clear_filename_filter"), &FileDialog::clear_filename_filter);
+ ClassDB::bind_method(D_METHOD("set_filename_filter", "filter"), &FileDialog::set_filename_filter);
+ ClassDB::bind_method(D_METHOD("get_filename_filter"), &FileDialog::get_filename_filter);
ClassDB::bind_method(D_METHOD("get_option_name", "option"), &FileDialog::get_option_name);
ClassDB::bind_method(D_METHOD("get_option_values", "option"), &FileDialog::get_option_values);
ClassDB::bind_method(D_METHOD("get_option_default", "option"), &FileDialog::get_option_default);
@@ -1305,6 +1388,7 @@ void FileDialog::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "filename_filter"), "set_filename_filter", "get_filename_filter");
ADD_ARRAY_COUNT("Options", "option_count", "set_option_count", "get_option_count", "option_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_native_dialog"), "set_use_native_dialog", "get_use_native_dialog");
@@ -1315,6 +1399,7 @@ void FileDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths")));
ADD_SIGNAL(MethodInfo("dir_selected", PropertyInfo(Variant::STRING, "dir")));
+ ADD_SIGNAL(MethodInfo("filename_filter_changed", PropertyInfo(Variant::STRING, "filter")));
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILE);
BIND_ENUM_CONSTANT(FILE_MODE_OPEN_FILES);
@@ -1332,6 +1417,7 @@ void FileDialog::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, reload);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_hidden);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, folder);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, toggle_filename_filter);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, file);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FileDialog, create_folder);
@@ -1363,6 +1449,26 @@ void FileDialog::set_show_hidden_files(bool p_show) {
invalidate();
}
+void FileDialog::set_show_filename_filter(bool p_show) {
+ if (p_show == show_filename_filter) {
+ return;
+ }
+ if (p_show) {
+ filename_filter->grab_focus();
+ } else {
+ if (filename_filter->has_focus()) {
+ tree->call_deferred("grab_focus");
+ }
+ }
+ show_filename_filter = p_show;
+ update_filename_filter_gui();
+ invalidate();
+}
+
+bool FileDialog::get_show_filename_filter() const {
+ return show_filename_filter;
+}
+
bool FileDialog::is_showing_hidden_files() const {
return show_hidden_files;
}
@@ -1446,6 +1552,14 @@ FileDialog::FileDialog() {
show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
hbc->add_child(show_hidden);
+ show_filename_filter_button = memnew(Button);
+ show_filename_filter_button->set_theme_type_variation("FlatButton");
+ show_filename_filter_button->set_toggle_mode(true);
+ show_filename_filter_button->set_pressed(false);
+ show_filename_filter_button->set_tooltip_text(RTR("Toggle the visibility of the filter for file names."));
+ show_filename_filter_button->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_filename_filter));
+ hbc->add_child(show_filename_filter_button);
+
shortcuts_container = memnew(HBoxContainer);
hbc->add_child(shortcuts_container);
@@ -1467,6 +1581,17 @@ FileDialog::FileDialog() {
message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
tree->add_child(message);
+ filename_filter_box = memnew(HBoxContainer);
+ filename_filter_box->add_child(memnew(Label(RTR("Filter:"))));
+ filename_filter = memnew(LineEdit);
+ filename_filter->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
+ filename_filter->set_stretch_ratio(4);
+ filename_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ filename_filter->set_clear_button_enabled(true);
+ filename_filter_box->add_child(filename_filter);
+ filename_filter_box->set_visible(false);
+ vbox->add_child(filename_filter_box);
+
file_box = memnew(HBoxContainer);
file_box->add_child(memnew(Label(ETR("File:"))));
file = memnew(LineEdit);
@@ -1495,6 +1620,8 @@ FileDialog::FileDialog() {
tree->connect("item_activated", callable_mp(this, &FileDialog::_tree_item_activated));
tree->connect("nothing_selected", callable_mp(this, &FileDialog::deselect_all));
dir->connect("text_submitted", callable_mp(this, &FileDialog::_dir_submitted));
+ filename_filter->connect(SceneStringName(text_changed), callable_mp(this, &FileDialog::_filename_filter_changed).unbind(1));
+ filename_filter->connect("text_submitted", callable_mp(this, &FileDialog::_filename_filter_selected).unbind(1));
file->connect("text_submitted", callable_mp(this, &FileDialog::_file_submitted));
filter->connect(SceneStringName(item_selected), callable_mp(this, &FileDialog::_filter_selected));
@@ -1523,6 +1650,7 @@ FileDialog::FileDialog() {
add_child(exterr, false, INTERNAL_MODE_FRONT);
update_filters();
+ update_filename_filter_gui();
update_dir();
set_hide_on_ok(false);
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 9680157f0a..6ef60a0f4f 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -80,6 +80,8 @@ private:
HBoxContainer *shortcuts_container = nullptr;
OptionButton *drives = nullptr;
Tree *tree = nullptr;
+ HBoxContainer *filename_filter_box = nullptr;
+ LineEdit *filename_filter = nullptr;
HBoxContainer *file_box = nullptr;
LineEdit *file = nullptr;
OptionButton *filter = nullptr;
@@ -96,8 +98,11 @@ private:
Button *refresh = nullptr;
Button *show_hidden = nullptr;
+ Button *show_filename_filter_button = nullptr;
Vector<String> filters;
+ String file_name_filter;
+ bool show_filename_filter = false;
Vector<String> local_history;
int local_history_pos = 0;
@@ -119,6 +124,7 @@ private:
Ref<Texture2D> back_folder;
Ref<Texture2D> reload;
Ref<Texture2D> toggle_hidden;
+ Ref<Texture2D> toggle_filename_filter;
Ref<Texture2D> folder;
Ref<Texture2D> file;
Ref<Texture2D> create_folder;
@@ -149,6 +155,8 @@ private:
void update_dir();
void update_file_name();
void update_file_list();
+ void update_filename_filter();
+ void update_filename_filter_gui();
void update_filters();
void _focus_file_text();
@@ -164,6 +172,9 @@ private:
void _save_confirm_pressed();
void _cancel_pressed();
void _filter_selected(int);
+ void _filename_filter_changed();
+ void _filename_filter_selected();
+ void _tree_select_first();
void _make_dir();
void _make_dir_confirm();
void _go_up();
@@ -208,6 +219,9 @@ public:
void add_filter(const String &p_filter, const String &p_description = "");
void set_filters(const Vector<String> &p_filters);
Vector<String> get_filters() const;
+ void clear_filename_filter();
+ void set_filename_filter(const String &p_filename_filter);
+ String get_filename_filter() const;
void set_enable_multiple_selection(bool p_enable);
Vector<String> get_selected_files() const;
@@ -253,6 +267,8 @@ public:
void set_show_hidden_files(bool p_show);
bool is_showing_hidden_files() const;
+ void set_show_filename_filter(bool p_show);
+ bool get_show_filename_filter() const;
static void set_default_show_hidden_files(bool p_show);
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
index d32c75fbcd..90d5b6b36d 100644
--- a/scene/gui/flow_container.cpp
+++ b/scene/gui/flow_container.cpp
@@ -261,6 +261,7 @@ void FlowContainer::_resort() {
}
cached_size = (vertical ? ofs.x : ofs.y) + line_height;
cached_line_count = lines_data.size();
+ cached_line_max_child_count = lines_data.size() > 0 ? lines_data[0].child_count : 0;
}
Size2 FlowContainer::get_minimum_size() const {
@@ -339,6 +340,10 @@ int FlowContainer::get_line_count() const {
return cached_line_count;
}
+int FlowContainer::get_line_max_child_count() const {
+ return cached_line_max_child_count;
+}
+
void FlowContainer::set_alignment(AlignmentMode p_alignment) {
if (alignment == p_alignment) {
return;
diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h
index 65ebc89c78..6a00e5b0e5 100644
--- a/scene/gui/flow_container.h
+++ b/scene/gui/flow_container.h
@@ -52,6 +52,8 @@ public:
private:
int cached_size = 0;
int cached_line_count = 0;
+ int cached_line_max_child_count = 0;
+ int cached_items_on_last_row = 0;
bool vertical = false;
bool reverse_fill = false;
@@ -74,6 +76,7 @@ protected:
public:
int get_line_count() const;
+ int get_line_max_child_count() const;
void set_alignment(AlignmentMode p_alignment);
AlignmentMode get_alignment() const;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 11a6411e65..646757008a 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -260,6 +260,8 @@ PackedStringArray GraphEdit::get_configuration_warnings() const {
}
Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) {
+ ERR_FAIL_NULL_V_MSG(connections_layer, FAILED, "connections_layer is missing.");
+
if (is_node_connected(p_from, p_from_port, p_to, p_to_port)) {
return OK;
}
@@ -313,6 +315,8 @@ bool GraphEdit::is_node_connected(const StringName &p_from, int p_from_port, con
}
void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
for (const List<Ref<Connection>>::Element *E = connections.front(); E; E = E->next()) {
if (E->get()->from_node == p_from && E->get()->from_port == p_from_port && E->get()->to_node == p_to && E->get()->to_port == p_to_port) {
connection_map[p_from].erase(E->get());
@@ -356,6 +360,8 @@ void GraphEdit::_scroll_moved(double) {
}
void GraphEdit::_update_scroll_offset() {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
set_block_minimum_size_adjust(true);
for (int i = 0; i < get_child_count(); i++) {
@@ -524,6 +530,8 @@ void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node
}
void GraphEdit::_graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
_update_graph_frame(p_frame);
minimap->queue_redraw();
@@ -535,6 +543,7 @@ void GraphEdit::_graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, Gr
void GraphEdit::_graph_element_moved(Node *p_node) {
GraphElement *graph_element = Object::cast_to<GraphElement>(p_node);
ERR_FAIL_NULL(graph_element);
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
minimap->queue_redraw();
queue_redraw();
@@ -543,6 +552,7 @@ void GraphEdit::_graph_element_moved(Node *p_node) {
}
void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
GraphNode *graph_node = Object::cast_to<GraphNode>(p_node);
ERR_FAIL_NULL(graph_node);
@@ -558,6 +568,8 @@ void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) {
}
void GraphEdit::_graph_node_rect_changed(GraphNode *p_node) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
// Only invalidate the cache when zooming or the node is moved/resized in graph space.
if (panner->is_panning()) {
return;
@@ -566,7 +578,6 @@ void GraphEdit::_graph_node_rect_changed(GraphNode *p_node) {
for (Ref<Connection> &c : connection_map[p_node->get_name()]) {
c->_cache.dirty = true;
}
-
connections_layer->queue_redraw();
callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred();
@@ -623,7 +634,9 @@ void GraphEdit::add_child_notify(Node *p_child) {
}
graph_element->connect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from).bind(graph_element));
graph_element->connect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request).bind(graph_element));
- graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw));
+ if (connections_layer != nullptr && connections_layer->is_inside_tree()) {
+ graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw));
+ }
graph_element->connect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw));
graph_element->set_scale(Vector2(zoom, zoom));
@@ -640,6 +653,9 @@ void GraphEdit::remove_child_notify(Node *p_child) {
minimap = nullptr;
} else if (p_child == connections_layer) {
connections_layer = nullptr;
+ if (is_inside_tree()) {
+ WARN_PRINT("GraphEdit's connection_layer removed. This should not be done. If you like to remove all GraphElements from a GraphEdit node, do not simply remove all non-internal children but check their type since the connection layer has to be kept non-internal due to technical reasons.");
+ }
}
if (top_layer != nullptr && is_inside_tree()) {
@@ -662,7 +678,9 @@ void GraphEdit::remove_child_notify(Node *p_child) {
for (const Ref<Connection> &conn : connection_map[graph_node->get_name()]) {
conn->_cache.dirty = true;
}
- connections_layer->queue_redraw();
+ if (connections_layer != nullptr && connections_layer->is_inside_tree()) {
+ connections_layer->queue_redraw();
+ }
}
GraphFrame *frame = Object::cast_to<GraphFrame>(graph_element);
@@ -1690,6 +1708,8 @@ void GraphEdit::set_selected(Node *p_child) {
}
void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
ERR_FAIL_COND(p_ev.is_null());
if (panner->gui_input(p_ev, warped_panning ? get_global_rect() : Rect2())) {
return;
@@ -1999,6 +2019,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
} else if (p_ev->is_action("ui_copy", true)) {
emit_signal(SNAME("copy_nodes_request"));
accept_event();
+ } else if (p_ev->is_action("ui_cut", true)) {
+ emit_signal(SNAME("cut_nodes_request"));
+ accept_event();
} else if (p_ev->is_action("ui_paste", true)) {
emit_signal(SNAME("paste_nodes_request"));
accept_event();
@@ -2022,6 +2045,8 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
void GraphEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
h_scrollbar->set_value(h_scrollbar->get_value() - p_scroll_vec.x);
v_scrollbar->set_value(v_scrollbar->get_value() - p_scroll_vec.y);
@@ -2037,6 +2062,8 @@ void GraphEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputE
}
void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
for (Ref<Connection> &c : connection_map[p_from]) {
if (c->from_node == p_from && c->from_port == p_from_port && c->to_node == p_to && c->to_port == p_to_port) {
if (!Math::is_equal_approx(c->activity, p_activity)) {
@@ -2053,6 +2080,8 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por
}
void GraphEdit::reset_all_connection_activity() {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
bool changed = false;
for (Ref<Connection> &conn : connections) {
if (conn->activity > 0) {
@@ -2067,6 +2096,8 @@ void GraphEdit::reset_all_connection_activity() {
}
void GraphEdit::clear_connections() {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
for (Ref<Connection> &c : connections) {
c->_cache.line->queue_free();
}
@@ -2080,7 +2111,9 @@ void GraphEdit::clear_connections() {
}
void GraphEdit::force_connection_drag_end() {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
ERR_FAIL_COND_MSG(!connecting, "Drag end requested without active drag!");
+
connecting = false;
connecting_valid = false;
minimap->queue_redraw();
@@ -2110,6 +2143,8 @@ void GraphEdit::set_zoom(float p_zoom) {
}
void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
p_zoom = CLAMP(p_zoom, zoom_min, zoom_max);
if (zoom == p_zoom) {
return;
@@ -2518,6 +2553,8 @@ bool GraphEdit::is_showing_arrange_button() const {
}
void GraphEdit::override_connections_shader(const Ref<Shader> &p_shader) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
connections_shader = p_shader;
_invalidate_connection_line_cache();
@@ -2536,6 +2573,8 @@ void GraphEdit::_minimap_toggled() {
}
void GraphEdit::set_connection_lines_curvature(float p_curvature) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
lines_curvature = p_curvature;
_invalidate_connection_line_cache();
connections_layer->queue_redraw();
@@ -2547,7 +2586,9 @@ float GraphEdit::get_connection_lines_curvature() const {
}
void GraphEdit::set_connection_lines_thickness(float p_thickness) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
ERR_FAIL_COND_MSG(p_thickness < 0, "Connection lines thickness must be greater than or equal to 0.");
+
if (lines_thickness == p_thickness) {
return;
}
@@ -2562,6 +2603,8 @@ float GraphEdit::get_connection_lines_thickness() const {
}
void GraphEdit::set_connection_lines_antialiased(bool p_antialiased) {
+ ERR_FAIL_NULL_MSG(connections_layer, "connections_layer is missing.");
+
if (lines_antialiased == p_antialiased) {
return;
}
@@ -2735,6 +2778,7 @@ void GraphEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("connection_drag_ended"));
ADD_SIGNAL(MethodInfo("copy_nodes_request"));
+ ADD_SIGNAL(MethodInfo("cut_nodes_request"));
ADD_SIGNAL(MethodInfo("paste_nodes_request"));
ADD_SIGNAL(MethodInfo("duplicate_nodes_request"));
ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName")));
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index 72e59bfc8a..8f5023104a 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -208,7 +208,7 @@ void GraphNode::_resort() {
// Avoid negative stretch space.
stretch_diff = MAX(stretch_diff, 0);
- available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP);
+ available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP) - titlebar_min_size.height - sb_titlebar->get_minimum_size().height;
// Second pass, discard elements that can't be stretched, this will run while stretchable elements exist.
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index bf16c0699e..f3cf29d0cf 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -32,7 +32,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
-#include "core/string/translation.h"
#include "scene/theme/theme_db.h"
void ItemList::_shape_text(int p_idx) {
@@ -58,12 +57,12 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo
Item item;
item.icon = p_texture;
item.text = p_item;
- item.xl_text = atr(p_item);
item.selectable = p_selectable;
items.push_back(item);
int item_id = items.size() - 1;
- _shape_text(items.size() - 1);
+ items.write[item_id].xl_text = _atr(item_id, p_item);
+ _shape_text(item_id);
queue_redraw();
shape_changed = true;
@@ -95,7 +94,7 @@ void ItemList::set_item_text(int p_idx, const String &p_text) {
}
items.write[p_idx].text = p_text;
- items.write[p_idx].xl_text = atr(p_text);
+ items.write[p_idx].xl_text = _atr(p_idx, p_text);
_shape_text(p_idx);
queue_redraw();
shape_changed = true;
@@ -141,6 +140,24 @@ String ItemList::get_item_language(int p_idx) const {
return items[p_idx].language;
}
+void ItemList::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode) {
+ if (p_idx < 0) {
+ p_idx += get_item_count();
+ }
+ ERR_FAIL_INDEX(p_idx, items.size());
+ if (items[p_idx].auto_translate_mode != p_mode) {
+ items.write[p_idx].auto_translate_mode = p_mode;
+ items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
+ _shape_text(p_idx);
+ queue_redraw();
+ }
+}
+
+Node::AutoTranslateMode ItemList::get_item_auto_translate_mode(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), AUTO_TRANSLATE_MODE_INHERIT);
+ return items[p_idx].auto_translate_mode;
+}
+
void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) {
if (p_idx < 0) {
p_idx += get_item_count();
@@ -1022,7 +1039,7 @@ void ItemList::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
for (int i = 0; i < items.size(); i++) {
- items.write[i].xl_text = atr(items[i].text);
+ items.write[i].xl_text = _atr(i, items[i].text);
_shape_text(i);
}
shape_changed = true;
@@ -1039,7 +1056,7 @@ void ItemList::_notification(int p_what) {
scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM));
Size2 size = get_size();
- int width = size.width - theme_cache.panel_style->get_minimum_size().width;
+ int width = size.width - theme_cache.panel_style->get_margin(SIDE_RIGHT);
if (scroll_bar->is_visible()) {
width -= scroll_bar_minwidth;
}
@@ -1192,9 +1209,9 @@ void ItemList::_notification(int p_what) {
Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs;
if (icon_mode == ICON_MODE_TOP) {
- pos.y += theme_cache.v_separation / 2;
+ pos.y += MAX(theme_cache.v_separation, 0) / 2;
} else {
- pos.x += theme_cache.h_separation / 2;
+ pos.x += MAX(theme_cache.h_separation, 0) / 2;
}
if (icon_mode == ICON_MODE_TOP) {
@@ -1243,8 +1260,8 @@ void ItemList::_notification(int p_what) {
}
Point2 draw_pos = items[i].rect_cache.position;
- draw_pos.x += theme_cache.h_separation / 2;
- draw_pos.y += theme_cache.v_separation / 2;
+ draw_pos.x += MAX(theme_cache.h_separation, 0) / 2;
+ draw_pos.y += MAX(theme_cache.v_separation, 0) / 2;
if (rtl) {
draw_pos.x = size.width - draw_pos.x - tag_icon_size.x;
}
@@ -1283,8 +1300,7 @@ void ItemList::_notification(int p_what) {
text_ofs += base_ofs;
text_ofs += items[i].rect_cache.position;
- text_ofs.x += theme_cache.h_separation / 2;
- text_ofs.y += theme_cache.v_separation / 2;
+ text_ofs.y += MAX(theme_cache.v_separation, 0) / 2;
if (rtl) {
text_ofs.x = size.width - text_ofs.x - max_len;
@@ -1292,7 +1308,7 @@ void ItemList::_notification(int p_what) {
items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER);
- float text_w = items[i].rect_cache.size.width - theme_cache.h_separation;
+ float text_w = items[i].rect_cache.size.width;
items.write[i].text_buf->set_width(text_w);
if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
@@ -1307,17 +1323,17 @@ void ItemList::_notification(int p_what) {
if (icon_mode == ICON_MODE_TOP) {
text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2;
- text_ofs.x += theme_cache.h_separation / 2;
- text_ofs.y += theme_cache.v_separation / 2;
+ text_ofs.x += MAX(theme_cache.h_separation, 0) / 2;
+ text_ofs.y += MAX(theme_cache.v_separation, 0) / 2;
} else {
text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2;
- text_ofs.x += theme_cache.h_separation / 2;
+ text_ofs.x += MAX(theme_cache.h_separation, 0) / 2;
}
text_ofs += base_ofs;
text_ofs += items[i].rect_cache.position;
- float text_w = width - text_ofs.x - theme_cache.h_separation;
+ float text_w = width - text_ofs.x;
items.write[i].text_buf->set_width(text_w);
if (rtl) {
@@ -1410,14 +1426,14 @@ void ItemList::force_update_list_size() {
max_column_width = MAX(max_column_width, minsize.x);
// Elements need to adapt to the selected size.
- minsize.y += theme_cache.v_separation;
- minsize.x += theme_cache.h_separation;
+ minsize.y += MAX(theme_cache.v_separation, 0);
+ minsize.x += MAX(theme_cache.h_separation, 0);
items.write[i].rect_cache.size = minsize;
items.write[i].min_rect_cache.size = minsize;
}
- int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width - scroll_bar_minwidth;
+ int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width;
//2-attempt best fit
current_columns = 0x7FFFFFFF;
@@ -1430,12 +1446,13 @@ void ItemList::force_update_list_size() {
bool all_fit = true;
Vector2 ofs;
int col = 0;
+ int max_w = 0;
int max_h = 0;
separators.clear();
for (int i = 0; i < items.size(); i++) {
- if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size) {
+ if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size && !auto_width) {
// Went past.
current_columns = MAX(col, 1);
all_fit = false;
@@ -1443,7 +1460,7 @@ void ItemList::force_update_list_size() {
}
if (same_column_width) {
- items.write[i].rect_cache.size.x = max_column_width + theme_cache.h_separation;
+ items.write[i].rect_cache.size.x = max_column_width + MAX(theme_cache.h_separation, 0);
}
items.write[i].rect_cache.position = ofs;
@@ -1461,6 +1478,7 @@ void ItemList::force_update_list_size() {
items.write[j].rect_cache.size.y = max_h;
}
+ max_w = MAX(max_w, ofs.x);
ofs.x = 0;
ofs.y += max_h;
col = 0;
@@ -1468,22 +1486,30 @@ void ItemList::force_update_list_size() {
}
}
+ float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height);
+ float max = MAX(page, ofs.y + max_h);
+ if (page >= max) {
+ fit_size -= scroll_bar_minwidth;
+ }
+
if (all_fit) {
for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) {
items.write[j].rect_cache.size.y = max_h;
}
- float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height);
- float max = MAX(page, ofs.y + max_h);
if (auto_height) {
auto_height_value = ofs.y + max_h + theme_cache.panel_style->get_minimum_size().height;
}
+ if (auto_width) {
+ auto_width_value = max_w + theme_cache.panel_style->get_minimum_size().width;
+ }
scroll_bar->set_max(max);
scroll_bar->set_page(page);
if (max <= page) {
scroll_bar->set_value(0);
scroll_bar->hide();
} else {
+ auto_width_value += scroll_bar_minwidth;
scroll_bar->show();
if (do_autoscroll_to_bottom) {
@@ -1509,6 +1535,23 @@ void ItemList::_mouse_exited() {
}
}
+String ItemList::_atr(int p_idx, const String &p_text) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), atr(p_text));
+ switch (items[p_idx].auto_translate_mode) {
+ case AUTO_TRANSLATE_MODE_INHERIT: {
+ return atr(p_text);
+ } break;
+ case AUTO_TRANSLATE_MODE_ALWAYS: {
+ return tr(p_text);
+ } break;
+ case AUTO_TRANSLATE_MODE_DISABLED: {
+ return p_text;
+ } break;
+ }
+
+ ERR_FAIL_V_MSG(atr(p_text), "Unexpected auto translate mode: " + itos(items[p_idx].auto_translate_mode));
+}
+
int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const {
Vector2 pos = p_pos;
pos -= theme_cache.panel_style->get_offset();
@@ -1667,16 +1710,35 @@ bool ItemList::is_anything_selected() {
}
Size2 ItemList::get_minimum_size() const {
+ Size2 min_size;
+ if (auto_width) {
+ min_size.x = auto_width_value;
+ }
+
if (auto_height) {
- return Size2(0, auto_height_value);
+ min_size.y = auto_height_value;
}
- return Size2();
+ return min_size;
}
void ItemList::set_autoscroll_to_bottom(const bool p_enable) {
do_autoscroll_to_bottom = p_enable;
}
+void ItemList::set_auto_width(bool p_enable) {
+ if (auto_width == p_enable) {
+ return;
+ }
+
+ auto_width = p_enable;
+ shape_changed = true;
+ queue_redraw();
+}
+
+bool ItemList::has_auto_width() const {
+ return auto_width;
+}
+
void ItemList::set_auto_height(bool p_enable) {
if (auto_height == p_enable) {
return;
@@ -1748,6 +1810,9 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &ItemList::set_item_language);
ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &ItemList::get_item_language);
+ ClassDB::bind_method(D_METHOD("set_item_auto_translate_mode", "idx", "mode"), &ItemList::set_item_auto_translate_mode);
+ ClassDB::bind_method(D_METHOD("get_item_auto_translate_mode", "idx"), &ItemList::get_item_auto_translate_mode);
+
ClassDB::bind_method(D_METHOD("set_item_icon_transposed", "idx", "transposed"), &ItemList::set_item_icon_transposed);
ClassDB::bind_method(D_METHOD("is_item_icon_transposed", "idx"), &ItemList::is_item_icon_transposed);
@@ -1829,6 +1894,9 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &ItemList::set_allow_search);
ClassDB::bind_method(D_METHOD("get_allow_search"), &ItemList::get_allow_search);
+ ClassDB::bind_method(D_METHOD("set_auto_width", "enable"), &ItemList::set_auto_width);
+ ClassDB::bind_method(D_METHOD("has_auto_width"), &ItemList::has_auto_width);
+
ClassDB::bind_method(D_METHOD("set_auto_height", "enable"), &ItemList::set_auto_height);
ClassDB::bind_method(D_METHOD("has_auto_height"), &ItemList::has_auto_height);
@@ -1850,6 +1918,7 @@ void ItemList::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_width"), "set_auto_width", "has_auto_width");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index a20688b3d0..0836ea33d5 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -62,6 +62,7 @@ private:
Ref<TextParagraph> text_buf;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
+ AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
bool selectable = true;
bool selected = false;
@@ -99,6 +100,9 @@ private:
bool same_column_width = false;
bool allow_search = true;
+ bool auto_width = false;
+ float auto_width_value = 0.0;
+
bool auto_height = false;
float auto_height_value = 0.0;
@@ -159,6 +163,8 @@ private:
void _shape_text(int p_idx);
void _mouse_exited();
+ String _atr(int p_idx, const String &p_text) const;
+
protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
@@ -183,6 +189,9 @@ public:
void set_item_language(int p_idx, const String &p_language);
String get_item_language(int p_idx) const;
+ void set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode);
+ AutoTranslateMode get_item_auto_translate_mode(int p_idx) const;
+
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_item_icon(int p_idx) const;
@@ -285,6 +294,9 @@ public:
void set_icon_scale(real_t p_scale);
real_t get_icon_scale() const;
+ void set_auto_width(bool p_enable);
+ bool has_auto_width() const;
+
void set_auto_height(bool p_enable);
bool has_auto_height() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 6e5b555cdf..9967805134 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -45,28 +45,37 @@
#include "editor/editor_settings.h"
#endif
-void LineEdit::_edit() {
+void LineEdit::edit() {
if (!is_inside_tree()) {
return;
}
if (!has_focus()) {
grab_focus();
+ return;
}
if (!editable || editing) {
return;
}
+ if (select_all_on_focus) {
+ if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
+ // Select all when the mouse button is up.
+ pending_select_all_on_focus = true;
+ } else {
+ select_all();
+ }
+ }
+
editing = true;
_validate_caret_can_draw();
show_virtual_keyboard();
queue_redraw();
- emit_signal(SNAME("editing_toggled"), true);
}
-void LineEdit::_unedit() {
+void LineEdit::unedit() {
if (!editing) {
return;
}
@@ -84,8 +93,6 @@ void LineEdit::_unedit() {
if (deselect_on_focus_loss_enabled && !selection.drag_attempt) {
deselect();
}
-
- emit_signal(SNAME("editing_toggled"), false);
}
bool LineEdit::is_editing() const {
@@ -390,7 +397,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
if (editable && !editing) {
- _edit();
+ edit();
+ emit_signal(SNAME("editing_toggled"), true);
}
accept_event();
@@ -406,7 +414,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
set_caret_at_pixel_pos(b->get_position().x);
if (!editing) {
- _edit();
+ edit();
+ emit_signal(SNAME("editing_toggled"), true);
}
if (!paste_buffer.is_empty()) {
@@ -506,7 +515,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
if (editable && !editing) {
- _edit();
+ edit();
+ emit_signal(SNAME("editing_toggled"), true);
return;
}
queue_redraw();
@@ -599,7 +609,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) {
- _edit();
+ edit();
+ emit_signal(SNAME("editing_toggled"), true);
+ accept_event();
return;
}
@@ -734,7 +746,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
}
if (editing) {
- _unedit();
+ unedit();
+ emit_signal(SNAME("editing_toggled"), false);
}
accept_event();
@@ -743,7 +756,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (k->is_action("ui_cancel")) {
if (editing) {
- _unedit();
+ unedit();
+ emit_signal(SNAME("editing_toggled"), false);
}
accept_event();
@@ -1332,24 +1346,17 @@ void LineEdit::_notification(int p_what) {
} break;
case NOTIFICATION_FOCUS_ENTER: {
- if (select_all_on_focus) {
- if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
- // Select all when the mouse button is up.
- pending_select_all_on_focus = true;
- } else {
- select_all();
- }
- }
-
// Only allow editing if the LineEdit is not focused with arrow keys.
if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) {
- _edit();
+ edit();
+ emit_signal(SNAME("editing_toggled"), true);
}
} break;
case NOTIFICATION_FOCUS_EXIT: {
if (editing) {
- _unedit();
+ unedit();
+ emit_signal(SNAME("editing_toggled"), false);
}
} break;
@@ -2138,7 +2145,8 @@ void LineEdit::set_editable(bool p_editable) {
editable = p_editable;
if (!editable && editing) {
- _unedit();
+ unedit();
+ emit_signal(SNAME("editing_toggled"), false);
}
_validate_caret_can_draw();
@@ -2759,6 +2767,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("edit"), &LineEdit::edit);
+ ClassDB::bind_method(D_METHOD("unedit"), &LineEdit::unedit);
ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing);
ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear);
ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1));
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index ac7436646b..9253dd8711 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -207,9 +207,6 @@ private:
float base_scale = 1.0;
} theme_cache;
- void _edit();
- void _unedit();
-
void _close_ime_window();
void _update_ime_window_position();
@@ -265,6 +262,8 @@ protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
public:
+ void edit();
+ void unedit();
bool is_editing() const;
bool has_ime_text() const;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index e302927692..26141663c1 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -2067,16 +2067,27 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ bool scroll_value_modified = false;
+ double prev_scroll = vscroll->get_value();
+
if (b->get_button_index() == MouseButton::WHEEL_UP) {
if (scroll_active) {
vscroll->scroll(-vscroll->get_page() * b->get_factor() * 0.5 / 8);
+ scroll_value_modified = true;
}
}
if (b->get_button_index() == MouseButton::WHEEL_DOWN) {
if (scroll_active) {
vscroll->scroll(vscroll->get_page() * b->get_factor() * 0.5 / 8);
+ scroll_value_modified = true;
}
}
+
+ if (scroll_value_modified && vscroll->get_value() != prev_scroll) {
+ accept_event();
+ return;
+ }
+
if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
_update_context_menu();
menu->set_position(get_screen_position() + b->get_position());
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 2211bd76fc..1ac0e8b59f 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -547,7 +547,7 @@ PackedStringArray ScrollContainer::get_configuration_warnings() const {
int found = 0;
for (int i = 0; i < get_child_count(); i++) {
- Control *c = as_sortable_control(get_child(i));
+ Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 90bb0799b1..6cc7ec3bd5 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -354,6 +354,8 @@ void TabBar::_notification(int p_what) {
if (scroll_to_selected) {
ensure_tab_visible(current);
}
+ // Set initialized even if no tabs were set.
+ initialized = true;
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
@@ -655,10 +657,10 @@ void TabBar::set_tab_count(int p_count) {
}
if (!initialized) {
- if (queued_current != current) {
- current = queued_current;
- }
initialized = true;
+ if (queued_current != CURRENT_TAB_UNINITIALIZED && queued_current != current) {
+ set_current_tab(queued_current);
+ }
}
queue_redraw();
@@ -740,6 +742,13 @@ bool TabBar::select_next_available() {
return false;
}
+void TabBar::set_tab_offset(int p_offset) {
+ ERR_FAIL_INDEX(p_offset, tabs.size());
+ offset = p_offset;
+ _update_cache();
+ queue_redraw();
+}
+
int TabBar::get_tab_offset() const {
return offset;
}
diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h
index d62b39ae16..f5ae75de51 100644
--- a/scene/gui/tab_bar.h
+++ b/scene/gui/tab_bar.h
@@ -117,8 +117,9 @@ private:
bool scroll_to_selected = true;
int tabs_rearrange_group = -1;
+ static const int CURRENT_TAB_UNINITIALIZED = -2;
bool initialized = false;
- int queued_current = -1;
+ int queued_current = CURRENT_TAB_UNINITIALIZED;
const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
@@ -249,6 +250,7 @@ public:
bool select_previous_available();
bool select_next_available();
+ void set_tab_offset(int p_offset);
int get_tab_offset() const;
bool get_offset_buttons_visible() const;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index eb9616c939..33e72428cf 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -190,7 +190,7 @@ void TabContainer::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- if (!is_visible() || setup_current_tab > -2) {
+ if (!is_visible()) {
return;
}
@@ -200,7 +200,7 @@ void TabContainer::_notification(int p_what) {
// beat it to the punch and make sure that the correct node is the only one visible first.
// Otherwise, it can prevent a tab change done right before this container was made visible.
Vector<Control *> controls = _get_tab_controls();
- int current = get_current_tab();
+ int current = setup_current_tab > -2 ? setup_current_tab : get_current_tab();
for (int i = 0; i < controls.size(); i++) {
controls[i]->set_visible(i == current);
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e4f52ee8ee..646cd9c70e 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -185,6 +185,26 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const {
return cells[p_column].mode;
}
+/* auto translate mode */
+void TreeItem::set_auto_translate_mode(int p_column, Node::AutoTranslateMode p_mode) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+
+ if (cells[p_column].auto_translate_mode == p_mode) {
+ return;
+ }
+
+ cells.write[p_column].auto_translate_mode = p_mode;
+ cells.write[p_column].dirty = true;
+ cells.write[p_column].cached_minimum_size_dirty = true;
+
+ _changed_notify(p_column);
+}
+
+Node::AutoTranslateMode TreeItem::get_auto_translate_mode(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Node::AUTO_TRANSLATE_MODE_INHERIT);
+ return cells[p_column].auto_translate_mode;
+}
+
/* multiline editable */
void TreeItem::set_edit_multiline(int p_column, bool p_multiline) {
ERR_FAIL_INDEX(p_column, cells.size());
@@ -247,6 +267,24 @@ void TreeItem::propagate_check(int p_column, bool p_emit_signal) {
_propagate_check_through_parents(p_column, p_emit_signal);
}
+String TreeItem::atr(int p_column, const String &p_text) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), tree->atr(p_text));
+
+ switch (cells[p_column].auto_translate_mode) {
+ case Node::AUTO_TRANSLATE_MODE_INHERIT: {
+ return tree->atr(p_text);
+ } break;
+ case Node::AUTO_TRANSLATE_MODE_ALWAYS: {
+ return tree->tr(p_text);
+ } break;
+ case Node::AUTO_TRANSLATE_MODE_DISABLED: {
+ return p_text;
+ } break;
+ }
+
+ ERR_FAIL_V_MSG(tree->atr(p_text), "Unexpected auto translate mode: " + itos(cells[p_column].auto_translate_mode));
+}
+
void TreeItem::_propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal) {
TreeItem *current = get_first_child();
while (current) {
@@ -323,7 +361,7 @@ void TreeItem::set_text(int p_column, String p_text) {
} else {
// Don't auto translate if it's in string mode and editable, as the text can be changed to anything by the user.
if (tree && (!cells[p_column].editable || cells[p_column].mode != TreeItem::CELL_MODE_STRING)) {
- cells.write[p_column].xl_text = tree->atr(p_text);
+ cells.write[p_column].xl_text = atr(p_column, p_text);
} else {
cells.write[p_column].xl_text = p_text;
}
@@ -1621,6 +1659,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cell_mode", "column", "mode"), &TreeItem::set_cell_mode);
ClassDB::bind_method(D_METHOD("get_cell_mode", "column"), &TreeItem::get_cell_mode);
+ ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "column", "mode"), &TreeItem::set_auto_translate_mode);
+ ClassDB::bind_method(D_METHOD("get_auto_translate_mode", "column"), &TreeItem::get_auto_translate_mode);
+
ClassDB::bind_method(D_METHOD("set_edit_multiline", "column", "multiline"), &TreeItem::set_edit_multiline);
ClassDB::bind_method(D_METHOD("is_edit_multiline", "column"), &TreeItem::is_edit_multiline);
@@ -2009,7 +2050,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) {
int option = (int)p_item->cells[p_col].val;
- valtext = atr(ETR("(Other)"));
+ valtext = p_item->atr(p_col, ETR("(Other)"));
Vector<String> strings = p_item->cells[p_col].text.split(",");
for (int j = 0; j < strings.size(); j++) {
int value = j;
@@ -2017,7 +2058,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) {
value = strings[j].get_slicec(':', 1).to_int();
}
if (option == value) {
- valtext = atr(strings[j].get_slicec(':', 0));
+ valtext = p_item->atr(p_col, strings[j].get_slicec(':', 0));
break;
}
}
@@ -2028,7 +2069,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) {
} else {
// Don't auto translate if it's in string mode and editable, as the text can be changed to anything by the user.
if (!p_item->cells[p_col].editable || p_item->cells[p_col].mode != TreeItem::CELL_MODE_STRING) {
- p_item->cells.write[p_col].xl_text = atr(p_item->cells[p_col].text);
+ p_item->cells.write[p_col].xl_text = p_item->atr(p_col, p_item->cells[p_col].text);
} else {
p_item->cells.write[p_col].xl_text = p_item->cells[p_col].text;
}
@@ -3993,25 +4034,25 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::WHEEL_UP: {
- if (_scroll(false, -mb->get_factor() / 8)) {
+ if (_scroll(mb->is_shift_pressed(), -mb->get_factor() / 8)) {
accept_event();
}
} break;
case MouseButton::WHEEL_DOWN: {
- if (_scroll(false, mb->get_factor() / 8)) {
+ if (_scroll(mb->is_shift_pressed(), mb->get_factor() / 8)) {
accept_event();
}
} break;
case MouseButton::WHEEL_LEFT: {
- if (_scroll(true, -mb->get_factor() / 8)) {
+ if (_scroll(!mb->is_shift_pressed(), -mb->get_factor() / 8)) {
accept_event();
}
} break;
case MouseButton::WHEEL_RIGHT: {
- if (_scroll(true, mb->get_factor() / 8)) {
+ if (_scroll(!mb->is_shift_pressed(), mb->get_factor() / 8)) {
accept_event();
}
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 17ea31a733..86efdfec52 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -64,6 +64,7 @@ private:
Rect2i icon_region;
String text;
String xl_text;
+ Node::AutoTranslateMode auto_translate_mode = Node::AUTO_TRANSLATE_MODE_INHERIT;
bool edit_multiline = false;
String suffix;
Ref<TextParagraph> text_buf;
@@ -210,6 +211,10 @@ public:
void set_cell_mode(int p_column, TreeCellMode p_mode);
TreeCellMode get_cell_mode(int p_column) const;
+ /* auto translate mode */
+ void set_auto_translate_mode(int p_column, Node::AutoTranslateMode p_mode);
+ Node::AutoTranslateMode get_auto_translate_mode(int p_column) const;
+
/* multiline editable */
void set_edit_multiline(int p_column, bool p_multiline);
bool is_edit_multiline(int p_column) const;
@@ -222,6 +227,8 @@ public:
void propagate_check(int p_column, bool p_emit_signal = true);
+ String atr(int p_column, const String &p_text) const;
+
private:
// Check helpers.
void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal);
diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp
index 3469b806a6..8526611093 100644
--- a/scene/main/http_request.cpp
+++ b/scene/main/http_request.cpp
@@ -49,7 +49,8 @@ Error HTTPRequest::_parse_url(const String &p_url) {
redirections = 0;
String scheme;
- Error err = p_url.parse_url(scheme, url, port, request_string);
+ String fragment;
+ Error err = p_url.parse_url(scheme, url, port, request_string, fragment);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing URL: '%s'.", p_url));
if (scheme == "https://") {
diff --git a/scene/main/node.h b/scene/main/node.h
index 298cbc7e59..799478fa35 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -743,8 +743,13 @@ public:
virtual void set_translation_domain(const StringName &p_domain) override;
void set_translation_domain_inherited();
- _FORCE_INLINE_ String atr(const String p_message, const StringName p_context = "") const { return can_auto_translate() ? tr(p_message, p_context) : p_message; }
- _FORCE_INLINE_ String atr_n(const String p_message, const StringName &p_message_plural, int p_n, const StringName p_context = "") const { return can_auto_translate() ? tr_n(p_message, p_message_plural, p_n, p_context) : p_message; }
+ _FORCE_INLINE_ String atr(const String &p_message, const StringName &p_context = "") const { return can_auto_translate() ? tr(p_message, p_context) : p_message; }
+ _FORCE_INLINE_ String atr_n(const String &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const {
+ if (can_auto_translate()) {
+ return tr_n(p_message, p_message_plural, p_n, p_context);
+ }
+ return p_n == 1 ? p_message : String(p_message_plural);
+ }
/* THREADING */
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 106130872d..71d91b970e 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -302,11 +302,15 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro
continue;
}
+ Node *node = gr_nodes[i];
if (!(p_call_flags & GROUP_CALL_DEFERRED)) {
Callable::CallError ce;
- gr_nodes[i]->callp(p_function, p_args, p_argcount, ce);
+ node->callp(p_function, p_args, p_argcount, ce);
+ if (unlikely(ce.error != Callable::CallError::CALL_OK && ce.error != Callable::CallError::CALL_ERROR_INVALID_METHOD)) {
+ ERR_PRINT(vformat("Error calling group method on node \"%s\": %s.", node->get_name(), Variant::get_callable_error_text(Callable(node, p_function), p_args, p_argcount, ce)));
+ }
} else {
- MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount);
+ MessageQueue::get_singleton()->push_callp(node, p_function, p_args, p_argcount);
}
}
@@ -316,11 +320,15 @@ void SceneTree::call_group_flagsp(uint32_t p_call_flags, const StringName &p_gro
continue;
}
+ Node *node = gr_nodes[i];
if (!(p_call_flags & GROUP_CALL_DEFERRED)) {
Callable::CallError ce;
- gr_nodes[i]->callp(p_function, p_args, p_argcount, ce);
+ node->callp(p_function, p_args, p_argcount, ce);
+ if (unlikely(ce.error != Callable::CallError::CALL_OK && ce.error != Callable::CallError::CALL_ERROR_INVALID_METHOD)) {
+ ERR_PRINT(vformat("Error calling group method on node \"%s\": %s.", node->get_name(), Variant::get_callable_error_text(Callable(node, p_function), p_args, p_argcount, ce)));
+ }
} else {
- MessageQueue::get_singleton()->push_callp(gr_nodes[i], p_function, p_args, p_argcount);
+ MessageQueue::get_singleton()->push_callp(node, p_function, p_args, p_argcount);
}
}
}
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 169a5dcb01..0cdb23618f 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1448,7 +1448,11 @@ void Viewport::_gui_show_tooltip() {
&tooltip_owner);
gui.tooltip_text = gui.tooltip_text.strip_edges();
- if (gui.tooltip_text.is_empty()) {
+ // Controls can implement `make_custom_tooltip` to provide their own tooltip.
+ // This should be a Control node which will be added as child to a TooltipPanel.
+ Control *base_tooltip = tooltip_owner ? tooltip_owner->make_custom_tooltip(gui.tooltip_text) : nullptr;
+
+ if (gui.tooltip_text.is_empty() && !base_tooltip) {
return; // Nothing to show.
}
@@ -1469,10 +1473,6 @@ void Viewport::_gui_show_tooltip() {
// Ensure no opaque background behind the panel as its StyleBox can be partially transparent (e.g. corners).
panel->set_transparent_background(true);
- // Controls can implement `make_custom_tooltip` to provide their own tooltip.
- // This should be a Control node which will be added as child to a TooltipPanel.
- Control *base_tooltip = tooltip_owner->make_custom_tooltip(gui.tooltip_text);
-
// If no custom tooltip is given, use a default implementation.
if (!base_tooltip) {
gui.tooltip_label = memnew(Label);
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 09227e260f..6b1ce2b4ca 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -883,7 +883,6 @@ void register_scene_types() {
GDREGISTER_CLASS(ProceduralSkyMaterial);
GDREGISTER_CLASS(PanoramaSkyMaterial);
GDREGISTER_CLASS(PhysicalSkyMaterial);
- SceneTree::add_idle_callback(BaseMaterial3D::flush_changes);
BaseMaterial3D::init_shaders();
GDREGISTER_CLASS(MeshLibrary);
diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp
index 92246b50db..6c6e98b50d 100644
--- a/scene/resources/3d/fog_material.cpp
+++ b/scene/resources/3d/fog_material.cpp
@@ -168,6 +168,8 @@ void fog() {
}
FogMaterial::FogMaterial() {
+ _set_material(RS::get_singleton()->material_create());
+
set_density(1.0);
set_albedo(Color(1, 1, 1, 1));
set_emission(Color(0, 0, 0, 1));
diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp
index c470db5d7f..10ef516f7a 100644
--- a/scene/resources/3d/sky_material.cpp
+++ b/scene/resources/3d/sky_material.cpp
@@ -357,6 +357,7 @@ void sky() {
}
ProceduralSkyMaterial::ProceduralSkyMaterial() {
+ _set_material(RS::get_singleton()->material_create());
set_sky_top_color(Color(0.385, 0.454, 0.55));
set_sky_horizon_color(Color(0.6463, 0.6558, 0.6708));
set_sky_curve(0.15);
@@ -486,6 +487,7 @@ void sky() {
}
PanoramaSkyMaterial::PanoramaSkyMaterial() {
+ _set_material(RS::get_singleton()->material_create());
set_energy_multiplier(1.0);
}
@@ -785,6 +787,7 @@ void sky() {
}
PhysicalSkyMaterial::PhysicalSkyMaterial() {
+ _set_material(RS::get_singleton()->material_create());
set_rayleigh_coefficient(2.0);
set_rayleigh_color(Color(0.3, 0.405, 0.6));
set_mie_coefficient(0.005);
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 1dac4b97ad..57a4e35f7a 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -63,6 +63,23 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
compression.enabled = true;
return true;
+ } else if (prop_name == SNAME("markers")) {
+ Array markers = p_value;
+ for (const Dictionary marker : markers) {
+ ERR_FAIL_COND_V(!marker.has("name"), false);
+ ERR_FAIL_COND_V(!marker.has("time"), false);
+ StringName marker_name = marker["name"];
+ double time = marker["time"];
+ _marker_insert(time, marker_names, MarkerKey(time, marker_name));
+ marker_times.insert(marker_name, time);
+ Color color = Color(1, 1, 1);
+ if (marker.has("color")) {
+ color = marker["color"];
+ }
+ marker_colors.insert(marker_name, color);
+ }
+
+ return true;
} else if (prop_name.begins_with("tracks/")) {
int track = prop_name.get_slicec('/', 1).to_int();
String what = prop_name.get_slicec('/', 2);
@@ -470,6 +487,18 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = comp;
return true;
+ } else if (prop_name == SNAME("markers")) {
+ Array markers;
+
+ for (HashMap<StringName, double>::ConstIterator E = marker_times.begin(); E; ++E) {
+ Dictionary d;
+ d["name"] = E->key;
+ d["time"] = E->value;
+ d["color"] = marker_colors[E->key];
+ markers.push_back(d);
+ }
+
+ r_ret = markers;
} else if (prop_name == "length") {
r_ret = length;
} else if (prop_name == "loop_mode") {
@@ -839,6 +868,7 @@ void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
if (compression.enabled) {
p_list->push_back(PropertyInfo(Variant::DICTIONARY, "_compression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "markers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
for (int i = 0; i < tracks.size(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING, "tracks/" + itos(i) + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/imported", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
@@ -1087,6 +1117,27 @@ int Animation::_insert(double p_time, T &p_keys, const V &p_value) {
return -1;
}
+int Animation::_marker_insert(double p_time, Vector<MarkerKey> &p_keys, const MarkerKey &p_value) {
+ int idx = p_keys.size();
+
+ while (true) {
+ // Condition for replacement.
+ if (idx > 0 && Math::is_equal_approx((double)p_keys[idx - 1].time, p_time)) {
+ p_keys.write[idx - 1] = p_value;
+ return idx - 1;
+
+ // Condition for insert.
+ } else if (idx == 0 || p_keys[idx - 1].time < p_time) {
+ p_keys.insert(idx, p_value);
+ return idx;
+ }
+
+ idx--;
+ }
+
+ return -1;
+}
+
template <typename T>
void Animation::_clear(T &p_keys) {
p_keys.clear();
@@ -3163,6 +3214,90 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
}
}
+void Animation::add_marker(const StringName &p_name, double p_time) {
+ int idx = _find(marker_names, p_time);
+
+ if (idx >= 0 && idx < marker_names.size() && Math::is_equal_approx(p_time, marker_names[idx].time)) {
+ marker_times.erase(marker_names[idx].name);
+ marker_colors.erase(marker_names[idx].name);
+ marker_names.write[idx].name = p_name;
+ marker_times.insert(p_name, p_time);
+ marker_colors.insert(p_name, Color(1, 1, 1));
+ } else {
+ _marker_insert(p_time, marker_names, MarkerKey(p_time, p_name));
+ marker_times.insert(p_name, p_time);
+ marker_colors.insert(p_name, Color(1, 1, 1));
+ }
+}
+
+void Animation::remove_marker(const StringName &p_name) {
+ HashMap<StringName, double>::Iterator E = marker_times.find(p_name);
+ ERR_FAIL_COND(!E);
+ int idx = _find(marker_names, E->value);
+ bool success = idx >= 0 && idx < marker_names.size() && Math::is_equal_approx(marker_names[idx].time, E->value);
+ ERR_FAIL_COND(!success);
+ marker_names.remove_at(idx);
+ marker_times.remove(E);
+ marker_colors.erase(p_name);
+}
+
+bool Animation::has_marker(const StringName &p_name) const {
+ return marker_times.has(p_name);
+}
+
+StringName Animation::get_marker_at_time(double p_time) const {
+ int idx = _find(marker_names, p_time);
+
+ if (idx >= 0 && idx < marker_names.size() && Math::is_equal_approx(marker_names[idx].time, p_time)) {
+ return marker_names[idx].name;
+ }
+
+ return StringName();
+}
+
+StringName Animation::get_next_marker(double p_time) const {
+ int idx = _find(marker_names, p_time);
+
+ if (idx >= -1 && idx < marker_names.size() - 1) {
+ // _find ensures that the time at idx is always the closest time to p_time that is also smaller to it.
+ // So we add 1 to get the next marker.
+ return marker_names[idx + 1].name;
+ }
+ return StringName();
+}
+
+StringName Animation::get_prev_marker(double p_time) const {
+ int idx = _find(marker_names, p_time);
+
+ if (idx >= 0 && idx < marker_names.size()) {
+ return marker_names[idx].name;
+ }
+ return StringName();
+}
+
+double Animation::get_marker_time(const StringName &p_name) const {
+ ERR_FAIL_COND_V(!marker_times.has(p_name), -1);
+ return marker_times.get(p_name);
+}
+
+PackedStringArray Animation::get_marker_names() const {
+ PackedStringArray names;
+ // We iterate on marker_names so the result is sorted by time.
+ for (const MarkerKey &marker_name : marker_names) {
+ names.push_back(marker_name.name);
+ }
+ return names;
+}
+
+Color Animation::get_marker_color(const StringName &p_name) const {
+ ERR_FAIL_COND_V(!marker_colors.has(p_name), Color());
+ return marker_colors[p_name];
+}
+
+void Animation::set_marker_color(const StringName &p_name, const Color &p_color) {
+ marker_colors[p_name] = p_color;
+}
+
Vector<Variant> Animation::method_track_get_params(int p_track, int p_key_idx) const {
ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector<Variant>());
Track *t = tracks[p_track];
@@ -3894,6 +4029,17 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "track_idx", "key_idx"), &Animation::animation_track_get_key_animation);
+ ClassDB::bind_method(D_METHOD("add_marker", "name", "time"), &Animation::add_marker);
+ ClassDB::bind_method(D_METHOD("remove_marker", "name"), &Animation::remove_marker);
+ ClassDB::bind_method(D_METHOD("has_marker", "name"), &Animation::has_marker);
+ ClassDB::bind_method(D_METHOD("get_marker_at_time", "time"), &Animation::get_marker_at_time);
+ ClassDB::bind_method(D_METHOD("get_next_marker", "time"), &Animation::get_next_marker);
+ ClassDB::bind_method(D_METHOD("get_prev_marker", "time"), &Animation::get_prev_marker);
+ ClassDB::bind_method(D_METHOD("get_marker_time", "name"), &Animation::get_marker_time);
+ ClassDB::bind_method(D_METHOD("get_marker_names"), &Animation::get_marker_names);
+ ClassDB::bind_method(D_METHOD("get_marker_color", "name"), &Animation::get_marker_color);
+ ClassDB::bind_method(D_METHOD("set_marker_color", "name", "color"), &Animation::set_marker_color);
+
ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length);
ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length);
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 0c29790ea4..618dc9ca17 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -237,6 +237,20 @@ private:
}
};
+ /* Marker */
+
+ struct MarkerKey {
+ double time;
+ StringName name;
+ MarkerKey(double p_time, const StringName &p_name) :
+ time(p_time), name(p_name) {}
+ MarkerKey() = default;
+ };
+
+ Vector<MarkerKey> marker_names; // time -> name
+ HashMap<StringName, double> marker_times; // name -> time
+ HashMap<StringName, Color> marker_colors; // name -> color
+
Vector<Track *> tracks;
template <typename T>
@@ -245,6 +259,8 @@ private:
template <typename T, typename V>
int _insert(double p_time, T &p_keys, const V &p_value);
+ int _marker_insert(double p_time, Vector<MarkerKey> &p_keys, const MarkerKey &p_value);
+
template <typename K>
inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false, bool p_limit = false) const;
@@ -501,6 +517,17 @@ public:
void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const;
+ void add_marker(const StringName &p_name, double p_time);
+ void remove_marker(const StringName &p_name);
+ bool has_marker(const StringName &p_name) const;
+ StringName get_marker_at_time(double p_time) const;
+ StringName get_next_marker(double p_time) const;
+ StringName get_prev_marker(double p_time) const;
+ double get_marker_time(const StringName &p_time) const;
+ PackedStringArray get_marker_names() const;
+ Color get_marker_color(const StringName &p_name) const;
+ void set_marker_color(const StringName &p_name, const Color &p_color);
+
void set_length(real_t p_length);
real_t get_length() const;
diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp
index 76e99aca92..6f43106ea9 100644
--- a/scene/resources/canvas_item_material.cpp
+++ b/scene/resources/canvas_item_material.cpp
@@ -274,6 +274,8 @@ void CanvasItemMaterial::_bind_methods() {
CanvasItemMaterial::CanvasItemMaterial() :
element(this) {
+ _set_material(RS::get_singleton()->material_create());
+
set_particles_anim_h_frames(1);
set_particles_anim_v_frames(1);
set_particles_anim_loop(false);
diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp
index 907c0ab4ca..072542f0ad 100644
--- a/scene/resources/immediate_mesh.cpp
+++ b/scene/resources/immediate_mesh.cpp
@@ -312,6 +312,8 @@ void ImmediateMesh::surface_end() {
uses_uv2s = false;
surface_active = false;
+
+ emit_changed();
}
void ImmediateMesh::clear_surfaces() {
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index ab25aabb81..ecc1982aa5 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -46,11 +46,15 @@ void Material::set_next_pass(const Ref<Material> &p_pass) {
}
next_pass = p_pass;
- RID next_pass_rid;
- if (next_pass.is_valid()) {
- next_pass_rid = next_pass->get_rid();
+
+ if (material.is_valid()) {
+ RID next_pass_rid;
+ if (next_pass.is_valid()) {
+ next_pass_rid = next_pass->get_rid();
+ }
+
+ RS::get_singleton()->material_set_next_pass(material, next_pass_rid);
}
- RS::get_singleton()->material_set_next_pass(material, next_pass_rid);
}
Ref<Material> Material::get_next_pass() const {
@@ -61,7 +65,10 @@ void Material::set_render_priority(int p_priority) {
ERR_FAIL_COND(p_priority < RENDER_PRIORITY_MIN);
ERR_FAIL_COND(p_priority > RENDER_PRIORITY_MAX);
render_priority = p_priority;
- RS::get_singleton()->material_set_render_priority(material, p_priority);
+
+ if (material.is_valid()) {
+ RS::get_singleton()->material_set_render_priority(material, p_priority);
+ }
}
int Material::get_render_priority() const {
@@ -165,13 +172,14 @@ void Material::_bind_methods() {
}
Material::Material() {
- material = RenderingServer::get_singleton()->material_create();
render_priority = 0;
}
Material::~Material() {
- ERR_FAIL_NULL(RenderingServer::get_singleton());
- RenderingServer::get_singleton()->free(material);
+ if (material.is_valid()) {
+ ERR_FAIL_NULL(RenderingServer::get_singleton());
+ RenderingServer::get_singleton()->free(material);
+ }
}
///////////////////////////////////
@@ -374,14 +382,11 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const {
bool ShaderMaterial::_property_can_revert(const StringName &p_name) const {
if (shader.is_valid()) {
- const StringName *pr = remap_cache.getptr(p_name);
- if (pr) {
- Variant default_value = RenderingServer::get_singleton()->shader_get_parameter_default(shader->get_rid(), *pr);
- Variant current_value = get_shader_parameter(*pr);
- return default_value.get_type() != Variant::NIL && default_value != current_value;
- } else if (p_name == "render_priority" || p_name == "next_pass") {
+ if (remap_cache.has(p_name)) {
return true;
}
+ const String sname = p_name;
+ return sname == "render_priority" || sname == "next_pass";
}
return false;
}
@@ -422,7 +427,11 @@ void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) {
}
}
- RS::get_singleton()->material_set_shader(_get_material(), rid);
+ RID material_rid = _get_material();
+ if (material_rid.is_valid()) {
+ RS::get_singleton()->material_set_shader(material_rid, rid);
+ }
+
notify_property_list_changed(); //properties for shader exposed
emit_changed();
}
@@ -432,9 +441,12 @@ Ref<Shader> ShaderMaterial::get_shader() const {
}
void ShaderMaterial::set_shader_parameter(const StringName &p_param, const Variant &p_value) {
+ RID material_rid = _get_material();
if (p_value.get_type() == Variant::NIL) {
param_cache.erase(p_param);
- RS::get_singleton()->material_set_param(_get_material(), p_param, Variant());
+ if (material_rid.is_valid()) {
+ RS::get_singleton()->material_set_param(material_rid, p_param, Variant());
+ }
} else {
Variant *v = param_cache.getptr(p_param);
if (!v) {
@@ -449,12 +461,15 @@ void ShaderMaterial::set_shader_parameter(const StringName &p_param, const Varia
RID tex_rid = p_value;
if (tex_rid == RID()) {
param_cache.erase(p_param);
- RS::get_singleton()->material_set_param(_get_material(), p_param, Variant());
- } else {
- RS::get_singleton()->material_set_param(_get_material(), p_param, tex_rid);
+
+ if (material_rid.is_valid()) {
+ RS::get_singleton()->material_set_param(material_rid, p_param, Variant());
+ }
+ } else if (material_rid.is_valid()) {
+ RS::get_singleton()->material_set_param(material_rid, p_param, tex_rid);
}
- } else {
- RS::get_singleton()->material_set_param(_get_material(), p_param, p_value);
+ } else if (material_rid.is_valid()) {
+ RS::get_singleton()->material_set_param(material_rid, p_param, p_value);
}
}
}
@@ -471,6 +486,32 @@ void ShaderMaterial::_shader_changed() {
notify_property_list_changed(); //update all properties
}
+void ShaderMaterial::_check_material_rid() const {
+ MutexLock lock(material_rid_mutex);
+ if (_get_material().is_null()) {
+ RID shader_rid = shader.is_valid() ? shader->get_rid() : RID();
+ RID next_pass_rid;
+ if (get_next_pass().is_valid()) {
+ next_pass_rid = get_next_pass()->get_rid();
+ }
+
+ _set_material(RS::get_singleton()->material_create_from_shader(next_pass_rid, get_render_priority(), shader_rid));
+
+ for (KeyValue<StringName, Variant> param : param_cache) {
+ if (param.value.get_type() == Variant::OBJECT) {
+ RID tex_rid = param.value;
+ if (tex_rid.is_valid()) {
+ RS::get_singleton()->material_set_param(_get_material(), param.key, tex_rid);
+ } else {
+ RS::get_singleton()->material_set_param(_get_material(), param.key, Variant());
+ }
+ } else {
+ RS::get_singleton()->material_set_param(_get_material(), param.key, param.value);
+ }
+ }
+ }
+}
+
void ShaderMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shader", "shader"), &ShaderMaterial::set_shader);
ClassDB::bind_method(D_METHOD("get_shader"), &ShaderMaterial::get_shader);
@@ -511,6 +552,12 @@ Shader::Mode ShaderMaterial::get_shader_mode() const {
return Shader::MODE_SPATIAL;
}
}
+
+RID ShaderMaterial::get_rid() const {
+ _check_material_rid();
+ return Material::get_rid();
+}
+
RID ShaderMaterial::get_shader_rid() const {
if (shader.is_valid()) {
return shader->get_rid();
@@ -520,6 +567,7 @@ RID ShaderMaterial::get_shader_rid() const {
}
ShaderMaterial::ShaderMaterial() {
+ // Material RID will be empty until it is required.
}
ShaderMaterial::~ShaderMaterial() {
@@ -527,9 +575,8 @@ ShaderMaterial::~ShaderMaterial() {
/////////////////////////////////
-Mutex BaseMaterial3D::material_mutex;
-SelfList<BaseMaterial3D>::List BaseMaterial3D::dirty_materials;
HashMap<BaseMaterial3D::MaterialKey, BaseMaterial3D::ShaderData, BaseMaterial3D::MaterialKey> BaseMaterial3D::shader_map;
+Mutex BaseMaterial3D::shader_map_mutex;
BaseMaterial3D::ShaderNames *BaseMaterial3D::shader_names = nullptr;
void BaseMaterial3D::init_shaders() {
@@ -619,22 +666,31 @@ HashMap<uint64_t, Ref<StandardMaterial3D>> BaseMaterial3D::materials_for_2d;
void BaseMaterial3D::finish_shaders() {
materials_for_2d.clear();
- dirty_materials.clear();
-
memdelete(shader_names);
shader_names = nullptr;
}
+void BaseMaterial3D::_mark_dirty() {
+ dirty = true;
+}
+
void BaseMaterial3D::_update_shader() {
+ if (!dirty) {
+ return;
+ }
+
+ dirty = false;
+
MaterialKey mk = _compute_key();
if (mk == current_key) {
return; //no update required in the end
}
+ MutexLock lock(shader_map_mutex);
if (shader_map.has(current_key)) {
shader_map[current_key].users--;
if (shader_map[current_key].users == 0) {
- //deallocate shader, as it's no longer in use
+ // Deallocate shader which is no longer in use.
RS::get_singleton()->free(shader_map[current_key].shader);
shader_map.erase(current_key);
}
@@ -643,8 +699,13 @@ void BaseMaterial3D::_update_shader() {
current_key = mk;
if (shader_map.has(mk)) {
- RS::get_singleton()->material_set_shader(_get_material(), shader_map[mk].shader);
+ shader_rid = shader_map[mk].shader;
shader_map[mk].users++;
+
+ if (_get_material().is_valid()) {
+ RS::get_singleton()->material_set_shader(_get_material(), shader_rid);
+ }
+
return;
}
@@ -946,7 +1007,7 @@ uniform vec4 refraction_texture_channel;
code += "uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear_mipmap;\n";
}
- if (proximity_fade_enabled) {
+ if (features[FEATURE_REFRACTION] || proximity_fade_enabled) {
code += "uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;\n";
}
@@ -1627,7 +1688,14 @@ void fragment() {)";
}
code += R"(
float ref_amount = 1.0 - albedo.a * albedo_tex.a;
- EMISSION += textureLod(screen_texture, ref_ofs, ROUGHNESS * 8.0).rgb * ref_amount * EXPOSURE;
+
+ float refraction_depth_tex = textureLod(depth_texture, ref_ofs, 0.0).r;
+ vec4 refraction_view_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, refraction_depth_tex, 1.0);
+ refraction_view_pos.xyz /= refraction_view_pos.w;
+
+ // If the depth buffer is lower then the model's Z position, use the refracted UV, otherwise use the normal screen UV.
+ // At low depth differences, decrease refraction intensity to avoid sudden discontinuities.
+ EMISSION += textureLod(screen_texture, mix(SCREEN_UV, ref_ofs, smoothstep(0.0, 1.0, VERTEX.z - refraction_view_pos.z)), ROUGHNESS * 8.0).rgb * ref_amount * EXPOSURE;
ALBEDO *= 1.0 - ref_amount;
// Force transparency on the material (required for refraction).
ALPHA = 1.0;
@@ -1649,10 +1717,10 @@ void fragment() {)";
if (proximity_fade_enabled) {
code += R"(
// Proximity Fade: Enabled
- float depth_tex = textureLod(depth_texture, SCREEN_UV, 0.0).r;
- vec4 world_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth_tex, 1.0);
- world_pos.xyz /= world_pos.w;
- ALPHA *= clamp(1.0 - smoothstep(world_pos.z + proximity_fade_distance, world_pos.z, VERTEX.z), 0.0, 1.0);
+ float proximity_depth_tex = textureLod(depth_texture, SCREEN_UV, 0.0).r;
+ vec4 proximity_view_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, proximity_depth_tex, 1.0);
+ proximity_view_pos.xyz /= proximity_view_pos.w;
+ ALPHA *= clamp(1.0 - smoothstep(proximity_view_pos.z + proximity_fade_distance, proximity_view_pos.z, VERTEX.z), 0.0, 1.0);
)";
}
@@ -1866,37 +1934,45 @@ void fragment() {)";
code += "}\n";
ShaderData shader_data;
- shader_data.shader = RS::get_singleton()->shader_create();
+ shader_data.shader = RS::get_singleton()->shader_create_from_code(code);
shader_data.users = 1;
-
- RS::get_singleton()->shader_set_code(shader_data.shader, code);
-
shader_map[mk] = shader_data;
+ shader_rid = shader_data.shader;
- RS::get_singleton()->material_set_shader(_get_material(), shader_data.shader);
+ if (_get_material().is_valid()) {
+ RS::get_singleton()->material_set_shader(_get_material(), shader_rid);
+ }
}
-void BaseMaterial3D::flush_changes() {
- MutexLock lock(material_mutex);
+void BaseMaterial3D::_check_material_rid() {
+ MutexLock lock(material_rid_mutex);
+ if (_get_material().is_null()) {
+ RID next_pass_rid;
+ if (get_next_pass().is_valid()) {
+ next_pass_rid = get_next_pass()->get_rid();
+ }
+
+ _set_material(RS::get_singleton()->material_create_from_shader(next_pass_rid, get_render_priority(), shader_rid));
+
+ for (KeyValue<StringName, Variant> param : pending_params) {
+ RS::get_singleton()->material_set_param(_get_material(), param.key, param.value);
+ }
- while (dirty_materials.first()) {
- dirty_materials.first()->self()->_update_shader();
- dirty_materials.first()->remove_from_list();
+ pending_params.clear();
}
}
-void BaseMaterial3D::_queue_shader_change() {
- MutexLock lock(material_mutex);
-
- if (_is_initialized() && !element.in_list()) {
- dirty_materials.add(&element);
+void BaseMaterial3D::_material_set_param(const StringName &p_name, const Variant &p_value) {
+ if (_get_material().is_valid()) {
+ RS::get_singleton()->material_set_param(_get_material(), p_name, p_value);
+ } else {
+ pending_params[p_name] = p_value;
}
}
void BaseMaterial3D::set_albedo(const Color &p_albedo) {
albedo = p_albedo;
-
- RS::get_singleton()->material_set_param(_get_material(), shader_names->albedo, p_albedo);
+ _material_set_param(shader_names->albedo, p_albedo);
}
Color BaseMaterial3D::get_albedo() const {
@@ -1905,7 +1981,7 @@ Color BaseMaterial3D::get_albedo() const {
void BaseMaterial3D::set_specular(float p_specular) {
specular = p_specular;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->specular, p_specular);
+ _material_set_param(shader_names->specular, p_specular);
}
float BaseMaterial3D::get_specular() const {
@@ -1914,7 +1990,7 @@ float BaseMaterial3D::get_specular() const {
void BaseMaterial3D::set_roughness(float p_roughness) {
roughness = p_roughness;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->roughness, p_roughness);
+ _material_set_param(shader_names->roughness, p_roughness);
}
float BaseMaterial3D::get_roughness() const {
@@ -1923,7 +1999,7 @@ float BaseMaterial3D::get_roughness() const {
void BaseMaterial3D::set_metallic(float p_metallic) {
metallic = p_metallic;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->metallic, p_metallic);
+ _material_set_param(shader_names->metallic, p_metallic);
}
float BaseMaterial3D::get_metallic() const {
@@ -1932,7 +2008,7 @@ float BaseMaterial3D::get_metallic() const {
void BaseMaterial3D::set_emission(const Color &p_emission) {
emission = p_emission;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->emission, p_emission);
+ _material_set_param(shader_names->emission, p_emission);
}
Color BaseMaterial3D::get_emission() const {
@@ -1941,10 +2017,11 @@ Color BaseMaterial3D::get_emission() const {
void BaseMaterial3D::set_emission_energy_multiplier(float p_emission_energy_multiplier) {
emission_energy_multiplier = p_emission_energy_multiplier;
+
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
- RS::get_singleton()->material_set_param(_get_material(), shader_names->emission_energy, p_emission_energy_multiplier * emission_intensity);
+ _material_set_param(shader_names->emission_energy, p_emission_energy_multiplier * emission_intensity);
} else {
- RS::get_singleton()->material_set_param(_get_material(), shader_names->emission_energy, p_emission_energy_multiplier);
+ _material_set_param(shader_names->emission_energy, p_emission_energy_multiplier);
}
}
@@ -1955,7 +2032,7 @@ float BaseMaterial3D::get_emission_energy_multiplier() const {
void BaseMaterial3D::set_emission_intensity(float p_emission_intensity) {
ERR_FAIL_COND_EDMSG(!GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units"), "Cannot set material emission intensity when Physical Light Units disabled.");
emission_intensity = p_emission_intensity;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->emission_energy, emission_energy_multiplier * emission_intensity);
+ _material_set_param(shader_names->emission_energy, emission_energy_multiplier * emission_intensity);
}
float BaseMaterial3D::get_emission_intensity() const {
@@ -1964,7 +2041,7 @@ float BaseMaterial3D::get_emission_intensity() const {
void BaseMaterial3D::set_normal_scale(float p_normal_scale) {
normal_scale = p_normal_scale;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->normal_scale, p_normal_scale);
+ _material_set_param(shader_names->normal_scale, p_normal_scale);
}
float BaseMaterial3D::get_normal_scale() const {
@@ -1973,7 +2050,7 @@ float BaseMaterial3D::get_normal_scale() const {
void BaseMaterial3D::set_rim(float p_rim) {
rim = p_rim;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->rim, p_rim);
+ _material_set_param(shader_names->rim, p_rim);
}
float BaseMaterial3D::get_rim() const {
@@ -1982,7 +2059,7 @@ float BaseMaterial3D::get_rim() const {
void BaseMaterial3D::set_rim_tint(float p_rim_tint) {
rim_tint = p_rim_tint;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->rim_tint, p_rim_tint);
+ _material_set_param(shader_names->rim_tint, p_rim_tint);
}
float BaseMaterial3D::get_rim_tint() const {
@@ -1991,7 +2068,7 @@ float BaseMaterial3D::get_rim_tint() const {
void BaseMaterial3D::set_ao_light_affect(float p_ao_light_affect) {
ao_light_affect = p_ao_light_affect;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->ao_light_affect, p_ao_light_affect);
+ _material_set_param(shader_names->ao_light_affect, p_ao_light_affect);
}
float BaseMaterial3D::get_ao_light_affect() const {
@@ -2000,7 +2077,7 @@ float BaseMaterial3D::get_ao_light_affect() const {
void BaseMaterial3D::set_clearcoat(float p_clearcoat) {
clearcoat = p_clearcoat;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->clearcoat, p_clearcoat);
+ _material_set_param(shader_names->clearcoat, p_clearcoat);
}
float BaseMaterial3D::get_clearcoat() const {
@@ -2009,7 +2086,7 @@ float BaseMaterial3D::get_clearcoat() const {
void BaseMaterial3D::set_clearcoat_roughness(float p_clearcoat_roughness) {
clearcoat_roughness = p_clearcoat_roughness;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->clearcoat_roughness, p_clearcoat_roughness);
+ _material_set_param(shader_names->clearcoat_roughness, p_clearcoat_roughness);
}
float BaseMaterial3D::get_clearcoat_roughness() const {
@@ -2018,7 +2095,7 @@ float BaseMaterial3D::get_clearcoat_roughness() const {
void BaseMaterial3D::set_anisotropy(float p_anisotropy) {
anisotropy = p_anisotropy;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->anisotropy, p_anisotropy);
+ _material_set_param(shader_names->anisotropy, p_anisotropy);
}
float BaseMaterial3D::get_anisotropy() const {
@@ -2027,7 +2104,7 @@ float BaseMaterial3D::get_anisotropy() const {
void BaseMaterial3D::set_heightmap_scale(float p_heightmap_scale) {
heightmap_scale = p_heightmap_scale;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_scale, p_heightmap_scale);
+ _material_set_param(shader_names->heightmap_scale, p_heightmap_scale);
}
float BaseMaterial3D::get_heightmap_scale() const {
@@ -2036,7 +2113,7 @@ float BaseMaterial3D::get_heightmap_scale() const {
void BaseMaterial3D::set_subsurface_scattering_strength(float p_subsurface_scattering_strength) {
subsurface_scattering_strength = p_subsurface_scattering_strength;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->subsurface_scattering_strength, subsurface_scattering_strength);
+ _material_set_param(shader_names->subsurface_scattering_strength, subsurface_scattering_strength);
}
float BaseMaterial3D::get_subsurface_scattering_strength() const {
@@ -2045,7 +2122,7 @@ float BaseMaterial3D::get_subsurface_scattering_strength() const {
void BaseMaterial3D::set_transmittance_color(const Color &p_color) {
transmittance_color = p_color;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_color, p_color);
+ _material_set_param(shader_names->transmittance_color, p_color);
}
Color BaseMaterial3D::get_transmittance_color() const {
@@ -2054,7 +2131,7 @@ Color BaseMaterial3D::get_transmittance_color() const {
void BaseMaterial3D::set_transmittance_depth(float p_depth) {
transmittance_depth = p_depth;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_depth, p_depth);
+ _material_set_param(shader_names->transmittance_depth, p_depth);
}
float BaseMaterial3D::get_transmittance_depth() const {
@@ -2063,7 +2140,7 @@ float BaseMaterial3D::get_transmittance_depth() const {
void BaseMaterial3D::set_transmittance_boost(float p_boost) {
transmittance_boost = p_boost;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->transmittance_boost, p_boost);
+ _material_set_param(shader_names->transmittance_boost, p_boost);
}
float BaseMaterial3D::get_transmittance_boost() const {
@@ -2072,7 +2149,7 @@ float BaseMaterial3D::get_transmittance_boost() const {
void BaseMaterial3D::set_backlight(const Color &p_backlight) {
backlight = p_backlight;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->backlight, backlight);
+ _material_set_param(shader_names->backlight, backlight);
}
Color BaseMaterial3D::get_backlight() const {
@@ -2081,7 +2158,7 @@ Color BaseMaterial3D::get_backlight() const {
void BaseMaterial3D::set_refraction(float p_refraction) {
refraction = p_refraction;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->refraction, refraction);
+ _material_set_param(shader_names->refraction, refraction);
}
float BaseMaterial3D::get_refraction() const {
@@ -2094,7 +2171,7 @@ void BaseMaterial3D::set_detail_uv(DetailUV p_detail_uv) {
}
detail_uv = p_detail_uv;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::DetailUV BaseMaterial3D::get_detail_uv() const {
@@ -2107,7 +2184,7 @@ void BaseMaterial3D::set_blend_mode(BlendMode p_mode) {
}
blend_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::BlendMode BaseMaterial3D::get_blend_mode() const {
@@ -2116,7 +2193,7 @@ BaseMaterial3D::BlendMode BaseMaterial3D::get_blend_mode() const {
void BaseMaterial3D::set_detail_blend_mode(BlendMode p_mode) {
detail_blend_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::BlendMode BaseMaterial3D::get_detail_blend_mode() const {
@@ -2129,7 +2206,7 @@ void BaseMaterial3D::set_transparency(Transparency p_transparency) {
}
transparency = p_transparency;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2143,7 +2220,7 @@ void BaseMaterial3D::set_alpha_antialiasing(AlphaAntiAliasing p_alpha_aa) {
}
alpha_antialiasing_mode = p_alpha_aa;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2157,7 +2234,7 @@ void BaseMaterial3D::set_shading_mode(ShadingMode p_shading_mode) {
}
shading_mode = p_shading_mode;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2171,7 +2248,7 @@ void BaseMaterial3D::set_depth_draw_mode(DepthDrawMode p_mode) {
}
depth_draw_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::DepthDrawMode BaseMaterial3D::get_depth_draw_mode() const {
@@ -2184,7 +2261,7 @@ void BaseMaterial3D::set_cull_mode(CullMode p_mode) {
}
cull_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::CullMode BaseMaterial3D::get_cull_mode() const {
@@ -2197,7 +2274,7 @@ void BaseMaterial3D::set_diffuse_mode(DiffuseMode p_mode) {
}
diffuse_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::DiffuseMode BaseMaterial3D::get_diffuse_mode() const {
@@ -2210,7 +2287,7 @@ void BaseMaterial3D::set_specular_mode(SpecularMode p_mode) {
}
specular_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::SpecularMode BaseMaterial3D::get_specular_mode() const {
@@ -2240,7 +2317,7 @@ void BaseMaterial3D::set_flag(Flags p_flag, bool p_enabled) {
update_configuration_warning();
}
- _queue_shader_change();
+ _mark_dirty();
}
bool BaseMaterial3D::get_flag(Flags p_flag) const {
@@ -2256,7 +2333,7 @@ void BaseMaterial3D::set_feature(Feature p_feature, bool p_enabled) {
features[p_feature] = p_enabled;
notify_property_list_changed();
- _queue_shader_change();
+ _mark_dirty();
}
bool BaseMaterial3D::get_feature(Feature p_feature) const {
@@ -2269,15 +2346,14 @@ void BaseMaterial3D::set_texture(TextureParam p_param, const Ref<Texture2D> &p_t
textures[p_param] = p_texture;
Variant rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant();
- RS::get_singleton()->material_set_param(_get_material(), shader_names->texture_names[p_param], rid);
+ _material_set_param(shader_names->texture_names[p_param], rid);
if (p_texture.is_valid() && p_param == TEXTURE_ALBEDO) {
- RS::get_singleton()->material_set_param(_get_material(), shader_names->albedo_texture_size,
- Vector2i(p_texture->get_width(), p_texture->get_height()));
+ _material_set_param(shader_names->albedo_texture_size, Vector2i(p_texture->get_width(), p_texture->get_height()));
}
notify_property_list_changed();
- _queue_shader_change();
+ _mark_dirty();
}
Ref<Texture2D> BaseMaterial3D::get_texture(TextureParam p_param) const {
@@ -2297,7 +2373,7 @@ Ref<Texture2D> BaseMaterial3D::get_texture_by_name(const StringName &p_name) con
void BaseMaterial3D::set_texture_filter(TextureFilter p_filter) {
texture_filter = p_filter;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::TextureFilter BaseMaterial3D::get_texture_filter() const {
@@ -2469,7 +2545,7 @@ void BaseMaterial3D::_validate_property(PropertyInfo &p_property) const {
void BaseMaterial3D::set_point_size(float p_point_size) {
point_size = p_point_size;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->point_size, p_point_size);
+ _material_set_param(shader_names->point_size, p_point_size);
}
float BaseMaterial3D::get_point_size() const {
@@ -2478,7 +2554,7 @@ float BaseMaterial3D::get_point_size() const {
void BaseMaterial3D::set_uv1_scale(const Vector3 &p_scale) {
uv1_scale = p_scale;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->uv1_scale, p_scale);
+ _material_set_param(shader_names->uv1_scale, p_scale);
}
Vector3 BaseMaterial3D::get_uv1_scale() const {
@@ -2487,7 +2563,7 @@ Vector3 BaseMaterial3D::get_uv1_scale() const {
void BaseMaterial3D::set_uv1_offset(const Vector3 &p_offset) {
uv1_offset = p_offset;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->uv1_offset, p_offset);
+ _material_set_param(shader_names->uv1_offset, p_offset);
}
Vector3 BaseMaterial3D::get_uv1_offset() const {
@@ -2497,7 +2573,7 @@ Vector3 BaseMaterial3D::get_uv1_offset() const {
void BaseMaterial3D::set_uv1_triplanar_blend_sharpness(float p_sharpness) {
// Negative values or values higher than 150 can result in NaNs, leading to broken rendering.
uv1_triplanar_sharpness = CLAMP(p_sharpness, 0.0, 150.0);
- RS::get_singleton()->material_set_param(_get_material(), shader_names->uv1_blend_sharpness, uv1_triplanar_sharpness);
+ _material_set_param(shader_names->uv1_blend_sharpness, uv1_triplanar_sharpness);
}
float BaseMaterial3D::get_uv1_triplanar_blend_sharpness() const {
@@ -2506,7 +2582,7 @@ float BaseMaterial3D::get_uv1_triplanar_blend_sharpness() const {
void BaseMaterial3D::set_uv2_scale(const Vector3 &p_scale) {
uv2_scale = p_scale;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->uv2_scale, p_scale);
+ _material_set_param(shader_names->uv2_scale, p_scale);
}
Vector3 BaseMaterial3D::get_uv2_scale() const {
@@ -2515,7 +2591,7 @@ Vector3 BaseMaterial3D::get_uv2_scale() const {
void BaseMaterial3D::set_uv2_offset(const Vector3 &p_offset) {
uv2_offset = p_offset;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->uv2_offset, p_offset);
+ _material_set_param(shader_names->uv2_offset, p_offset);
}
Vector3 BaseMaterial3D::get_uv2_offset() const {
@@ -2525,7 +2601,7 @@ Vector3 BaseMaterial3D::get_uv2_offset() const {
void BaseMaterial3D::set_uv2_triplanar_blend_sharpness(float p_sharpness) {
// Negative values or values higher than 150 can result in NaNs, leading to broken rendering.
uv2_triplanar_sharpness = CLAMP(p_sharpness, 0.0, 150.0);
- RS::get_singleton()->material_set_param(_get_material(), shader_names->uv2_blend_sharpness, uv2_triplanar_sharpness);
+ _material_set_param(shader_names->uv2_blend_sharpness, uv2_triplanar_sharpness);
}
float BaseMaterial3D::get_uv2_triplanar_blend_sharpness() const {
@@ -2534,7 +2610,7 @@ float BaseMaterial3D::get_uv2_triplanar_blend_sharpness() const {
void BaseMaterial3D::set_billboard_mode(BillboardMode p_mode) {
billboard_mode = p_mode;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2544,7 +2620,7 @@ BaseMaterial3D::BillboardMode BaseMaterial3D::get_billboard_mode() const {
void BaseMaterial3D::set_particles_anim_h_frames(int p_frames) {
particles_anim_h_frames = p_frames;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_h_frames, p_frames);
+ _material_set_param(shader_names->particles_anim_h_frames, p_frames);
}
int BaseMaterial3D::get_particles_anim_h_frames() const {
@@ -2553,7 +2629,7 @@ int BaseMaterial3D::get_particles_anim_h_frames() const {
void BaseMaterial3D::set_particles_anim_v_frames(int p_frames) {
particles_anim_v_frames = p_frames;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_v_frames, p_frames);
+ _material_set_param(shader_names->particles_anim_v_frames, p_frames);
}
int BaseMaterial3D::get_particles_anim_v_frames() const {
@@ -2562,7 +2638,7 @@ int BaseMaterial3D::get_particles_anim_v_frames() const {
void BaseMaterial3D::set_particles_anim_loop(bool p_loop) {
particles_anim_loop = p_loop;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->particles_anim_loop, particles_anim_loop);
+ _material_set_param(shader_names->particles_anim_loop, particles_anim_loop);
}
bool BaseMaterial3D::get_particles_anim_loop() const {
@@ -2571,7 +2647,7 @@ bool BaseMaterial3D::get_particles_anim_loop() const {
void BaseMaterial3D::set_heightmap_deep_parallax(bool p_enable) {
deep_parallax = p_enable;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2581,7 +2657,7 @@ bool BaseMaterial3D::is_heightmap_deep_parallax_enabled() const {
void BaseMaterial3D::set_heightmap_deep_parallax_min_layers(int p_layer) {
deep_parallax_min_layers = p_layer;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_min_layers, p_layer);
+ _material_set_param(shader_names->heightmap_min_layers, p_layer);
}
int BaseMaterial3D::get_heightmap_deep_parallax_min_layers() const {
@@ -2590,7 +2666,7 @@ int BaseMaterial3D::get_heightmap_deep_parallax_min_layers() const {
void BaseMaterial3D::set_heightmap_deep_parallax_max_layers(int p_layer) {
deep_parallax_max_layers = p_layer;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_max_layers, p_layer);
+ _material_set_param(shader_names->heightmap_max_layers, p_layer);
}
int BaseMaterial3D::get_heightmap_deep_parallax_max_layers() const {
@@ -2599,7 +2675,7 @@ int BaseMaterial3D::get_heightmap_deep_parallax_max_layers() const {
void BaseMaterial3D::set_heightmap_deep_parallax_flip_tangent(bool p_flip) {
heightmap_parallax_flip_tangent = p_flip;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1));
+ _material_set_param(shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1));
}
bool BaseMaterial3D::get_heightmap_deep_parallax_flip_tangent() const {
@@ -2608,7 +2684,7 @@ bool BaseMaterial3D::get_heightmap_deep_parallax_flip_tangent() const {
void BaseMaterial3D::set_heightmap_deep_parallax_flip_binormal(bool p_flip) {
heightmap_parallax_flip_binormal = p_flip;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1));
+ _material_set_param(shader_names->heightmap_flip, Vector2(heightmap_parallax_flip_tangent ? -1 : 1, heightmap_parallax_flip_binormal ? -1 : 1));
}
bool BaseMaterial3D::get_heightmap_deep_parallax_flip_binormal() const {
@@ -2617,7 +2693,7 @@ bool BaseMaterial3D::get_heightmap_deep_parallax_flip_binormal() const {
void BaseMaterial3D::set_grow_enabled(bool p_enable) {
grow_enabled = p_enable;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2627,7 +2703,7 @@ bool BaseMaterial3D::is_grow_enabled() const {
void BaseMaterial3D::set_alpha_scissor_threshold(float p_threshold) {
alpha_scissor_threshold = p_threshold;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->alpha_scissor_threshold, p_threshold);
+ _material_set_param(shader_names->alpha_scissor_threshold, p_threshold);
}
float BaseMaterial3D::get_alpha_scissor_threshold() const {
@@ -2636,7 +2712,7 @@ float BaseMaterial3D::get_alpha_scissor_threshold() const {
void BaseMaterial3D::set_alpha_hash_scale(float p_scale) {
alpha_hash_scale = p_scale;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->alpha_hash_scale, p_scale);
+ _material_set_param(shader_names->alpha_hash_scale, p_scale);
}
float BaseMaterial3D::get_alpha_hash_scale() const {
@@ -2645,7 +2721,7 @@ float BaseMaterial3D::get_alpha_hash_scale() const {
void BaseMaterial3D::set_alpha_antialiasing_edge(float p_edge) {
alpha_antialiasing_edge = p_edge;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->alpha_antialiasing_edge, p_edge);
+ _material_set_param(shader_names->alpha_antialiasing_edge, p_edge);
}
float BaseMaterial3D::get_alpha_antialiasing_edge() const {
@@ -2654,7 +2730,7 @@ float BaseMaterial3D::get_alpha_antialiasing_edge() const {
void BaseMaterial3D::set_grow(float p_grow) {
grow = p_grow;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->grow, p_grow);
+ _material_set_param(shader_names->grow, p_grow);
}
float BaseMaterial3D::get_grow() const {
@@ -2676,7 +2752,7 @@ static Plane _get_texture_mask(BaseMaterial3D::TextureChannel p_channel) {
void BaseMaterial3D::set_metallic_texture_channel(TextureChannel p_channel) {
ERR_FAIL_INDEX(p_channel, 5);
metallic_texture_channel = p_channel;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->metallic_texture_channel, _get_texture_mask(p_channel));
+ _material_set_param(shader_names->metallic_texture_channel, _get_texture_mask(p_channel));
}
BaseMaterial3D::TextureChannel BaseMaterial3D::get_metallic_texture_channel() const {
@@ -2686,7 +2762,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_metallic_texture_channel() co
void BaseMaterial3D::set_roughness_texture_channel(TextureChannel p_channel) {
ERR_FAIL_INDEX(p_channel, 5);
roughness_texture_channel = p_channel;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::TextureChannel BaseMaterial3D::get_roughness_texture_channel() const {
@@ -2696,7 +2772,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_roughness_texture_channel() c
void BaseMaterial3D::set_ao_texture_channel(TextureChannel p_channel) {
ERR_FAIL_INDEX(p_channel, 5);
ao_texture_channel = p_channel;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->ao_texture_channel, _get_texture_mask(p_channel));
+ _material_set_param(shader_names->ao_texture_channel, _get_texture_mask(p_channel));
}
BaseMaterial3D::TextureChannel BaseMaterial3D::get_ao_texture_channel() const {
@@ -2706,7 +2782,7 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_ao_texture_channel() const {
void BaseMaterial3D::set_refraction_texture_channel(TextureChannel p_channel) {
ERR_FAIL_INDEX(p_channel, 5);
refraction_texture_channel = p_channel;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->refraction_texture_channel, _get_texture_mask(p_channel));
+ _material_set_param(shader_names->refraction_texture_channel, _get_texture_mask(p_channel));
}
BaseMaterial3D::TextureChannel BaseMaterial3D::get_refraction_texture_channel() const {
@@ -2768,7 +2844,7 @@ void BaseMaterial3D::set_on_top_of_alpha() {
void BaseMaterial3D::set_proximity_fade_enabled(bool p_enable) {
proximity_fade_enabled = p_enable;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2778,7 +2854,7 @@ bool BaseMaterial3D::is_proximity_fade_enabled() const {
void BaseMaterial3D::set_proximity_fade_distance(float p_distance) {
proximity_fade_distance = MAX(p_distance, 0.01);
- RS::get_singleton()->material_set_param(_get_material(), shader_names->proximity_fade_distance, proximity_fade_distance);
+ _material_set_param(shader_names->proximity_fade_distance, proximity_fade_distance);
}
float BaseMaterial3D::get_proximity_fade_distance() const {
@@ -2787,7 +2863,7 @@ float BaseMaterial3D::get_proximity_fade_distance() const {
void BaseMaterial3D::set_msdf_pixel_range(float p_range) {
msdf_pixel_range = p_range;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->msdf_pixel_range, p_range);
+ _material_set_param(shader_names->msdf_pixel_range, p_range);
}
float BaseMaterial3D::get_msdf_pixel_range() const {
@@ -2796,7 +2872,7 @@ float BaseMaterial3D::get_msdf_pixel_range() const {
void BaseMaterial3D::set_msdf_outline_size(float p_size) {
msdf_outline_size = p_size;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->msdf_outline_size, p_size);
+ _material_set_param(shader_names->msdf_outline_size, p_size);
}
float BaseMaterial3D::get_msdf_outline_size() const {
@@ -2805,7 +2881,7 @@ float BaseMaterial3D::get_msdf_outline_size() const {
void BaseMaterial3D::set_distance_fade(DistanceFadeMode p_mode) {
distance_fade = p_mode;
- _queue_shader_change();
+ _mark_dirty();
notify_property_list_changed();
}
@@ -2815,7 +2891,7 @@ BaseMaterial3D::DistanceFadeMode BaseMaterial3D::get_distance_fade() const {
void BaseMaterial3D::set_distance_fade_max_distance(float p_distance) {
distance_fade_max_distance = p_distance;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->distance_fade_max, distance_fade_max_distance);
+ _material_set_param(shader_names->distance_fade_max, distance_fade_max_distance);
}
float BaseMaterial3D::get_distance_fade_max_distance() const {
@@ -2824,7 +2900,7 @@ float BaseMaterial3D::get_distance_fade_max_distance() const {
void BaseMaterial3D::set_distance_fade_min_distance(float p_distance) {
distance_fade_min_distance = p_distance;
- RS::get_singleton()->material_set_param(_get_material(), shader_names->distance_fade_min, distance_fade_min_distance);
+ _material_set_param(shader_names->distance_fade_min, distance_fade_min_distance);
}
float BaseMaterial3D::get_distance_fade_min_distance() const {
@@ -2836,20 +2912,22 @@ void BaseMaterial3D::set_emission_operator(EmissionOperator p_op) {
return;
}
emission_op = p_op;
- _queue_shader_change();
+ _mark_dirty();
}
BaseMaterial3D::EmissionOperator BaseMaterial3D::get_emission_operator() const {
return emission_op;
}
+RID BaseMaterial3D::get_rid() const {
+ const_cast<BaseMaterial3D *>(this)->_update_shader();
+ const_cast<BaseMaterial3D *>(this)->_check_material_rid();
+ return _get_material();
+}
+
RID BaseMaterial3D::get_shader_rid() const {
- MutexLock lock(material_mutex);
- if (element.in_list()) {
- ((BaseMaterial3D *)this)->_update_shader();
- }
- ERR_FAIL_COND_V(!shader_map.has(current_key), RID());
- return shader_map[current_key].shader;
+ const_cast<BaseMaterial3D *>(this)->_update_shader();
+ return shader_rid;
}
Shader::Mode BaseMaterial3D::get_shader_mode() const {
@@ -3365,8 +3443,7 @@ void BaseMaterial3D::_bind_methods() {
BIND_ENUM_CONSTANT(DISTANCE_FADE_OBJECT_DITHER);
}
-BaseMaterial3D::BaseMaterial3D(bool p_orm) :
- element(this) {
+BaseMaterial3D::BaseMaterial3D(bool p_orm) {
orm = p_orm;
// Initialize to the same values as the shader
set_albedo(Color(1.0, 1.0, 1.0, 1.0));
@@ -3433,21 +3510,25 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) :
current_key.invalid_key = 1;
- _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change), callable_mp(this, &BaseMaterial3D::_update_shader));
+ _mark_dirty();
}
BaseMaterial3D::~BaseMaterial3D() {
- ERR_FAIL_NULL(RenderingServer::get_singleton());
- MutexLock lock(material_mutex);
+ ERR_FAIL_NULL(RS::get_singleton());
- if (shader_map.has(current_key)) {
- shader_map[current_key].users--;
- if (shader_map[current_key].users == 0) {
- //deallocate shader, as it's no longer in use
- RS::get_singleton()->free(shader_map[current_key].shader);
- shader_map.erase(current_key);
+ {
+ MutexLock lock(shader_map_mutex);
+ if (shader_map.has(current_key)) {
+ shader_map[current_key].users--;
+ if (shader_map[current_key].users == 0) {
+ // Deallocate shader which is no longer in use.
+ RS::get_singleton()->free(shader_map[current_key].shader);
+ shader_map.erase(current_key);
+ }
}
+ }
+ if (_get_material().is_valid()) {
RS::get_singleton()->material_set_shader(_get_material(), RID());
}
}
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 25c4450682..edd82b779e 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -42,7 +42,7 @@ class Material : public Resource {
RES_BASE_EXTENSION("material")
OBJ_SAVE_TYPE(Material);
- RID material;
+ mutable RID material;
Ref<Material> next_pass;
int render_priority;
@@ -55,6 +55,7 @@ class Material : public Resource {
void inspect_native_shader_code();
protected:
+ _FORCE_INLINE_ void _set_material(RID p_material) const { material = p_material; }
_FORCE_INLINE_ RID _get_material() const { return material; }
static void _bind_methods();
virtual bool _can_do_next_pass() const;
@@ -97,6 +98,7 @@ class ShaderMaterial : public Material {
mutable HashMap<StringName, StringName> remap_cache;
mutable HashMap<StringName, Variant> param_cache;
+ mutable Mutex material_rid_mutex;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
@@ -115,6 +117,7 @@ protected:
virtual bool _can_use_render_priority() const override;
void _shader_changed();
+ void _check_material_rid() const;
public:
void set_shader(const Ref<Shader> &p_shader);
@@ -125,6 +128,7 @@ public:
virtual Shader::Mode get_shader_mode() const override;
+ virtual RID get_rid() const override;
virtual RID get_shader_rid() const override;
ShaderMaterial();
@@ -136,6 +140,9 @@ class StandardMaterial3D;
class BaseMaterial3D : public Material {
GDCLASS(BaseMaterial3D, Material);
+private:
+ mutable Mutex material_rid_mutex;
+
public:
enum TextureParam {
TEXTURE_ALBEDO,
@@ -361,6 +368,7 @@ private:
};
static HashMap<MaterialKey, ShaderData, MaterialKey> shader_map;
+ static Mutex shader_map_mutex;
MaterialKey current_key;
@@ -459,16 +467,17 @@ private:
StringName albedo_texture_size;
};
- static Mutex material_mutex;
- static SelfList<BaseMaterial3D>::List dirty_materials;
static ShaderNames *shader_names;
- SelfList<BaseMaterial3D> element;
-
+ void _mark_dirty();
void _update_shader();
- _FORCE_INLINE_ void _queue_shader_change();
+ void _check_material_rid();
+ void _material_set_param(const StringName &p_name, const Variant &p_value);
bool orm;
+ bool dirty = true;
+ RID shader_rid;
+ HashMap<StringName, Variant> pending_params;
Color albedo;
float specular = 0.0f;
@@ -771,10 +780,10 @@ public:
static void init_shaders();
static void finish_shaders();
- static void flush_changes();
static Ref<Material> get_material_for_2d(bool p_shaded, Transparency p_transparency, bool p_double_sided, bool p_billboard = false, bool p_billboard_y = false, bool p_msdf = false, bool p_no_depth = false, bool p_fixed_size = false, TextureFilter p_filter = TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, AlphaAntiAliasing p_alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF, RID *r_shader_rid = nullptr);
+ virtual RID get_rid() const override;
virtual RID get_shader_rid() const override;
virtual Shader::Mode get_shader_mode() const override;
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index 8cfe4c92b7..09bc1fa8e4 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -2261,6 +2261,8 @@ void ParticleProcessMaterial::_bind_methods() {
ParticleProcessMaterial::ParticleProcessMaterial() :
element(this) {
+ _set_material(RS::get_singleton()->material_create());
+
set_direction(Vector3(1, 0, 0));
set_spread(45);
set_flatness(0);
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index d531eea311..e234a81c88 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -612,29 +612,27 @@ Error ResourceLoaderText::load() {
}
}
- if (ClassDB::has_property(res->get_class_name(), assign)) {
- if (value.get_type() == Variant::ARRAY) {
- Array set_array = value;
- bool is_get_valid = false;
- Variant get_value = res->get(assign, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
- Array get_array = get_value;
- if (!set_array.is_same_typed(get_array)) {
- value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
- }
+ if (value.get_type() == Variant::ARRAY) {
+ Array set_array = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
+ Array get_array = get_value;
+ if (!set_array.is_same_typed(get_array)) {
+ value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
}
}
+ }
- if (value.get_type() == Variant::DICTIONARY) {
- Dictionary set_dict = value;
- bool is_get_valid = false;
- Variant get_value = res->get(assign, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
- Dictionary get_dict = get_value;
- if (!set_dict.is_same_typed(get_dict)) {
- value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
- get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
- }
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = res->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
}
}
}
@@ -754,29 +752,27 @@ Error ResourceLoaderText::load() {
}
}
- if (ClassDB::has_property(resource->get_class_name(), assign)) {
- if (value.get_type() == Variant::ARRAY) {
- Array set_array = value;
- bool is_get_valid = false;
- Variant get_value = resource->get(assign, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
- Array get_array = get_value;
- if (!set_array.is_same_typed(get_array)) {
- value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
- }
+ if (value.get_type() == Variant::ARRAY) {
+ Array set_array = value;
+ bool is_get_valid = false;
+ Variant get_value = resource->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::ARRAY) {
+ Array get_array = get_value;
+ if (!set_array.is_same_typed(get_array)) {
+ value = Array(set_array, get_array.get_typed_builtin(), get_array.get_typed_class_name(), get_array.get_typed_script());
}
}
+ }
- if (value.get_type() == Variant::DICTIONARY) {
- Dictionary set_dict = value;
- bool is_get_valid = false;
- Variant get_value = resource->get(assign, &is_get_valid);
- if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
- Dictionary get_dict = get_value;
- if (!set_dict.is_same_typed(get_dict)) {
- value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
- get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
- }
+ if (value.get_type() == Variant::DICTIONARY) {
+ Dictionary set_dict = value;
+ bool is_get_valid = false;
+ Variant get_value = resource->get(assign, &is_get_valid);
+ if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) {
+ Dictionary get_dict = get_value;
+ if (!set_dict.is_same_typed(get_dict)) {
+ value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(),
+ get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script());
}
}
}
@@ -1708,6 +1704,8 @@ static String _resource_get_class(Ref<Resource> p_resource) {
}
Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) {
+ Resource::seed_scene_unique_id(p_path.hash()); // Seeding for save path should make it deterministic for importers.
+
if (p_path.ends_with(".tscn")) {
packed_scene = p_resource;
}
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index 46d38146a6..24d17108d5 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -50,6 +50,14 @@ Shader::Mode Shader::get_mode() const {
return mode;
}
+void Shader::_check_shader_rid() const {
+ MutexLock lock(shader_rid_mutex);
+ if (shader_rid.is_null() && !preprocessed_code.is_empty()) {
+ shader_rid = RenderingServer::get_singleton()->shader_create_from_code(preprocessed_code, get_path());
+ preprocessed_code = String();
+ }
+}
+
void Shader::_dependency_changed() {
// Preprocess and compile the code again because a dependency has changed. It also calls emit_changed() for us.
_recompile();
@@ -61,7 +69,10 @@ void Shader::_recompile() {
void Shader::set_path(const String &p_path, bool p_take_over) {
Resource::set_path(p_path, p_take_over);
- RS::get_singleton()->shader_set_path_hint(shader, p_path);
+
+ if (shader_rid.is_valid()) {
+ RS::get_singleton()->shader_set_path_hint(shader_rid, p_path);
+ }
}
void Shader::set_include_path(const String &p_path) {
@@ -76,7 +87,7 @@ void Shader::set_code(const String &p_code) {
}
code = p_code;
- String pp_code = p_code;
+ preprocessed_code = p_code;
{
String path = get_path();
@@ -88,7 +99,7 @@ void Shader::set_code(const String &p_code) {
// 2) Server does not do interaction with Resource filetypes, this is a scene level feature.
HashSet<Ref<ShaderInclude>> new_include_dependencies;
ShaderPreprocessor preprocessor;
- Error result = preprocessor.preprocess(p_code, path, pp_code, nullptr, nullptr, nullptr, &new_include_dependencies);
+ Error result = preprocessor.preprocess(p_code, path, preprocessed_code, nullptr, nullptr, nullptr, &new_include_dependencies);
if (result == OK) {
// This ensures previous include resources are not freed and then re-loaded during parse (which would make compiling slower)
include_dependencies = new_include_dependencies;
@@ -96,7 +107,7 @@ void Shader::set_code(const String &p_code) {
}
// Try to get the shader type from the final, fully preprocessed shader code.
- String type = ShaderLanguage::get_shader_type(pp_code);
+ String type = ShaderLanguage::get_shader_type(preprocessed_code);
if (type == "canvas_item") {
mode = MODE_CANVAS_ITEM;
@@ -114,7 +125,10 @@ void Shader::set_code(const String &p_code) {
E->connect_changed(callable_mp(this, &Shader::_dependency_changed));
}
- RenderingServer::get_singleton()->shader_set_code(shader, pp_code);
+ if (shader_rid.is_valid()) {
+ RenderingServer::get_singleton()->shader_set_code(shader_rid, preprocessed_code);
+ preprocessed_code = String();
+ }
emit_changed();
}
@@ -126,9 +140,10 @@ String Shader::get_code() const {
void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups) const {
_update_shader();
+ _check_shader_rid();
List<PropertyInfo> local;
- RenderingServer::get_singleton()->get_shader_parameter_list(shader, &local);
+ RenderingServer::get_singleton()->get_shader_parameter_list(shader_rid, &local);
#ifdef TOOLS_ENABLED
DocData::ClassDoc class_doc;
@@ -182,17 +197,20 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr
RID Shader::get_rid() const {
_update_shader();
+ _check_shader_rid();
- return shader;
+ return shader_rid;
}
void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index) {
+ _check_shader_rid();
+
if (p_texture.is_valid()) {
if (!default_textures.has(p_name)) {
default_textures[p_name] = HashMap<int, Ref<Texture>>();
}
default_textures[p_name][p_index] = p_texture;
- RS::get_singleton()->shader_set_default_texture_parameter(shader, p_name, p_texture->get_rid(), p_index);
+ RS::get_singleton()->shader_set_default_texture_parameter(shader_rid, p_name, p_texture->get_rid(), p_index);
} else {
if (default_textures.has(p_name) && default_textures[p_name].has(p_index)) {
default_textures[p_name].erase(p_index);
@@ -201,7 +219,7 @@ void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<T
default_textures.erase(p_name);
}
}
- RS::get_singleton()->shader_set_default_texture_parameter(shader, p_name, RID(), p_index);
+ RS::get_singleton()->shader_set_default_texture_parameter(shader_rid, p_name, RID(), p_index);
}
emit_changed();
@@ -225,6 +243,7 @@ bool Shader::is_text_shader() const {
}
void Shader::_update_shader() const {
+ // Base implementation does nothing.
}
Array Shader::_get_shader_uniform_list(bool p_get_groups) {
@@ -258,12 +277,14 @@ void Shader::_bind_methods() {
}
Shader::Shader() {
- shader = RenderingServer::get_singleton()->shader_create();
+ // Shader RID will be empty until it is required.
}
Shader::~Shader() {
- ERR_FAIL_NULL(RenderingServer::get_singleton());
- RenderingServer::get_singleton()->free(shader);
+ if (shader_rid.is_valid()) {
+ ERR_FAIL_NULL(RenderingServer::get_singleton());
+ RenderingServer::get_singleton()->free(shader_rid);
+ }
}
////////////
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 682fbd7ea6..18197419f3 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -52,7 +52,10 @@ public:
};
private:
- RID shader;
+ mutable RID shader_rid;
+ mutable String preprocessed_code;
+ mutable Mutex shader_rid_mutex;
+
Mode mode = MODE_SPATIAL;
HashSet<Ref<ShaderInclude>> include_dependencies;
String code;
@@ -60,6 +63,7 @@ private:
HashMap<StringName, HashMap<int, Ref<Texture>>> default_textures;
+ void _check_shader_rid() const;
void _dependency_changed();
void _recompile();
virtual void _update_shader() const; //used for visual shader
diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp
index 60b91ef0cb..15816925c1 100644
--- a/scene/resources/style_box_flat.cpp
+++ b/scene/resources/style_box_flat.cpp
@@ -226,33 +226,16 @@ inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_re
real_t border_right = style_rect.size.width - inner_rect.size.width - border_left;
real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top;
- real_t rad;
-
- // Top left.
- rad = MIN(border_top, border_left);
- inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0);
-
- // Top right;
- rad = MIN(border_top, border_right);
- inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0);
-
- // Bottom right.
- rad = MIN(border_bottom, border_right);
- inner_corner_radius[2] = MAX(corner_radius[2] - rad, 0);
-
- // Bottom left.
- rad = MIN(border_bottom, border_left);
- inner_corner_radius[3] = MAX(corner_radius[3] - rad, 0);
+ inner_corner_radius[0] = MAX(corner_radius[0] - MIN(border_top, border_left), 0); // Top left.
+ inner_corner_radius[1] = MAX(corner_radius[1] - MIN(border_top, border_right), 0); // Top right.
+ inner_corner_radius[2] = MAX(corner_radius[2] - MIN(border_bottom, border_right), 0); // Bottom right.
+ inner_corner_radius[3] = MAX(corner_radius[3] - MIN(border_bottom, border_left), 0); // Bottom left.
}
inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const real_t corner_radius[4],
const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const Vector2 &skew, bool is_filled = false) {
int vert_offset = verts.size();
- if (!vert_offset) {
- vert_offset = 0;
- }
-
- int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail;
+ int adapted_corner_detail = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0) ? corner_detail : 1;
bool draw_border = !is_filled;
@@ -280,30 +263,44 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices,
// If the center is filled, we do not draw the border and directly use the inner ring as reference. Because all calls to this
// method either draw a ring or a filled rounded rectangle, but not both.
- int max_inner_outer = draw_border ? 2 : 1;
-
- for (int corner_index = 0; corner_index < 4; corner_index++) {
+ real_t quarter_arc_rad = Math_PI / 2.0;
+ Point2 style_rect_center = style_rect.get_center();
+
+ int colors_size = colors.size();
+ int verts_size = verts.size();
+ int new_verts_amount = (adapted_corner_detail + 1) * (draw_border ? 8 : 4);
+ colors.resize(colors_size + new_verts_amount);
+ verts.resize(verts_size + new_verts_amount);
+ Color *colors_ptr = colors.ptrw();
+ Vector2 *verts_ptr = verts.ptrw();
+
+ for (int corner_idx = 0; corner_idx < 4; corner_idx++) {
for (int detail = 0; detail <= adapted_corner_detail; detail++) {
- for (int inner_outer = 0; inner_outer < max_inner_outer; inner_outer++) {
- real_t radius;
- Color color;
- Point2 corner_point;
- if (inner_outer == 0) {
- radius = inner_corner_radius[corner_index];
- color = inner_color;
- corner_point = inner_points[corner_index];
- } else {
- radius = ring_corner_radius[corner_index];
- color = outer_color;
- corner_point = outer_points[corner_index];
- }
+ int idx_ofs = (adapted_corner_detail + 1) * corner_idx + detail;
+ if (draw_border) {
+ idx_ofs *= 2;
+ }
- const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x;
- const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y;
- const float x_skew = -skew.x * (y - style_rect.get_center().y);
- const float y_skew = -skew.y * (x - style_rect.get_center().x);
- verts.push_back(Vector2(x + x_skew, y + y_skew));
- colors.push_back(color);
+ const real_t pt_angle = (corner_idx + detail / (double)adapted_corner_detail) * quarter_arc_rad + Math_PI;
+ const real_t angle_cosine = cos(pt_angle);
+ const real_t angle_sine = sin(pt_angle);
+
+ {
+ const real_t x = inner_corner_radius[corner_idx] * angle_cosine + inner_points[corner_idx].x;
+ const real_t y = inner_corner_radius[corner_idx] * angle_sine + inner_points[corner_idx].y;
+ const float x_skew = -skew.x * (y - style_rect_center.y);
+ const float y_skew = -skew.y * (x - style_rect_center.x);
+ verts_ptr[verts_size + idx_ofs] = Vector2(x + x_skew, y + y_skew);
+ colors_ptr[colors_size + idx_ofs] = inner_color;
+ }
+
+ if (draw_border) {
+ const real_t x = ring_corner_radius[corner_idx] * angle_cosine + outer_points[corner_idx].x;
+ const real_t y = ring_corner_radius[corner_idx] * angle_sine + outer_points[corner_idx].y;
+ const float x_skew = -skew.x * (y - style_rect_center.y);
+ const float y_skew = -skew.y * (x - style_rect_center.x);
+ verts_ptr[verts_size + idx_ofs + 1] = Vector2(x + x_skew, y + y_skew);
+ colors_ptr[colors_size + idx_ofs + 1] = outer_color;
}
}
}
@@ -313,10 +310,15 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices,
// Fill the indices and the colors for the border.
if (draw_border) {
+ int indices_size = indices.size();
+ indices.resize(indices_size + ring_vert_count * 3);
+ int *indices_ptr = indices.ptrw();
+
for (int i = 0; i < ring_vert_count; i++) {
- indices.push_back(vert_offset + ((i + 0) % ring_vert_count));
- indices.push_back(vert_offset + ((i + 2) % ring_vert_count));
- indices.push_back(vert_offset + ((i + 1) % ring_vert_count));
+ int idx_ofs = indices_size + i * 3;
+ indices_ptr[idx_ofs] = vert_offset + i % ring_vert_count;
+ indices_ptr[idx_ofs + 1] = vert_offset + (i + 2) % ring_vert_count;
+ indices_ptr[idx_ofs + 2] = vert_offset + (i + 1) % ring_vert_count;
}
}
@@ -327,40 +329,30 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices,
int stripes_count = ring_vert_count / 2 - 1;
int last_vert_id = ring_vert_count - 1;
+ int indices_size = indices.size();
+ indices.resize(indices_size + stripes_count * 6);
+ int *indices_ptr = indices.ptrw();
+
for (int i = 0; i < stripes_count; i++) {
+ int idx_ofs = indices_size + i * 6;
// Polygon 1.
- indices.push_back(vert_offset + i);
- indices.push_back(vert_offset + last_vert_id - i - 1);
- indices.push_back(vert_offset + i + 1);
+ indices_ptr[idx_ofs] = vert_offset + i;
+ indices_ptr[idx_ofs + 1] = vert_offset + last_vert_id - i - 1;
+ indices_ptr[idx_ofs + 2] = vert_offset + i + 1;
// Polygon 2.
- indices.push_back(vert_offset + i);
- indices.push_back(vert_offset + last_vert_id - 0 - i);
- indices.push_back(vert_offset + last_vert_id - 1 - i);
+ indices_ptr[idx_ofs + 3] = vert_offset + i;
+ indices_ptr[idx_ofs + 4] = vert_offset + last_vert_id - i;
+ indices_ptr[idx_ofs + 5] = vert_offset + last_vert_id - i - 1;
}
}
}
inline void adapt_values(int p_index_a, int p_index_b, real_t *adapted_values, const real_t *p_values, const real_t p_width, const real_t p_max_a, const real_t p_max_b) {
- if (p_values[p_index_a] + p_values[p_index_b] > p_width) {
- real_t factor;
- real_t new_value;
-
- factor = (real_t)p_width / (real_t)(p_values[p_index_a] + p_values[p_index_b]);
-
- new_value = (p_values[p_index_a] * factor);
- if (new_value < adapted_values[p_index_a]) {
- adapted_values[p_index_a] = new_value;
- }
- new_value = (p_values[p_index_b] * factor);
- if (new_value < adapted_values[p_index_b]) {
- adapted_values[p_index_b] = new_value;
- }
- } else {
- adapted_values[p_index_a] = MIN(p_values[p_index_a], adapted_values[p_index_a]);
- adapted_values[p_index_b] = MIN(p_values[p_index_b], adapted_values[p_index_b]);
- }
- adapted_values[p_index_a] = MIN(p_max_a, adapted_values[p_index_a]);
- adapted_values[p_index_b] = MIN(p_max_b, adapted_values[p_index_b]);
+ real_t value_a = p_values[p_index_a];
+ real_t value_b = p_values[p_index_b];
+ real_t factor = MIN(1.0, p_width / (value_a + value_b));
+ adapted_values[p_index_a] = MIN(MIN(value_a * factor, p_max_a), adapted_values[p_index_a]);
+ adapted_values[p_index_b] = MIN(MIN(value_b * factor, p_max_b), adapted_values[p_index_b]);
}
Rect2 StyleBoxFlat::get_draw_rect(const Rect2 &p_rect) const {
@@ -388,7 +380,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const {
}
const bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0);
- // Only enable antialiasing if it is actually needed. This improve performances
+ // Only enable antialiasing if it is actually needed. This improves performance
// and maximizes sharpness for non-skewed StyleBoxes with sharp corners.
const bool aa_on = (rounded_corners || !skew.is_zero_approx()) && anti_aliased;
@@ -428,7 +420,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const {
Vector<Color> colors;
Vector<Point2> uvs;
- // Create shadow
+ // Create shadow.
if (draw_shadow) {
Rect2 shadow_inner_rect = style_rect;
shadow_inner_rect.position += shadow_offset;
@@ -538,9 +530,10 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const {
// Compute UV coordinates.
Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0);
uvs.resize(verts.size());
+ Point2 *uvs_ptr = uvs.ptrw();
for (int i = 0; i < verts.size(); i++) {
- uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width;
- uvs.write[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height;
+ uvs_ptr[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width;
+ uvs_ptr[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height;
}
// Draw stylebox.
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index 0c211be9f9..caf44ac392 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -689,6 +689,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("forward_folder", "FileDialog", icons["arrow_right"]);
theme->set_icon("reload", "FileDialog", icons["reload"]);
theme->set_icon("toggle_hidden", "FileDialog", icons["visibility_visible"]);
+ theme->set_icon("toggle_filename_filter", "FileDialog", icons["toggle_filename_filter"]);
theme->set_icon("folder", "FileDialog", icons["folder"]);
theme->set_icon("file", "FileDialog", icons["file"]);
theme->set_icon("create_folder", "FileDialog", icons["folder_create"]);
diff --git a/scene/theme/icons/toggle_filename_filter.svg b/scene/theme/icons/toggle_filename_filter.svg
new file mode 100644
index 0000000000..351cc44ced
--- /dev/null
+++ b/scene/theme/icons/toggle_filename_filter.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#e0e0e0"><path d="m13.297.714h-13.013a.454.454 0 0 0 -.318.779l4.615 4.507v7.086a.45.45 0 0 0 .738.354l3.511-2.812a.454.454 0 0 0 .17-.354v-4.274l4.614-4.506a.454.454 0 0 0 -.317-.779z"/><path d="m11.085832 14.18196c3.399443 1.97457 6.855925-2.441094 4.074102-5.1815164-2.781825-2.7404217-7.2642008.6646174-5.2597994 4.0134654l-1.9001346 1.871854 1.1856973 1.168051zm1.699723-4.4945981c2.236109 0 2.236109 3.3042441 0 3.3042441-2.236108 0-2.236108-3.3042441 0-3.3042441z"/></g></svg> \ No newline at end of file
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 5835ecfed0..70ef88e36d 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -1248,6 +1248,8 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) {
return;
}
+ p_playback->stop();
+
AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback);
if (!playback_node) {
return;
diff --git a/servers/camera/camera_feed.cpp b/servers/camera/camera_feed.cpp
index 8f6a40481d..4021d9564b 100644
--- a/servers/camera/camera_feed.cpp
+++ b/servers/camera/camera_feed.cpp
@@ -33,26 +33,23 @@
#include "servers/rendering_server.h"
void CameraFeed::_bind_methods() {
- // The setters prefixed with _ are only exposed so we can have feeds through GDExtension!
- // They should not be called by the end user.
-
ClassDB::bind_method(D_METHOD("get_id"), &CameraFeed::get_id);
ClassDB::bind_method(D_METHOD("is_active"), &CameraFeed::is_active);
ClassDB::bind_method(D_METHOD("set_active", "active"), &CameraFeed::set_active);
ClassDB::bind_method(D_METHOD("get_name"), &CameraFeed::get_name);
- ClassDB::bind_method(D_METHOD("_set_name", "name"), &CameraFeed::set_name);
+ ClassDB::bind_method(D_METHOD("set_name", "name"), &CameraFeed::set_name);
ClassDB::bind_method(D_METHOD("get_position"), &CameraFeed::get_position);
- ClassDB::bind_method(D_METHOD("_set_position", "position"), &CameraFeed::set_position);
+ ClassDB::bind_method(D_METHOD("set_position", "position"), &CameraFeed::set_position);
// Note, for transform some feeds may override what the user sets (such as ARKit)
ClassDB::bind_method(D_METHOD("get_transform"), &CameraFeed::get_transform);
ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CameraFeed::set_transform);
- ClassDB::bind_method(D_METHOD("_set_RGB_img", "rgb_img"), &CameraFeed::set_RGB_img);
- ClassDB::bind_method(D_METHOD("_set_YCbCr_img", "ycbcr_img"), &CameraFeed::set_YCbCr_img);
+ ClassDB::bind_method(D_METHOD("set_rgb_image", "rgb_image"), &CameraFeed::set_rgb_image);
+ ClassDB::bind_method(D_METHOD("set_ycbcr_image", "ycbcr_image"), &CameraFeed::set_ycbcr_image);
ClassDB::bind_method(D_METHOD("get_datatype"), &CameraFeed::get_datatype);
@@ -175,7 +172,7 @@ CameraFeed::~CameraFeed() {
RenderingServer::get_singleton()->free(texture[CameraServer::FEED_CBCR_IMAGE]);
}
-void CameraFeed::set_RGB_img(const Ref<Image> &p_rgb_img) {
+void CameraFeed::set_rgb_image(const Ref<Image> &p_rgb_img) {
ERR_FAIL_COND(p_rgb_img.is_null());
if (active) {
int new_width = p_rgb_img->get_width();
@@ -198,7 +195,7 @@ void CameraFeed::set_RGB_img(const Ref<Image> &p_rgb_img) {
}
}
-void CameraFeed::set_YCbCr_img(const Ref<Image> &p_ycbcr_img) {
+void CameraFeed::set_ycbcr_image(const Ref<Image> &p_ycbcr_img) {
ERR_FAIL_COND(p_ycbcr_img.is_null());
if (active) {
int new_width = p_ycbcr_img->get_width();
@@ -221,7 +218,7 @@ void CameraFeed::set_YCbCr_img(const Ref<Image> &p_ycbcr_img) {
}
}
-void CameraFeed::set_YCbCr_imgs(const Ref<Image> &p_y_img, const Ref<Image> &p_cbcr_img) {
+void CameraFeed::set_ycbcr_images(const Ref<Image> &p_y_img, const Ref<Image> &p_cbcr_img) {
ERR_FAIL_COND(p_y_img.is_null());
ERR_FAIL_COND(p_cbcr_img.is_null());
if (active) {
diff --git a/servers/camera/camera_feed.h b/servers/camera/camera_feed.h
index 5d1f54be07..492a909239 100644
--- a/servers/camera/camera_feed.h
+++ b/servers/camera/camera_feed.h
@@ -110,9 +110,9 @@ public:
virtual ~CameraFeed();
FeedDataType get_datatype() const;
- void set_RGB_img(const Ref<Image> &p_rgb_img);
- void set_YCbCr_img(const Ref<Image> &p_ycbcr_img);
- void set_YCbCr_imgs(const Ref<Image> &p_y_img, const Ref<Image> &p_cbcr_img);
+ void set_rgb_image(const Ref<Image> &p_rgb_img);
+ void set_ycbcr_image(const Ref<Image> &p_ycbcr_img);
+ void set_ycbcr_images(const Ref<Image> &p_y_img, const Ref<Image> &p_cbcr_img);
virtual bool set_format(int p_index, const Dictionary &p_parameters);
virtual Array get_formats() const;
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 86b4016da8..ce0d6cb996 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -634,6 +634,10 @@ int DisplayServer::virtual_keyboard_get_height() const {
ERR_FAIL_V_MSG(0, "Virtual keyboard not supported by this display server.");
}
+bool DisplayServer::has_hardware_keyboard() const {
+ return true;
+}
+
void DisplayServer::cursor_set_shape(CursorShape p_shape) {
WARN_PRINT("Cursor shape not supported by this display server.");
}
@@ -976,6 +980,8 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("virtual_keyboard_get_height"), &DisplayServer::virtual_keyboard_get_height);
+ ClassDB::bind_method(D_METHOD("has_hardware_keyboard"), &DisplayServer::has_hardware_keyboard);
+
ClassDB::bind_method(D_METHOD("cursor_set_shape", "shape"), &DisplayServer::cursor_set_shape);
ClassDB::bind_method(D_METHOD("cursor_get_shape"), &DisplayServer::cursor_get_shape);
ClassDB::bind_method(D_METHOD("cursor_set_custom_image", "cursor", "shape", "hotspot"), &DisplayServer::cursor_set_custom_image, DEFVAL(CURSOR_ARROW), DEFVAL(Vector2()));
@@ -1229,6 +1235,12 @@ bool DisplayServer::can_create_rendering_device() {
return true;
}
+ if (created_rendering_device == RenderingDeviceCreationStatus::SUCCESS) {
+ return true;
+ } else if (created_rendering_device == RenderingDeviceCreationStatus::FAILURE) {
+ return false;
+ }
+
Error err;
RenderingContextDriver *rcd = nullptr;
@@ -1258,7 +1270,14 @@ bool DisplayServer::can_create_rendering_device() {
memdelete(rd);
rd = nullptr;
if (err == OK) {
+ // Creating a RenderingDevice is quite slow.
+ // Cache the result for future usage, so that it's much faster on subsequent calls.
+ created_rendering_device = RenderingDeviceCreationStatus::SUCCESS;
+ memdelete(rcd);
+ rcd = nullptr;
return true;
+ } else {
+ created_rendering_device = RenderingDeviceCreationStatus::FAILURE;
}
}
diff --git a/servers/display_server.h b/servers/display_server.h
index 04f4b0c03d..36798bd011 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -507,6 +507,8 @@ public:
// returns height of the currently shown virtual keyboard (0 if keyboard is hidden)
virtual int virtual_keyboard_get_height() const;
+ virtual bool has_hardware_keyboard() const;
+
enum CursorShape {
CURSOR_ARROW,
CURSOR_IBEAM,
@@ -594,6 +596,16 @@ public:
static Vector<String> get_create_function_rendering_drivers(int p_index);
static DisplayServer *create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error);
+ enum RenderingDeviceCreationStatus {
+ UNKNOWN,
+ SUCCESS,
+ FAILURE,
+ };
+
+ // Used to cache the result of `can_create_rendering_device()` when RenderingDevice isn't currently being used.
+ // This is done as creating a RenderingDevice is quite slow.
+ static inline RenderingDeviceCreationStatus created_rendering_device = RenderingDeviceCreationStatus::UNKNOWN;
+
static bool can_create_rendering_device();
DisplayServer();
diff --git a/servers/rendering/dummy/rasterizer_canvas_dummy.h b/servers/rendering/dummy/rasterizer_canvas_dummy.h
index a450b2a21d..d61ee1bdb6 100644
--- a/servers/rendering/dummy/rasterizer_canvas_dummy.h
+++ b/servers/rendering/dummy/rasterizer_canvas_dummy.h
@@ -56,6 +56,7 @@ public:
void update() override {}
virtual void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) override {}
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override { return 0; }
RasterizerCanvasDummy() {}
~RasterizerCanvasDummy() {}
diff --git a/servers/rendering/dummy/rasterizer_dummy.h b/servers/rendering/dummy/rasterizer_dummy.h
index c3f5f3102d..7640afc711 100644
--- a/servers/rendering/dummy/rasterizer_dummy.h
+++ b/servers/rendering/dummy/rasterizer_dummy.h
@@ -110,6 +110,7 @@ public:
uint64_t get_frame_number() const override { return frame; }
double get_frame_delta_time() const override { return delta; }
double get_total_time() const override { return time; }
+ bool can_create_resources_async() const override { return false; }
RasterizerDummy() {}
~RasterizerDummy() {}
diff --git a/servers/rendering/dummy/rasterizer_scene_dummy.h b/servers/rendering/dummy/rasterizer_scene_dummy.h
index a699a58b1f..f129c86746 100644
--- a/servers/rendering/dummy/rasterizer_scene_dummy.h
+++ b/servers/rendering/dummy/rasterizer_scene_dummy.h
@@ -94,6 +94,11 @@ public:
uint32_t geometry_instance_get_pair_mask() override { return 0; }
+ /* PIPELINES */
+
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) override {}
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override { return 0; }
+
/* SDFGI UPDATE */
void sdfgi_update(const Ref<RenderSceneBuffers> &p_render_buffers, RID p_environment, const Vector3 &p_world_position) override {}
diff --git a/servers/rendering/dummy/storage/texture_storage.h b/servers/rendering/dummy/storage/texture_storage.h
index 6da7446e69..6735f6bcda 100644
--- a/servers/rendering/dummy/storage/texture_storage.h
+++ b/servers/rendering/dummy/storage/texture_storage.h
@@ -51,8 +51,6 @@ public:
TextureStorage();
~TextureStorage();
- virtual bool can_create_resources_async() const override { return false; }
-
/* Canvas Texture API */
virtual RID canvas_texture_allocate() override { return RID(); };
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index 0ec161d8cf..701b4da8f8 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -51,7 +51,7 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
for (int i = 0; i < p_child_item_count; i++) {
- _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, p_canvas_cull_mask, Point2(), 1, nullptr);
+ _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, false, p_canvas_cull_mask, Point2(), 1, nullptr);
}
RendererCanvasRender::Item *list = nullptr;
@@ -79,45 +79,71 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
}
}
-void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, const Transform2D &p_transform, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
+void RendererCanvasCull::_collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) {
int child_item_count = p_canvas_item->child_items.size();
RendererCanvasCull::Item **child_items = p_canvas_item->child_items.ptrw();
for (int i = 0; i < child_item_count; i++) {
- int abs_z = 0;
if (child_items[i]->visible) {
- if (r_items) {
- r_items[r_index] = child_items[i];
- child_items[i]->ysort_xform = p_transform;
- child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.columns[2]);
- child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
- child_items[i]->ysort_modulate = p_modulate;
- child_items[i]->ysort_index = r_index;
- child_items[i]->ysort_parent_abs_z_index = p_z;
-
- if (!child_items[i]->repeat_source) {
- child_items[i]->repeat_size = p_canvas_item->repeat_size;
- child_items[i]->repeat_times = p_canvas_item->repeat_times;
- child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item;
- }
+ // To y-sort according to the item's final position, physics interpolation
+ // and transform snapping need to be applied before y-sorting.
+ Transform2D child_xform;
+ if (!_interpolation_data.interpolation_enabled || !child_items[i]->interpolated) {
+ child_xform = child_items[i]->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(child_items[i]->xform_prev, child_items[i]->xform_curr, child_xform, f);
+ }
- // Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
- if (child_items[i]->z_relative) {
- abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
- } else {
- abs_z = child_items[i]->z_index;
- }
+ if (snapping_2d_transforms_to_pixel) {
+ child_xform.columns[2] = (child_xform.columns[2] + Point2(0.5, 0.5)).floor();
+ }
+
+ r_items[r_index] = child_items[i];
+ child_items[i]->ysort_xform = p_canvas_item->ysort_xform * child_xform;
+ child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr;
+ child_items[i]->ysort_modulate = p_modulate;
+ child_items[i]->ysort_index = r_index;
+ child_items[i]->ysort_parent_abs_z_index = p_z;
+
+ if (!child_items[i]->repeat_source) {
+ child_items[i]->repeat_size = p_canvas_item->repeat_size;
+ child_items[i]->repeat_times = p_canvas_item->repeat_times;
+ child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item;
+ }
+
+ // Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
+ int abs_z = 0;
+ if (child_items[i]->z_relative) {
+ abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX);
+ } else {
+ abs_z = child_items[i]->z_index;
}
r_index++;
if (child_items[i]->sort_y) {
- _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform_curr, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
+ _collect_ysort_children(child_items[i], child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z);
}
}
}
}
-void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner, RID_Owner<RendererCanvasCull::Item, true> &canvas_item_owner) {
+int RendererCanvasCull::_count_ysort_children(RendererCanvasCull::Item *p_canvas_item) {
+ int ysort_children_count = 0;
+ int child_item_count = p_canvas_item->child_items.size();
+ RendererCanvasCull::Item *const *child_items = p_canvas_item->child_items.ptr();
+ for (int i = 0; i < child_item_count; i++) {
+ if (child_items[i]->visible) {
+ ysort_children_count++;
+ if (child_items[i]->sort_y) {
+ ysort_children_count += _count_ysort_children(child_items[i]);
+ }
+ }
+ }
+ return ysort_children_count;
+}
+
+void RendererCanvasCull::_mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner) {
do {
ysort_owner->ysort_children_count = -1;
ysort_owner = canvas_item_owner.owns(ysort_owner->parent) ? canvas_item_owner.get_or_null(ysort_owner->parent) : nullptr;
@@ -236,7 +262,7 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
}
}
-void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) {
+void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) {
Item *ci = p_canvas_item;
if (!ci->visible) {
@@ -260,15 +286,29 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
}
+ Transform2D self_xform;
Transform2D final_xform;
- if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
- final_xform = ci->xform_curr;
+ if (p_is_already_y_sorted) {
+ // Y-sorted item's final transform is calculated before y-sorting,
+ // and is passed as `p_parent_xform` afterwards. No need to recalculate.
+ final_xform = p_parent_xform;
} else {
- real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
- TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f);
- }
+ if (!_interpolation_data.interpolation_enabled || !ci->interpolated) {
+ self_xform = ci->xform_curr;
+ } else {
+ real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+ TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, self_xform, f);
+ }
- Transform2D parent_xform = p_parent_xform;
+ Transform2D parent_xform = p_parent_xform;
+
+ if (snapping_2d_transforms_to_pixel) {
+ self_xform.columns[2] = (self_xform.columns[2] + Point2(0.5, 0.5)).floor();
+ parent_xform.columns[2] = (parent_xform.columns[2] + Point2(0.5, 0.5)).floor();
+ }
+
+ final_xform = parent_xform * self_xform;
+ }
Point2 repeat_size = p_repeat_size;
int repeat_times = p_repeat_times;
@@ -284,13 +324,6 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
ci->repeat_source_item = repeat_source_item;
}
- if (snapping_2d_transforms_to_pixel) {
- final_xform.columns[2] = (final_xform.columns[2] + Point2(0.5, 0.5)).floor();
- parent_xform.columns[2] = (parent_xform.columns[2] + Point2(0.5, 0.5)).floor();
- }
-
- final_xform = parent_xform * final_xform;
-
Rect2 global_rect = final_xform.xform(rect);
if (repeat_source_item && (repeat_size.x || repeat_size.y)) {
// Top-left repeated rect.
@@ -355,29 +388,27 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
if (ci->sort_y) {
- if (p_allow_y_sort) {
+ if (!p_is_already_y_sorted) {
if (ci->ysort_children_count == -1) {
- ci->ysort_children_count = 0;
- _collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), nullptr, ci->ysort_children_count, p_z);
+ ci->ysort_children_count = _count_ysort_children(ci);
}
child_item_count = ci->ysort_children_count + 1;
child_items = (Item **)alloca(child_item_count * sizeof(Item *));
- ci->ysort_xform = ci->xform_curr.affine_inverse();
- ci->ysort_pos = Vector2();
+ ci->ysort_xform = Transform2D();
ci->ysort_modulate = Color(1, 1, 1, 1);
ci->ysort_index = 0;
ci->ysort_parent_abs_z_index = parent_z;
child_items[0] = ci;
int i = 1;
- _collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);
+ _collect_ysort_children(ci, p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z);
- SortArray<Item *, ItemPtrSort> sorter;
+ SortArray<Item *, ItemYSort> sorter;
sorter.sort(child_items, child_item_count);
for (i = 0; i < child_item_count; i++) {
- _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item);
+ _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, true, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -401,14 +432,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
}
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
}
}
}
@@ -509,7 +540,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
item_owner->child_items.erase(canvas_item);
if (item_owner->sort_y) {
- _mark_ysort_dirty(item_owner, canvas_item_owner);
+ _mark_ysort_dirty(item_owner);
}
}
@@ -529,7 +560,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) {
item_owner->children_order_dirty = true;
if (item_owner->sort_y) {
- _mark_ysort_dirty(item_owner, canvas_item_owner);
+ _mark_ysort_dirty(item_owner);
}
} else {
@@ -546,7 +577,7 @@ void RendererCanvasCull::canvas_item_set_visible(RID p_item, bool p_visible) {
canvas_item->visible = p_visible;
- _mark_ysort_dirty(canvas_item, canvas_item_owner);
+ _mark_ysort_dirty(canvas_item);
}
void RendererCanvasCull::canvas_item_set_light_mask(RID p_item, int p_mask) {
@@ -1742,7 +1773,7 @@ void RendererCanvasCull::canvas_item_set_sort_children_by_y(RID p_item, bool p_e
canvas_item->sort_y = p_enable;
- _mark_ysort_dirty(canvas_item, canvas_item_owner);
+ _mark_ysort_dirty(canvas_item);
}
void RendererCanvasCull::canvas_item_set_z_index(RID p_item, int p_z) {
@@ -2423,7 +2454,7 @@ bool RendererCanvasCull::free(RID p_rid) {
item_owner->child_items.erase(canvas_item);
if (item_owner->sort_y) {
- _mark_ysort_dirty(item_owner, canvas_item_owner);
+ _mark_ysort_dirty(item_owner);
}
}
}
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index 91c03054f7..9a088d94ed 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -50,8 +50,7 @@ public:
bool children_order_dirty;
int ysort_children_count;
Color ysort_modulate;
- Transform2D ysort_xform;
- Vector2 ysort_pos;
+ Transform2D ysort_xform; // Relative to y-sorted subtree's root item (identity for such root). Its `origin.y` is used for sorting.
int ysort_index;
int ysort_parent_abs_z_index; // Absolute Z index of parent. Only populated and used when y-sorting.
uint32_t visibility_layer = 0xffffffff;
@@ -84,7 +83,6 @@ public:
index = 0;
ysort_children_count = -1;
ysort_xform = Transform2D();
- ysort_pos = Vector2();
ysort_index = 0;
ysort_parent_abs_z_index = 0;
}
@@ -96,13 +94,15 @@ public:
}
};
- struct ItemPtrSort {
+ struct ItemYSort {
_FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const {
- if (Math::is_equal_approx(p_left->ysort_pos.y, p_right->ysort_pos.y)) {
+ const real_t left_y = p_left->ysort_xform.columns[2].y;
+ const real_t right_y = p_right->ysort_xform.columns[2].y;
+ if (Math::is_equal_approx(left_y, right_y)) {
return p_left->ysort_index < p_right->ysort_index;
}
- return p_left->ysort_pos.y < p_right->ysort_pos.y;
+ return left_y < right_y;
}
};
@@ -187,7 +187,11 @@ public:
private:
void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item);
+ void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item);
+
+ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z);
+ int _count_ysort_children(RendererCanvasCull::Item *p_canvas_item);
+ void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner);
static constexpr int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1;
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index c57abee165..328fe32ea6 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -545,6 +545,7 @@ public:
virtual void update() = 0;
virtual void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) = 0;
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) = 0;
RendererCanvasRender() {
ERR_FAIL_COND_MSG(singleton != nullptr, "A RendererCanvasRender singleton already exists.");
diff --git a/servers/rendering/renderer_compositor.h b/servers/rendering/renderer_compositor.h
index 933b9e457f..a585c9430b 100644
--- a/servers/rendering/renderer_compositor.h
+++ b/servers/rendering/renderer_compositor.h
@@ -104,6 +104,7 @@ public:
virtual uint64_t get_frame_number() const = 0;
virtual double get_frame_delta_time() const = 0;
virtual double get_total_time() const = 0;
+ virtual bool can_create_resources_async() const = 0;
static bool is_low_end() { return low_end; };
virtual bool is_xr_enabled() const;
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index aca85ce497..76d8972ad9 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -31,6 +31,7 @@
#include "render_forward_clustered.h"
#include "core/config/project_settings.h"
#include "core/object/worker_thread_pool.h"
+#include "scene/resources/material.h"
#include "servers/rendering/renderer_rd/framebuffer_cache_rd.h"
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/storage_rd/light_storage.h"
@@ -43,23 +44,18 @@
using namespace RendererSceneRenderImplementation;
+#define PRELOAD_PIPELINES_ON_SURFACE_CACHE_CONSTRUCTION 1
+
+#define FADE_ALPHA_PASS_THRESHOLD 0.999
+
void RenderForwardClustered::RenderBufferDataForwardClustered::ensure_specular() {
ERR_FAIL_NULL(render_buffers);
if (!render_buffers->has_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_SPECULAR)) {
- RD::DataFormat format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
- uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
- if (render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
- } else {
- usage_bits |= RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
- }
-
- render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_SPECULAR, format, usage_bits);
-
- if (render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT;
- render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_SPECULAR_MSAA, format, usage_bits, render_buffers->get_texture_samples());
+ bool msaa = render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED;
+ render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_SPECULAR, get_specular_format(), get_specular_usage_bits(msaa, false, render_buffers->get_can_be_storage()));
+ if (msaa) {
+ render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_SPECULAR_MSAA, get_specular_format(), get_specular_usage_bits(false, msaa, render_buffers->get_can_be_storage()), render_buffers->get_texture_samples());
}
}
}
@@ -68,20 +64,10 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::ensure_normal_rou
ERR_FAIL_NULL(render_buffers);
if (!render_buffers->has_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_NORMAL_ROUGHNESS)) {
- RD::DataFormat format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
- uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
-
- if (render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
- } else {
- usage_bits |= RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
- }
-
- render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_NORMAL_ROUGHNESS, format, usage_bits);
-
- if (render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
- render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_NORMAL_ROUGHNESS_MSAA, format, usage_bits, render_buffers->get_texture_samples());
+ bool msaa = render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED;
+ render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_NORMAL_ROUGHNESS, get_normal_roughness_format(), get_normal_roughness_usage_bits(msaa, false, render_buffers->get_can_be_storage()));
+ if (msaa) {
+ render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_NORMAL_ROUGHNESS_MSAA, get_normal_roughness_format(), get_normal_roughness_usage_bits(false, msaa, render_buffers->get_can_be_storage()), render_buffers->get_texture_samples());
}
}
}
@@ -90,17 +76,10 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::ensure_voxelgi()
ERR_FAIL_NULL(render_buffers);
if (!render_buffers->has_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_VOXEL_GI)) {
- RD::DataFormat format = RD::DATA_FORMAT_R8G8_UINT;
- uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
- if (render_buffers->get_msaa_3d() == RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits |= RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
- }
-
- render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_VOXEL_GI, format, usage_bits);
-
- if (render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
- render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_VOXEL_GI_MSAA, format, usage_bits, render_buffers->get_texture_samples());
+ bool msaa = render_buffers->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED;
+ render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_VOXEL_GI, get_voxelgi_format(), get_voxelgi_usage_bits(msaa, false, render_buffers->get_can_be_storage()));
+ if (msaa) {
+ render_buffers->create_texture(RB_SCOPE_FORWARD_CLUSTERED, RB_TEX_VOXEL_GI_MSAA, get_voxelgi_format(), get_voxelgi_usage_bits(false, msaa, render_buffers->get_can_be_storage()), render_buffers->get_texture_samples());
}
}
}
@@ -249,6 +228,30 @@ RID RenderForwardClustered::RenderBufferDataForwardClustered::get_velocity_only_
return FramebufferCacheRD::get_singleton()->get_cache_multiview(render_buffers->get_view_count(), velocity);
}
+RD::DataFormat RenderForwardClustered::RenderBufferDataForwardClustered::get_specular_format() {
+ return RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+}
+
+uint32_t RenderForwardClustered::RenderBufferDataForwardClustered::get_specular_usage_bits(bool p_resolve, bool p_msaa, bool p_storage) {
+ return RenderSceneBuffersRD::get_color_usage_bits(p_resolve, p_msaa, p_storage);
+}
+
+RD::DataFormat RenderForwardClustered::RenderBufferDataForwardClustered::get_normal_roughness_format() {
+ return RD::DATA_FORMAT_R8G8B8A8_UNORM;
+}
+
+uint32_t RenderForwardClustered::RenderBufferDataForwardClustered::get_normal_roughness_usage_bits(bool p_resolve, bool p_msaa, bool p_storage) {
+ return RenderSceneBuffersRD::get_color_usage_bits(p_resolve, p_msaa, p_storage);
+}
+
+RD::DataFormat RenderForwardClustered::RenderBufferDataForwardClustered::get_voxelgi_format() {
+ return RD::DATA_FORMAT_R8G8_UINT;
+}
+
+uint32_t RenderForwardClustered::RenderBufferDataForwardClustered::get_voxelgi_usage_bits(bool p_resolve, bool p_msaa, bool p_storage) {
+ return RenderSceneBuffersRD::get_color_usage_bits(p_resolve, p_msaa, p_storage);
+}
+
void RenderForwardClustered::setup_render_buffer_data(Ref<RenderSceneBuffersRD> p_render_buffers) {
Ref<RenderBufferDataForwardClustered> data;
data.instantiate();
@@ -266,6 +269,12 @@ bool RenderForwardClustered::free(RID p_rid) {
return false;
}
+void RenderForwardClustered::update() {
+ RendererSceneRenderRD::update();
+ _update_global_pipeline_data_requirements_from_project();
+ _update_global_pipeline_data_requirements_from_light_storage();
+}
+
/// RENDERING ///
template <RenderForwardClustered::PassMode p_pass_mode, uint32_t p_color_pass_flags>
@@ -284,9 +293,14 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
RID prev_vertex_array_rd;
RID prev_index_array_rd;
- RID prev_pipeline_rd;
RID prev_xforms_uniform_set;
+ SceneShaderForwardClustered::ShaderData *shader = nullptr;
+ SceneShaderForwardClustered::ShaderData *prev_shader = nullptr;
+ SceneShaderForwardClustered::ShaderData::PipelineKey pipeline_key;
+ uint32_t pipeline_hash = 0;
+ uint32_t prev_pipeline_hash = 0;
+
bool shadow_pass = (p_pass_mode == PASS_MODE_SHADOW) || (p_pass_mode == PASS_MODE_SHADOW_DP);
SceneState::PushConstant push_constant;
@@ -317,7 +331,6 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
push_constant.base_index = i + p_params->element_offset;
RID material_uniform_set;
- SceneShaderForwardClustered::ShaderData *shader;
void *mesh_surface;
if (shadow_pass || p_pass_mode == PASS_MODE_DEPTH) { //regular depth pass can use these too
@@ -356,166 +369,211 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p
should_request_redraw = true;
}
- //find cull variant
- SceneShaderForwardClustered::ShaderData::CullVariant cull_variant;
-
- if (p_pass_mode == PASS_MODE_DEPTH_MATERIAL || p_pass_mode == PASS_MODE_SDF || ((p_pass_mode == PASS_MODE_SHADOW || p_pass_mode == PASS_MODE_SHADOW_DP) && surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS)) {
+ // Determine the cull variant.
+ SceneShaderForwardClustered::ShaderData::CullVariant cull_variant = SceneShaderForwardClustered::ShaderData::CULL_VARIANT_MAX;
+ if constexpr (p_pass_mode == PASS_MODE_DEPTH_MATERIAL || p_pass_mode == PASS_MODE_SDF) {
cull_variant = SceneShaderForwardClustered::ShaderData::CULL_VARIANT_DOUBLE_SIDED;
} else {
- bool mirror = surf->owner->mirror;
- if (p_params->reverse_cull) {
- mirror = !mirror;
+ if constexpr (p_pass_mode == PASS_MODE_SHADOW || p_pass_mode == PASS_MODE_SHADOW_DP) {
+ if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS) {
+ cull_variant = SceneShaderForwardClustered::ShaderData::CULL_VARIANT_DOUBLE_SIDED;
+ }
+ }
+
+ if (cull_variant == SceneShaderForwardClustered::ShaderData::CULL_VARIANT_MAX) {
+ bool mirror = surf->owner->mirror;
+ if (p_params->reverse_cull) {
+ mirror = !mirror;
+ }
+
+ cull_variant = mirror ? SceneShaderForwardClustered::ShaderData::CULL_VARIANT_REVERSED : SceneShaderForwardClustered::ShaderData::CULL_VARIANT_NORMAL;
}
- cull_variant = mirror ? SceneShaderForwardClustered::ShaderData::CULL_VARIANT_REVERSED : SceneShaderForwardClustered::ShaderData::CULL_VARIANT_NORMAL;
}
- RS::PrimitiveType primitive = surf->primitive;
+ pipeline_key.primitive_type = surf->primitive;
+
RID xforms_uniform_set = surf->owner->transforms_uniform_set;
- SceneShaderForwardClustered::PipelineVersion pipeline_version = SceneShaderForwardClustered::PIPELINE_VERSION_MAX; // Assigned to silence wrong -Wmaybe-initialized.
- uint32_t pipeline_color_pass_flags = 0;
- uint32_t pipeline_specialization = p_params->spec_constant_base_flags;
+ SceneShaderForwardClustered::ShaderSpecialization pipeline_specialization = p_params->base_specialization;
if constexpr (p_pass_mode == PASS_MODE_COLOR) {
- if (element_info.uses_softshadow) {
- pipeline_specialization |= SceneShaderForwardClustered::SHADER_SPECIALIZATION_SOFT_SHADOWS;
- }
- if (element_info.uses_projector) {
- pipeline_specialization |= SceneShaderForwardClustered::SHADER_SPECIALIZATION_PROJECTOR;
- }
-
- if (p_params->use_directional_soft_shadow) {
- pipeline_specialization |= SceneShaderForwardClustered::SHADER_SPECIALIZATION_DIRECTIONAL_SOFT_SHADOWS;
- }
+ pipeline_specialization.use_light_soft_shadows = element_info.uses_softshadow;
+ pipeline_specialization.use_light_projector = element_info.uses_projector;
+ pipeline_specialization.use_directional_soft_shadows = p_params->use_directional_soft_shadow;
}
+ pipeline_key.color_pass_flags = 0;
+
switch (p_pass_mode) {
case PASS_MODE_COLOR: {
if (element_info.uses_lightmap) {
- pipeline_color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_LIGHTMAP;
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_LIGHTMAP;
} else {
- if (element_info.uses_forward_gi) {
- pipeline_specialization |= SceneShaderForwardClustered::SHADER_SPECIALIZATION_FORWARD_GI;
- }
+ pipeline_specialization.use_forward_gi = element_info.uses_forward_gi;
}
if constexpr ((p_color_pass_flags & COLOR_PASS_FLAG_SEPARATE_SPECULAR) != 0) {
- pipeline_color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_SEPARATE_SPECULAR;
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_SEPARATE_SPECULAR;
}
if constexpr ((p_color_pass_flags & COLOR_PASS_FLAG_MOTION_VECTORS) != 0) {
- pipeline_color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
}
if constexpr ((p_color_pass_flags & COLOR_PASS_FLAG_TRANSPARENT) != 0) {
- pipeline_color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_TRANSPARENT;
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_TRANSPARENT;
}
if constexpr ((p_color_pass_flags & COLOR_PASS_FLAG_MULTIVIEW) != 0) {
- pipeline_color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MULTIVIEW;
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MULTIVIEW;
}
- pipeline_version = SceneShaderForwardClustered::PIPELINE_VERSION_COLOR_PASS;
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_COLOR_PASS;
} break;
case PASS_MODE_SHADOW:
case PASS_MODE_DEPTH: {
- pipeline_version = p_params->view_count > 1 ? SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_MULTIVIEW : SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS;
+ pipeline_key.version = p_params->view_count > 1 ? SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_MULTIVIEW : SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS;
} break;
case PASS_MODE_SHADOW_DP: {
ERR_FAIL_COND_MSG(p_params->view_count > 1, "Multiview not supported for shadow DP pass");
- pipeline_version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_DP;
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_DP;
} break;
case PASS_MODE_DEPTH_NORMAL_ROUGHNESS: {
- pipeline_version = p_params->view_count > 1 ? SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW : SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS;
+ pipeline_key.version = p_params->view_count > 1 ? SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW : SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS;
} break;
case PASS_MODE_DEPTH_NORMAL_ROUGHNESS_VOXEL_GI: {
- pipeline_version = p_params->view_count > 1 ? SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW : SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI;
+ pipeline_key.version = p_params->view_count > 1 ? SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW : SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI;
} break;
case PASS_MODE_DEPTH_MATERIAL: {
ERR_FAIL_COND_MSG(p_params->view_count > 1, "Multiview not supported for material pass");
- pipeline_version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_MATERIAL;
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_MATERIAL;
} break;
case PASS_MODE_SDF: {
// Note, SDF is prepared in world space, this shouldn't be a multiview buffer even when stereoscopic rendering is used.
ERR_FAIL_COND_MSG(p_params->view_count > 1, "Multiview not supported for SDF pass");
- pipeline_version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_SDF;
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_SDF;
} break;
}
- PipelineCacheRD *pipeline = nullptr;
+ pipeline_key.framebuffer_format_id = framebuffer_format;
+ pipeline_key.wireframe = p_params->force_wireframe;
+ pipeline_key.ubershader = 0;
- if constexpr (p_pass_mode == PASS_MODE_COLOR) {
- pipeline = &shader->color_pipelines[cull_variant][primitive][pipeline_color_pass_flags];
- } else {
- pipeline = &shader->pipelines[cull_variant][primitive][pipeline_version];
- }
-
- RD::VertexFormatID vertex_format = -1;
+ const RD::PolygonCullMode cull_mode = shader->get_cull_mode_from_cull_variant(cull_variant);
RID vertex_array_rd;
RID index_array_rd;
+ RID pipeline_rd;
+ uint32_t ubershader_iterations = 2;
+ if constexpr (p_pass_mode == PASS_MODE_DEPTH_MATERIAL || p_pass_mode == PASS_MODE_SDF) {
+ ubershader_iterations = 1;
+ }
+
+ bool pipeline_valid = false;
+ while (pipeline_key.ubershader < ubershader_iterations) {
+ // Skeleton and blend shape.
+ RD::VertexFormatID vertex_format = -1;
+ bool pipeline_motion_vectors = pipeline_key.color_pass_flags & SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
+ uint64_t input_mask = shader->get_vertex_input_mask(pipeline_key.version, pipeline_key.color_pass_flags, pipeline_key.ubershader);
+ if (surf->owner->mesh_instance.is_valid()) {
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, input_mask, pipeline_motion_vectors, vertex_array_rd, vertex_format);
+ } else {
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, input_mask, pipeline_motion_vectors, vertex_array_rd, vertex_format);
+ }
- //skeleton and blend shape
- bool pipeline_motion_vectors = pipeline_color_pass_flags & SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
- if (surf->owner->mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), pipeline_motion_vectors, vertex_array_rd, vertex_format);
- } else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), pipeline_motion_vectors, vertex_array_rd, vertex_format);
- }
+ pipeline_key.vertex_format_id = vertex_format;
- index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
+ if (pipeline_key.ubershader) {
+ pipeline_key.shader_specialization = {};
+ pipeline_key.cull_mode = RD::POLYGON_CULL_DISABLED;
+ } else {
+ pipeline_key.shader_specialization = pipeline_specialization;
+ pipeline_key.cull_mode = cull_mode;
+ }
- if (prev_vertex_array_rd != vertex_array_rd) {
- RD::get_singleton()->draw_list_bind_vertex_array(draw_list, vertex_array_rd);
- prev_vertex_array_rd = vertex_array_rd;
- }
+ pipeline_hash = pipeline_key.hash();
+
+ if (shader != prev_shader || pipeline_hash != prev_pipeline_hash) {
+ bool wait_for_compilation = (ubershader_iterations == 1) || pipeline_key.ubershader;
+ RS::PipelineSource pipeline_source = wait_for_compilation ? RS::PIPELINE_SOURCE_DRAW : RS::PIPELINE_SOURCE_SPECIALIZATION;
+ pipeline_rd = shader->pipeline_hash_map.get_pipeline(pipeline_key, pipeline_hash, wait_for_compilation, pipeline_source);
- if (prev_index_array_rd != index_array_rd) {
- if (index_array_rd.is_valid()) {
- RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array_rd);
+ if (pipeline_rd.is_valid()) {
+ pipeline_valid = true;
+ prev_shader = shader;
+ prev_pipeline_hash = pipeline_hash;
+ break;
+ } else {
+ pipeline_key.ubershader++;
+ }
+ } else {
+ // The same pipeline is bound already.
+ pipeline_valid = true;
+ break;
}
- prev_index_array_rd = index_array_rd;
}
- RID pipeline_rd = pipeline->get_render_pipeline(vertex_format, framebuffer_format, p_params->force_wireframe, 0, pipeline_specialization);
+ if (pipeline_valid) {
+ index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
- if (pipeline_rd != prev_pipeline_rd) {
- // checking with prev shader does not make so much sense, as
- // the pipeline may still be different.
- RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline_rd);
- prev_pipeline_rd = pipeline_rd;
- }
+ if (prev_vertex_array_rd != vertex_array_rd) {
+ RD::get_singleton()->draw_list_bind_vertex_array(draw_list, vertex_array_rd);
+ prev_vertex_array_rd = vertex_array_rd;
+ }
- if (xforms_uniform_set.is_valid() && prev_xforms_uniform_set != xforms_uniform_set) {
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, xforms_uniform_set, TRANSFORMS_UNIFORM_SET);
- prev_xforms_uniform_set = xforms_uniform_set;
- }
+ if (prev_index_array_rd != index_array_rd) {
+ if (index_array_rd.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array_rd);
+ }
+ prev_index_array_rd = index_array_rd;
+ }
- if (material_uniform_set != prev_material_uniform_set) {
- // Update uniform set.
- if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
+ if (!pipeline_rd.is_null()) {
+ RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline_rd);
}
- prev_material_uniform_set = material_uniform_set;
- }
+ if (xforms_uniform_set.is_valid() && prev_xforms_uniform_set != xforms_uniform_set) {
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, xforms_uniform_set, TRANSFORMS_UNIFORM_SET);
+ prev_xforms_uniform_set = xforms_uniform_set;
+ }
- if (surf->owner->base_flags & INSTANCE_DATA_FLAG_PARTICLES) {
- particles_storage->particles_get_instance_buffer_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset);
- } else if (surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH) {
- mesh_storage->_multimesh_get_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset);
- } else {
- push_constant.multimesh_motion_vectors_current_offset = 0;
- push_constant.multimesh_motion_vectors_previous_offset = 0;
- }
+ if (material_uniform_set != prev_material_uniform_set) {
+ // Update uniform set.
+ if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
+ }
- RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(SceneState::PushConstant));
+ prev_material_uniform_set = material_uniform_set;
+ }
+
+ if (surf->owner->base_flags & INSTANCE_DATA_FLAG_PARTICLES) {
+ particles_storage->particles_get_instance_buffer_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset);
+ } else if (surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH) {
+ mesh_storage->_multimesh_get_motion_vectors_offsets(surf->owner->data->base, push_constant.multimesh_motion_vectors_current_offset, push_constant.multimesh_motion_vectors_previous_offset);
+ } else {
+ push_constant.multimesh_motion_vectors_current_offset = 0;
+ push_constant.multimesh_motion_vectors_previous_offset = 0;
+ }
+
+ size_t push_constant_size = 0;
+ if (pipeline_key.ubershader) {
+ push_constant_size = sizeof(SceneState::PushConstant);
+ push_constant.ubershader.specialization = pipeline_specialization;
+ push_constant.ubershader.constants = {};
+ push_constant.ubershader.constants.cull_mode = cull_mode;
+ } else {
+ push_constant_size = sizeof(SceneState::PushConstant) - sizeof(SceneState::PushConstantUbershader);
+ }
- uint32_t instance_count = surf->owner->instance_count > 1 ? surf->owner->instance_count : element_info.repeat;
- if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS) {
- instance_count /= surf->owner->trail_steps;
+ RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, push_constant_size);
+
+ uint32_t instance_count = surf->owner->instance_count > 1 ? surf->owner->instance_count : element_info.repeat;
+ if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS) {
+ instance_count /= surf->owner->trail_steps;
+ }
+
+ RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
}
- RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
i += element_info.repeat - 1; //skip equal elements
}
@@ -575,6 +633,9 @@ void RenderForwardClustered::_render_list(RenderingDevice::DrawListID p_draw_lis
case PASS_MODE_SDF: {
_render_list_template<PASS_MODE_SDF>(p_draw_list, p_framebuffer_Format, p_params, p_from_element, p_to_element);
} break;
+ default: {
+ // Unknown pass mode.
+ } break;
}
}
@@ -1013,7 +1074,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con
bool force_alpha = false;
#endif
- if (fade_alpha < 0.999) {
+ if (fade_alpha < FADE_ALPHA_PASS_THRESHOLD) {
force_alpha = true;
}
@@ -1691,12 +1752,18 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
reverse_cull = true; // for some reason our views are inverted
samplers = RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default();
+
+ // Indicate pipelines for reflection probes are required.
+ global_pipeline_data_required.use_reflection_probes = true;
} else {
screen_size = rb->get_internal_size();
if (p_render_data->scene_data->calculate_motion_vectors) {
color_pass_flags |= COLOR_PASS_FLAG_MOTION_VECTORS;
scene_shader.enable_advanced_shader_group();
+
+ // Indicate pipelines for motion vectors are required.
+ global_pipeline_data_required.use_motion_vectors = true;
}
if (p_render_data->voxel_gi_instances->size() > 0) {
@@ -1718,6 +1785,9 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
color_pass_flags |= COLOR_PASS_FLAG_MULTIVIEW;
// Try enabling here in case is_xr_enabled() returns false.
scene_shader.shader.enable_group(SceneShaderForwardClustered::SHADER_GROUP_MULTIVIEW);
+
+ // Indicate pipelines for multiview are required.
+ global_pipeline_data_required.use_multiview = true;
}
color_framebuffer = rb_data->get_color_pass_fb(color_pass_flags);
@@ -1795,6 +1865,26 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
scene_shader.enable_advanced_shader_group(p_render_data->scene_data->view_count > 1);
}
+ // Update the global pipeline requirements with all the features found to be in use in this scene.
+ if (depth_pass_mode == PASS_MODE_DEPTH_NORMAL_ROUGHNESS) {
+ global_pipeline_data_required.use_normal_and_roughness = true;
+ }
+
+ if (scene_state.used_lightmap) {
+ global_pipeline_data_required.use_lightmaps = true;
+ }
+
+ if (using_voxelgi) {
+ global_pipeline_data_required.use_voxelgi = true;
+ }
+
+ if (using_separate_specular) {
+ global_pipeline_data_required.use_separate_specular = true;
+ }
+
+ // Update the compiled pipelines if any of the requirements have changed.
+ _update_dirty_geometry_pipelines();
+
RID radiance_texture;
bool draw_sky = false;
bool draw_sky_fog_only = false;
@@ -1911,12 +2001,8 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
bool debug_sdfgi_probes = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_SDFGI_PROBES;
bool depth_pre_pass = bool(GLOBAL_GET("rendering/driver/depth_prepass/enable")) && depth_framebuffer.is_valid();
- uint32_t spec_constant_base_flags = 0;
- {
- if (p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) {
- spec_constant_base_flags |= 1 << SPEC_CONSTANT_USE_DEPTH_FOG;
- }
- }
+ SceneShaderForwardClustered::ShaderSpecialization base_specialization = scene_shader.default_specialization;
+ base_specialization.use_depth_fog = p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH;
bool using_ssao = depth_pre_pass && !is_reflection_probe && p_render_data->environment.is_valid() && environment_get_ssao_enabled(p_render_data->environment);
@@ -1940,7 +2026,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, nullptr, RID(), samplers);
bool finish_depth = using_ssao || using_ssil || using_sdfgi || using_voxelgi || ce_pre_opaque_resolved_depth || ce_post_opaque_resolved_depth;
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, 0, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, depth_pass_mode, 0, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
_render_list_with_draw_list(&render_list_params, depth_framebuffer, needs_pre_resolve ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, needs_pre_resolve ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, needs_pre_resolve ? Vector<Color>() : depth_pass_clear);
RD::get_singleton()->draw_command_end_label();
@@ -2019,7 +2105,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
uint32_t opaque_color_pass_flags = using_motion_pass ? (color_pass_flags & ~COLOR_PASS_FLAG_MOTION_VECTORS) : color_pass_flags;
RID opaque_framebuffer = using_motion_pass ? rb_data->get_color_pass_fb(opaque_color_pass_flags) : color_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, opaque_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
_render_list_with_draw_list(&render_list_params, opaque_framebuffer, load_color ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pre_pass ? RD::INITIAL_ACTION_LOAD : RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, c, 0.0, 0);
}
@@ -2039,7 +2125,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_MOTION, p_render_data, radiance_texture, samplers, true);
- RenderListParameters render_list_params(render_list[RENDER_LIST_MOTION].elements.ptr(), render_list[RENDER_LIST_MOTION].element_info.ptr(), render_list[RENDER_LIST_MOTION].elements.size(), reverse_cull, PASS_MODE_COLOR, color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_MOTION].elements.ptr(), render_list[RENDER_LIST_MOTION].element_info.ptr(), render_list[RENDER_LIST_MOTION].elements.size(), reverse_cull, PASS_MODE_COLOR, color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
_render_list_with_draw_list(&render_list_params, color_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE);
RD::get_singleton()->draw_command_end_label();
@@ -2218,7 +2304,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
}
RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, base_specialization);
_render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE);
}
@@ -2841,6 +2927,9 @@ void RenderForwardClustered::_render_sdfgi(Ref<RenderSceneBuffersRD> p_render_bu
_update_render_base_uniform_set();
+ // Indicate pipelines for SDFGI are required.
+ global_pipeline_data_required.use_sdfgi = true;
+
PassMode pass_mode = PASS_MODE_SDF;
_fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
render_list[RENDER_LIST_SECONDARY].sort_by_key();
@@ -3734,14 +3823,23 @@ void RenderForwardClustered::GeometryInstanceForwardClustered::_mark_dirty() {
RenderForwardClustered::get_singleton()->geometry_instance_dirty_list.add(&dirty_list_element);
}
-void RenderForwardClustered::_geometry_instance_add_surface_with_material(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, SceneShaderForwardClustered::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh) {
- RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+void RenderForwardClustered::_update_global_pipeline_data_requirements_from_project() {
+ const int msaa_3d_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_3d");
+ const bool directional_shadow_16_bits = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/16_bits");
+ const bool positional_shadow_16_bits = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_16_bits");
+ global_pipeline_data_required.use_16_bit_shadows = directional_shadow_16_bits || positional_shadow_16_bits;
+ global_pipeline_data_required.use_32_bit_shadows = !directional_shadow_16_bits || !positional_shadow_16_bits;
+ global_pipeline_data_required.texture_samples = RenderSceneBuffersRD::msaa_to_samples(RS::ViewportMSAA(msaa_3d_mode));
+}
- bool has_read_screen_alpha = p_material->shader_data->uses_screen_texture || p_material->shader_data->uses_depth_texture || p_material->shader_data->uses_normal_texture;
- bool has_base_alpha = (p_material->shader_data->uses_alpha && (!p_material->shader_data->uses_alpha_clip || p_material->shader_data->uses_alpha_antialiasing)) || has_read_screen_alpha;
- bool has_blend_alpha = p_material->shader_data->uses_blend_alpha;
- bool has_alpha = has_base_alpha || has_blend_alpha;
+void RenderForwardClustered::_update_global_pipeline_data_requirements_from_light_storage() {
+ RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
+ global_pipeline_data_required.use_shadow_cubemaps = light_storage->get_shadow_cubemaps_used();
+ global_pipeline_data_required.use_shadow_dual_paraboloid = light_storage->get_shadow_dual_paraboloid_used();
+}
+void RenderForwardClustered::_geometry_instance_add_surface_with_material(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, SceneShaderForwardClustered::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
uint32_t flags = 0;
if (p_material->shader_data->uses_sss) {
@@ -3764,10 +3862,9 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS;
}
- if (has_alpha || has_read_screen_alpha || p_material->shader_data->depth_draw == SceneShaderForwardClustered::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardClustered::ShaderData::DEPTH_TEST_DISABLED) {
- //material is only meant for alpha pass
+ if (p_material->shader_data->uses_alpha_pass()) {
flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA;
- if ((p_material->shader_data->uses_depth_prepass_alpha || p_material->shader_data->uses_alpha_antialiasing) && !(p_material->shader_data->depth_draw == SceneShaderForwardClustered::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardClustered::ShaderData::DEPTH_TEST_DISABLED)) {
+ if (p_material->shader_data->uses_depth_in_alpha_pass()) {
flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH;
flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW;
}
@@ -3787,16 +3884,14 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
SceneShaderForwardClustered::MaterialData *material_shadow = nullptr;
void *surface_shadow = nullptr;
- if (!p_material->shader_data->uses_particle_trails && !p_material->shader_data->writes_modelview_or_projection && !p_material->shader_data->uses_vertex && !p_material->shader_data->uses_position && !p_material->shader_data->uses_discard && !p_material->shader_data->uses_depth_prepass_alpha && !p_material->shader_data->uses_alpha_clip && !p_material->shader_data->uses_alpha_antialiasing && p_material->shader_data->cull_mode == SceneShaderForwardClustered::ShaderData::CULL_BACK && !p_material->shader_data->uses_point_size && !p_material->shader_data->uses_world_coordinates) {
+ if (p_material->shader_data->uses_shared_shadow_material()) {
flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_SHARED_SHADOW_MATERIAL;
material_shadow = static_cast<SceneShaderForwardClustered::MaterialData *>(RendererRD::MaterialStorage::get_singleton()->material_get_data(scene_shader.default_material, RendererRD::MaterialStorage::SHADER_TYPE_3D));
RID shadow_mesh = mesh_storage->mesh_get_shadow_mesh(p_mesh);
-
if (shadow_mesh.is_valid()) {
surface_shadow = mesh_storage->mesh_get_surface(shadow_mesh, p_surface);
}
-
} else {
material_shadow = p_material;
}
@@ -3848,6 +3943,16 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material(Geomet
String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")";
WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path));
}
+
+#if PRELOAD_PIPELINES_ON_SURFACE_CACHE_CONSTRUCTION
+ if (!sdcache->compilation_dirty_element.in_list()) {
+ geometry_surface_compilation_dirty_list.add(&sdcache->compilation_dirty_element);
+ }
+
+ if (!sdcache->compilation_all_element.in_list()) {
+ geometry_surface_compilation_all_list.add(&sdcache->compilation_all_element);
+ }
+#endif
}
void RenderForwardClustered::_geometry_instance_add_surface_with_material_chain(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, SceneShaderForwardClustered::MaterialData *p_material, RID p_mat_src, RID p_mesh) {
@@ -3859,7 +3964,7 @@ void RenderForwardClustered::_geometry_instance_add_surface_with_material_chain(
while (material->next_pass.is_valid()) {
RID next_pass = material->next_pass;
material = static_cast<SceneShaderForwardClustered::MaterialData *>(material_storage->material_get_data(next_pass, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- if (!material || !material->shader_data->valid) {
+ if (!material || !material->shader_data->is_valid()) {
break;
}
if (ginstance->data->dirty_dependencies) {
@@ -3879,7 +3984,7 @@ void RenderForwardClustered::_geometry_instance_add_surface(GeometryInstanceForw
if (m_src.is_valid()) {
material = static_cast<SceneShaderForwardClustered::MaterialData *>(material_storage->material_get_data(m_src, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- if (!material || !material->shader_data->valid) {
+ if (!material || !material->shader_data->is_valid()) {
material = nullptr;
}
}
@@ -3901,7 +4006,7 @@ void RenderForwardClustered::_geometry_instance_add_surface(GeometryInstanceForw
m_src = ginstance->data->material_overlay;
material = static_cast<SceneShaderForwardClustered::MaterialData *>(material_storage->material_get_data(m_src, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- if (material && material->shader_data->valid) {
+ if (material && material->shader_data->is_valid()) {
if (ginstance->data->dirty_dependencies) {
material_storage->material_update_dependency(m_src, &ginstance->data->dependency_tracker);
}
@@ -4068,10 +4173,361 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p
ginstance->dirty_list_element.remove_from_list();
}
+static RD::FramebufferFormatID _get_color_framebuffer_format_for_pipeline(RD::DataFormat p_color_format, bool p_can_be_storage, RD::TextureSamples p_samples, bool p_specular, bool p_velocity, uint32_t p_view_count) {
+ const bool multisampling = p_samples > RD::TEXTURE_SAMPLES_1;
+ RD::AttachmentFormat attachment;
+ attachment.samples = p_samples;
+
+ RD::AttachmentFormat unused_attachment;
+ unused_attachment.usage_flags = RD::AttachmentFormat::UNUSED_ATTACHMENT;
+
+ thread_local Vector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ // Color attachment.
+ attachment.format = p_color_format;
+ attachment.usage_flags = RenderSceneBuffersRD::get_color_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+
+ if (p_specular) {
+ attachment.format = RenderForwardClustered::RenderBufferDataForwardClustered::get_specular_format();
+ attachment.usage_flags = RenderForwardClustered::RenderBufferDataForwardClustered::get_specular_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+ } else {
+ attachments.push_back(unused_attachment);
+ }
+
+ if (p_velocity) {
+ attachment.format = RenderSceneBuffersRD::get_velocity_format();
+ attachment.usage_flags = RenderSceneBuffersRD::get_velocity_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+ } else {
+ attachments.push_back(unused_attachment);
+ }
+
+ // Depth attachment.
+ attachment.format = RenderSceneBuffersRD::get_depth_format(false, multisampling, p_can_be_storage);
+ attachment.usage_flags = RenderSceneBuffersRD::get_depth_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+
+ thread_local Vector<RD::FramebufferPass> passes;
+ passes.resize(1);
+ passes.ptrw()[0].color_attachments.resize(attachments.size() - 1);
+
+ int *color_attachments = passes.ptrw()[0].color_attachments.ptrw();
+ for (int64_t i = 0; i < attachments.size() - 1; i++) {
+ color_attachments[i] = (attachments[i].usage_flags == RD::AttachmentFormat::UNUSED_ATTACHMENT) ? RD::ATTACHMENT_UNUSED : i;
+ }
+
+ passes.ptrw()[0].depth_attachment = attachments.size() - 1;
+
+ return RD::get_singleton()->framebuffer_format_create_multipass(attachments, passes, p_view_count);
+}
+
+static RD::FramebufferFormatID _get_reflection_probe_color_framebuffer_format_for_pipeline() {
+ RD::AttachmentFormat attachment;
+ thread_local Vector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ attachment.format = RendererRD::LightStorage::get_reflection_probe_color_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_reflection_probe_color_usage_bits();
+ attachments.push_back(attachment);
+
+ attachment.format = RendererRD::LightStorage::get_reflection_probe_depth_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_reflection_probe_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+static RD::FramebufferFormatID _get_depth_framebuffer_format_for_pipeline(bool p_can_be_storage, RD::TextureSamples p_samples, bool p_normal_roughness, bool p_voxelgi) {
+ const bool multisampling = p_samples > RD::TEXTURE_SAMPLES_1;
+ RD::AttachmentFormat attachment;
+ attachment.samples = p_samples;
+
+ thread_local LocalVector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ attachment.format = RenderSceneBuffersRD::get_depth_format(false, multisampling, p_can_be_storage);
+ attachment.usage_flags = RenderSceneBuffersRD::get_depth_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+
+ if (p_normal_roughness) {
+ attachment.format = RenderForwardClustered::RenderBufferDataForwardClustered::get_normal_roughness_format();
+ attachment.usage_flags = RenderForwardClustered::RenderBufferDataForwardClustered::get_normal_roughness_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+ }
+
+ if (p_voxelgi) {
+ attachment.format = RenderForwardClustered::RenderBufferDataForwardClustered::get_voxelgi_format();
+ attachment.usage_flags = RenderForwardClustered::RenderBufferDataForwardClustered::get_voxelgi_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+ }
+
+ thread_local Vector<RD::FramebufferPass> passes;
+ passes.resize(1);
+ passes.ptrw()[0].color_attachments.resize(attachments.size() - 1);
+
+ int *color_attachments = passes.ptrw()[0].color_attachments.ptrw();
+ for (int64_t i = 1; i < attachments.size(); i++) {
+ color_attachments[i - 1] = (attachments[i].usage_flags == RD::AttachmentFormat::UNUSED_ATTACHMENT) ? RD::ATTACHMENT_UNUSED : i;
+ }
+
+ passes.ptrw()[0].depth_attachment = 0;
+
+ return RD::get_singleton()->framebuffer_format_create_multipass(attachments, passes);
+}
+
+static RD::FramebufferFormatID _get_shadow_cubemap_framebuffer_format_for_pipeline() {
+ thread_local LocalVector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ RD::AttachmentFormat attachment;
+ attachment.format = RendererRD::LightStorage::get_cubemap_depth_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_cubemap_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+static RD::FramebufferFormatID _get_shadow_atlas_framebuffer_format_for_pipeline(bool p_use_16_bits) {
+ thread_local LocalVector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ RD::AttachmentFormat attachment;
+ attachment.format = RendererRD::LightStorage::get_shadow_atlas_depth_format(p_use_16_bits);
+ attachment.usage_flags = RendererRD::LightStorage::get_shadow_atlas_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+static RD::FramebufferFormatID _get_reflection_probe_depth_framebuffer_format_for_pipeline() {
+ thread_local LocalVector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ RD::AttachmentFormat attachment;
+ attachment.format = RendererRD::LightStorage::get_reflection_probe_depth_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_reflection_probe_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+void RenderForwardClustered::_mesh_compile_pipeline_for_surface(SceneShaderForwardClustered::ShaderData *p_shader, void *p_mesh_surface, bool p_ubershader, bool p_instanced_surface, RS::PipelineSource p_source, SceneShaderForwardClustered::ShaderData::PipelineKey &r_pipeline_key, Vector<ShaderPipelinePair> *r_pipeline_pairs) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ uint64_t input_mask = p_shader->get_vertex_input_mask(r_pipeline_key.version, r_pipeline_key.color_pass_flags, p_ubershader);
+ bool pipeline_motion_vectors = r_pipeline_key.color_pass_flags & SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
+ r_pipeline_key.vertex_format_id = mesh_storage->mesh_surface_get_vertex_format(p_mesh_surface, input_mask, p_instanced_surface, pipeline_motion_vectors);
+ r_pipeline_key.ubershader = p_ubershader;
+
+ p_shader->pipeline_hash_map.compile_pipeline(r_pipeline_key, r_pipeline_key.hash(), p_source);
+
+ if (r_pipeline_pairs != nullptr) {
+ r_pipeline_pairs->push_back({ p_shader, r_pipeline_key });
+ }
+}
+
+void RenderForwardClustered::_mesh_compile_pipelines_for_surface(const SurfacePipelineData &p_surface, const GlobalPipelineData &p_global, RS::PipelineSource p_source, Vector<ShaderPipelinePair> *r_pipeline_pairs) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+
+ // Retrieve from the scene shader which groups are currently enabled.
+ const bool multiview_enabled = p_global.use_multiview && scene_shader.is_multiview_shader_group_enabled();
+ const RD::DataFormat buffers_color_format = _render_buffers_get_color_format();
+ const bool buffers_can_be_storage = _render_buffers_can_be_storage();
+
+ // Set the attributes common to all pipelines.
+ SceneShaderForwardClustered::ShaderData::PipelineKey pipeline_key;
+ pipeline_key.cull_mode = RD::POLYGON_CULL_DISABLED;
+ pipeline_key.primitive_type = mesh_storage->mesh_surface_get_primitive(p_surface.mesh_surface);
+ pipeline_key.wireframe = false;
+
+ // Grab the shader and surface used for most passes.
+ const uint32_t multiview_iterations = multiview_enabled ? 2 : 1;
+ const uint32_t lightmap_iterations = p_global.use_lightmaps && p_surface.can_use_lightmap ? 2 : 1;
+ const uint32_t alpha_iterations = p_surface.uses_transparent ? 2 : 1;
+ for (uint32_t multiview = 0; multiview < multiview_iterations; multiview++) {
+ for (uint32_t lightmap = 0; lightmap < lightmap_iterations; lightmap++) {
+ for (uint32_t alpha = p_surface.uses_opaque ? 0 : 1; alpha < alpha_iterations; alpha++) {
+ // Generate all the possible variants used during the color pass.
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_COLOR_PASS;
+ pipeline_key.color_pass_flags = 0;
+
+ if (lightmap) {
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_LIGHTMAP;
+ }
+
+ if (alpha) {
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_TRANSPARENT;
+ }
+
+ if (multiview) {
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MULTIVIEW;
+ } else if (p_global.use_reflection_probes) {
+ // Reflection probe can't be rendered in multiview.
+ pipeline_key.framebuffer_format_id = _get_reflection_probe_color_framebuffer_format_for_pipeline();
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ // View count is assumed to be 2 as the configuration is dependent on the viewport. It's likely a safe assumption for stereo rendering.
+ uint32_t view_count = multiview ? 2 : 1;
+ pipeline_key.framebuffer_format_id = _get_color_framebuffer_format_for_pipeline(buffers_color_format, buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), false, false, view_count);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ // Generate all the possible variants used during the advanced color passes.
+ const uint32_t separate_specular_iterations = p_global.use_separate_specular ? 2 : 1;
+ const uint32_t motion_vectors_iterations = p_global.use_motion_vectors ? 2 : 1;
+ uint32_t base_color_pass_flags = pipeline_key.color_pass_flags;
+ for (uint32_t separate_specular = 0; separate_specular < separate_specular_iterations; separate_specular++) {
+ for (uint32_t motion_vectors = 0; motion_vectors < motion_vectors_iterations; motion_vectors++) {
+ if (!separate_specular && !motion_vectors) {
+ // This case was already generated.
+ continue;
+ }
+
+ pipeline_key.color_pass_flags = base_color_pass_flags;
+
+ if (separate_specular) {
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_SEPARATE_SPECULAR;
+ }
+
+ if (motion_vectors) {
+ pipeline_key.color_pass_flags |= SceneShaderForwardClustered::PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS;
+ }
+
+ pipeline_key.framebuffer_format_id = _get_color_framebuffer_format_for_pipeline(buffers_color_format, buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), separate_specular, motion_vectors, view_count);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+ }
+ }
+ }
+ }
+
+ if (!p_surface.uses_depth) {
+ return;
+ }
+
+ // Generate the depth pipelines if the material supports depth or it must be part of the shadow pass.
+ pipeline_key.color_pass_flags = 0;
+
+ if (p_global.use_normal_and_roughness) {
+ // A lot of different effects rely on normal and roughness being written to during the depth pass.
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS;
+ pipeline_key.framebuffer_format_id = _get_depth_framebuffer_format_for_pipeline(buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), true, false);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ if (p_global.use_voxelgi) {
+ // Depth pass with VoxelGI support.
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI;
+ pipeline_key.framebuffer_format_id = _get_depth_framebuffer_format_for_pipeline(buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), true, true);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ if (p_global.use_sdfgi) {
+ // Depth pass with SDFGI support.
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_WITH_SDF;
+ pipeline_key.framebuffer_format_id = _get_depth_framebuffer_format_for_pipeline(buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), false, false);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, false, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ // Depth pass with SDFGI support for an empty framebuffer.
+ pipeline_key.framebuffer_format_id = RD::get_singleton()->framebuffer_format_create_empty();
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, false, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ // The dedicated depth passes use a different version of the surface and the shader.
+ pipeline_key.primitive_type = mesh_storage->mesh_surface_get_primitive(p_surface.mesh_surface_shadow);
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS;
+ pipeline_key.framebuffer_format_id = _get_depth_framebuffer_format_for_pipeline(buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), false, false);
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ if (p_global.use_shadow_dual_paraboloid) {
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_DP;
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ if (p_global.use_shadow_cubemaps) {
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS;
+ pipeline_key.framebuffer_format_id = _get_shadow_cubemap_framebuffer_format_for_pipeline();
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ // Atlas shadowmaps (omni lights) can be in both 16-bit and 32-bit versions.
+ const uint32_t use_16_bits_start = p_global.use_32_bit_shadows ? 0 : 1;
+ const uint32_t use_16_bits_iterations = p_global.use_16_bit_shadows ? 2 : 1;
+ for (uint32_t use_16_bits = use_16_bits_start; use_16_bits < use_16_bits_iterations; use_16_bits++) {
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS;
+ pipeline_key.framebuffer_format_id = _get_shadow_atlas_framebuffer_format_for_pipeline(use_16_bits);
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ if (p_global.use_shadow_dual_paraboloid) {
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS_DP;
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+ }
+
+ if (p_global.use_reflection_probes) {
+ // Depth pass for reflection probes. Normally this will be redundant as the format is the exact same as the shadow cubemap.
+ pipeline_key.version = SceneShaderForwardClustered::PIPELINE_VERSION_DEPTH_PASS;
+ pipeline_key.framebuffer_format_id = _get_reflection_probe_depth_framebuffer_format_for_pipeline();
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, true, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+}
+
+void RenderForwardClustered::_mesh_generate_all_pipelines_for_surface_cache(GeometryInstanceSurfaceDataCache *p_surface_cache, const GlobalPipelineData &p_global) {
+ bool uses_alpha_pass = (p_surface_cache->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA) != 0;
+ float multiplied_fade_alpha = p_surface_cache->owner->force_alpha * p_surface_cache->owner->parent_fade_alpha;
+ bool uses_fade = (multiplied_fade_alpha < FADE_ALPHA_PASS_THRESHOLD) || p_surface_cache->owner->fade_near || p_surface_cache->owner->fade_far;
+ SurfacePipelineData surface;
+ surface.mesh_surface = p_surface_cache->surface;
+ surface.mesh_surface_shadow = p_surface_cache->surface_shadow;
+ surface.shader = p_surface_cache->shader;
+ surface.shader_shadow = p_surface_cache->shader_shadow;
+ surface.instanced = p_surface_cache->owner->mesh_instance.is_valid();
+ surface.uses_opaque = !uses_alpha_pass;
+ surface.uses_transparent = uses_alpha_pass || uses_fade;
+ surface.uses_depth = (p_surface_cache->flags & (GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH | GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE | GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW)) != 0;
+ surface.can_use_lightmap = p_surface_cache->owner->lightmap_instance.is_valid() || p_surface_cache->owner->lightmap_sh;
+ _mesh_compile_pipelines_for_surface(surface, p_global, RS::PIPELINE_SOURCE_SURFACE);
+}
+
void RenderForwardClustered::_update_dirty_geometry_instances() {
while (geometry_instance_dirty_list.first()) {
_geometry_instance_update(geometry_instance_dirty_list.first()->self());
}
+
+ _update_dirty_geometry_pipelines();
+}
+
+void RenderForwardClustered::_update_dirty_geometry_pipelines() {
+ if (global_pipeline_data_required.key != global_pipeline_data_compiled.key) {
+ // Go through the entire list of surfaces and compile pipelines for everything again.
+ SelfList<GeometryInstanceSurfaceDataCache> *list = geometry_surface_compilation_all_list.first();
+ while (list != nullptr) {
+ GeometryInstanceSurfaceDataCache *surface_cache = list->self();
+ _mesh_generate_all_pipelines_for_surface_cache(surface_cache, global_pipeline_data_required);
+
+ if (surface_cache->compilation_dirty_element.in_list()) {
+ // Remove any elements from the dirty list as they don't need to be processed again.
+ geometry_surface_compilation_dirty_list.remove(&surface_cache->compilation_dirty_element);
+ }
+
+ list = list->next();
+ }
+
+ global_pipeline_data_compiled.key = global_pipeline_data_required.key;
+ } else {
+ // Compile pipelines only for the dirty list.
+ if (!geometry_surface_compilation_dirty_list.first()) {
+ return;
+ }
+
+ while (geometry_surface_compilation_dirty_list.first() != nullptr) {
+ GeometryInstanceSurfaceDataCache *surface_cache = geometry_surface_compilation_dirty_list.first()->self();
+ _mesh_generate_all_pipelines_for_surface_cache(surface_cache, global_pipeline_data_compiled);
+ surface_cache->compilation_dirty_element.remove_from_list();
+ }
+ }
}
void RenderForwardClustered::_geometry_instance_dependency_changed(Dependency::DependencyChangedNotification p_notification, DependencyTracker *p_tracker) {
@@ -4174,6 +4630,66 @@ uint32_t RenderForwardClustered::geometry_instance_get_pair_mask() {
return (1 << RS::INSTANCE_VOXEL_GI);
}
+void RenderForwardClustered::mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) {
+ RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ RID shadow_mesh = mesh_storage->mesh_get_shadow_mesh(p_mesh);
+ uint32_t surface_count = 0;
+ const RID *materials = mesh_storage->mesh_get_surface_count_and_materials(p_mesh, surface_count);
+ Vector<ShaderPipelinePair> pipeline_pairs;
+ for (uint32_t i = 0; i < surface_count; i++) {
+ if (materials[i].is_null()) {
+ continue;
+ }
+
+ void *mesh_surface = mesh_storage->mesh_get_surface(p_mesh, i);
+ void *mesh_surface_shadow = mesh_surface;
+ SceneShaderForwardClustered::MaterialData *material = static_cast<SceneShaderForwardClustered::MaterialData *>(material_storage->material_get_data(materials[i], RendererRD::MaterialStorage::SHADER_TYPE_3D));
+ if (material == nullptr) {
+ continue;
+ }
+
+ SceneShaderForwardClustered::ShaderData *shader = material->shader_data;
+ SceneShaderForwardClustered::ShaderData *shader_shadow = shader;
+ if (material->shader_data->uses_shared_shadow_material()) {
+ SceneShaderForwardClustered::MaterialData *material_shadow = static_cast<SceneShaderForwardClustered::MaterialData *>(material_storage->material_get_data(scene_shader.default_material, RendererRD::MaterialStorage::SHADER_TYPE_3D));
+ if (material_shadow != nullptr) {
+ shader_shadow = material_shadow->shader_data;
+ if (shadow_mesh.is_valid()) {
+ mesh_surface_shadow = mesh_storage->mesh_get_surface(shadow_mesh, i);
+ }
+ }
+ }
+
+ if (!shader->is_valid()) {
+ continue;
+ }
+
+ SurfacePipelineData surface;
+ surface.mesh_surface = mesh_surface;
+ surface.mesh_surface_shadow = mesh_surface_shadow;
+ surface.shader = shader;
+ surface.shader_shadow = shader_shadow;
+ surface.instanced = mesh_storage->mesh_needs_instance(p_mesh, true);
+ surface.uses_opaque = !material->shader_data->uses_alpha_pass();
+ surface.uses_transparent = material->shader_data->uses_alpha_pass();
+ surface.uses_depth = surface.uses_opaque || (surface.uses_transparent && material->shader_data->uses_depth_in_alpha_pass());
+ surface.can_use_lightmap = mesh_storage->mesh_surface_get_format(mesh_surface) & RS::ARRAY_FORMAT_TEX_UV2;
+ _mesh_compile_pipelines_for_surface(surface, global_pipeline_data_required, RS::PIPELINE_SOURCE_MESH, &pipeline_pairs);
+ }
+
+ // Try to retrieve all the pipeline pairs that were compiled. This will force the loader to wait on all ubershader pipelines to be ready.
+ if (!p_background_compilation && !pipeline_pairs.is_empty()) {
+ for (ShaderPipelinePair pair : pipeline_pairs) {
+ pair.first->pipeline_hash_map.get_pipeline(pair.second, pair.second.hash(), true, RS::PIPELINE_SOURCE_MESH);
+ }
+ }
+}
+
+uint32_t RenderForwardClustered::get_pipeline_compilations(RS::PipelineSource p_source) {
+ return scene_shader.get_pipeline_compilations(p_source);
+}
+
void RenderForwardClustered::GeometryInstanceForwardClustered::pair_voxel_gi_instances(const RID *p_voxel_gi_instances, uint32_t p_voxel_gi_instance_count) {
if (p_voxel_gi_instance_count > 0) {
voxel_gi_instances[0] = p_voxel_gi_instances[0];
@@ -4195,54 +4711,23 @@ void RenderForwardClustered::GeometryInstanceForwardClustered::set_softshadow_pr
}
void RenderForwardClustered::_update_shader_quality_settings() {
- Vector<RD::PipelineSpecializationConstant> spec_constants;
-
- RD::PipelineSpecializationConstant sc;
- sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
-
- sc.constant_id = SPEC_CONSTANT_SOFT_SHADOW_SAMPLES;
- sc.int_value = soft_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_PENUMBRA_SHADOW_SAMPLES;
- sc.int_value = penumbra_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_DIRECTIONAL_SOFT_SHADOW_SAMPLES;
- sc.int_value = directional_soft_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_DIRECTIONAL_PENUMBRA_SHADOW_SAMPLES;
- sc.int_value = directional_penumbra_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_BOOL;
- sc.constant_id = SPEC_CONSTANT_DECAL_FILTER;
- sc.bool_value = decals_get_filter() == RS::DECAL_FILTER_NEAREST_MIPMAPS ||
+ SceneShaderForwardClustered::ShaderSpecialization specialization = {};
+ specialization.decal_use_mipmaps = decals_get_filter() == RS::DECAL_FILTER_NEAREST_MIPMAPS ||
decals_get_filter() == RS::DECAL_FILTER_LINEAR_MIPMAPS ||
decals_get_filter() == RS::DECAL_FILTER_NEAREST_MIPMAPS_ANISOTROPIC ||
decals_get_filter() == RS::DECAL_FILTER_LINEAR_MIPMAPS_ANISOTROPIC;
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_PROJECTOR_FILTER;
- sc.bool_value = light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_NEAREST_MIPMAPS ||
+ ;
+ specialization.projector_use_mipmaps = light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_NEAREST_MIPMAPS ||
light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_LINEAR_MIPMAPS ||
light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_NEAREST_MIPMAPS_ANISOTROPIC ||
light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_LINEAR_MIPMAPS_ANISOTROPIC;
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER;
- sc.bool_value = lightmap_filter_bicubic_get();
-
- spec_constants.push_back(sc);
-
- scene_shader.set_default_specialization_constants(spec_constants);
+ specialization.soft_shadow_samples = soft_shadow_samples_get();
+ specialization.penumbra_shadow_samples = penumbra_shadow_samples_get();
+ specialization.directional_soft_shadow_samples = directional_soft_shadow_samples_get();
+ specialization.directional_penumbra_shadow_samples = directional_penumbra_shadow_samples_get();
+ specialization.use_lightmap_bicubic_filter = lightmap_filter_bicubic_get();
+ scene_shader.set_default_specialization(specialization);
base_uniforms_changed(); //also need this
}
@@ -4261,6 +4746,11 @@ RenderForwardClustered::RenderForwardClustered() {
defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n";
defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n";
+ bool force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
+ if (force_vertex_shading) {
+ defines += "\n#define USE_VERTEX_LIGHTING\n";
+ }
+
{
//lightmaps
scene_state.max_lightmaps = MAX_LIGHTMAPS;
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index 5d14653db6..ebc291e803 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -66,17 +66,6 @@ class RenderForwardClustered : public RendererSceneRenderRD {
};
enum {
- SPEC_CONSTANT_SOFT_SHADOW_SAMPLES = 6,
- SPEC_CONSTANT_PENUMBRA_SHADOW_SAMPLES = 7,
- SPEC_CONSTANT_DIRECTIONAL_SOFT_SHADOW_SAMPLES = 8,
- SPEC_CONSTANT_DIRECTIONAL_PENUMBRA_SHADOW_SAMPLES = 9,
- SPEC_CONSTANT_DECAL_FILTER = 10,
- SPEC_CONSTANT_PROJECTOR_FILTER = 11,
- SPEC_CONSTANT_USE_DEPTH_FOG = 12,
- SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER = 13,
- };
-
- enum {
SDFGI_MAX_CASCADES = 8,
MAX_VOXEL_GI_INSTANCESS = 8,
MAX_LIGHTMAPS = 8,
@@ -96,6 +85,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
SceneShaderForwardClustered scene_shader;
+public:
/* Framebuffer */
class RenderBufferDataForwardClustered : public RenderBufferCustomDataRD {
@@ -155,8 +145,16 @@ class RenderForwardClustered : public RendererSceneRenderRD {
virtual void configure(RenderSceneBuffersRD *p_render_buffers) override;
virtual void free_data() override;
+
+ static RD::DataFormat get_specular_format();
+ static uint32_t get_specular_usage_bits(bool p_resolve, bool p_msaa, bool p_storage);
+ static RD::DataFormat get_normal_roughness_format();
+ static uint32_t get_normal_roughness_usage_bits(bool p_resolve, bool p_msaa, bool p_storage);
+ static RD::DataFormat get_voxelgi_format();
+ static uint32_t get_voxelgi_usage_bits(bool p_resolve, bool p_msaa, bool p_storage);
};
+private:
virtual void setup_render_buffer_data(Ref<RenderSceneBuffersRD> p_render_buffers) override;
RID render_base_uniform_set;
@@ -183,6 +181,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
PASS_MODE_DEPTH_NORMAL_ROUGHNESS_VOXEL_GI,
PASS_MODE_DEPTH_MATERIAL,
PASS_MODE_SDF,
+ PASS_MODE_MAX
};
enum ColorPassFlags {
@@ -212,9 +211,9 @@ class RenderForwardClustered : public RendererSceneRenderRD {
RD::FramebufferFormatID framebuffer_format = 0;
uint32_t element_offset = 0;
bool use_directional_soft_shadow = false;
- uint32_t spec_constant_base_flags = 0;
+ SceneShaderForwardClustered::ShaderSpecialization base_specialization = {};
- RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, uint32_t p_color_pass_flags, bool p_no_gi, bool p_use_directional_soft_shadows, RID p_render_pass_uniform_set, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), float p_lod_distance_multiplier = 0.0, float p_screen_mesh_lod_threshold = 0.0, uint32_t p_view_count = 1, uint32_t p_element_offset = 0, uint32_t p_spec_constant_base_flags = 0) {
+ RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, uint32_t p_color_pass_flags, bool p_no_gi, bool p_use_directional_soft_shadows, RID p_render_pass_uniform_set, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), float p_lod_distance_multiplier = 0.0, float p_screen_mesh_lod_threshold = 0.0, uint32_t p_view_count = 1, uint32_t p_element_offset = 0, SceneShaderForwardClustered::ShaderSpecialization p_base_specialization = {}) {
elements = p_elements;
element_info = p_element_info;
element_count = p_element_count;
@@ -230,7 +229,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
screen_mesh_lod_threshold = p_screen_mesh_lod_threshold;
element_offset = p_element_offset;
use_directional_soft_shadow = p_use_directional_soft_shadows;
- spec_constant_base_flags = p_spec_constant_base_flags;
+ base_specialization = p_base_specialization;
}
};
@@ -293,11 +292,17 @@ class RenderForwardClustered : public RendererSceneRenderRD {
uint32_t volumetric_fog_pad;
};
+ struct PushConstantUbershader {
+ SceneShaderForwardClustered::ShaderSpecialization specialization;
+ SceneShaderForwardClustered::UbershaderConstants constants;
+ };
+
struct PushConstant {
uint32_t base_index; //
uint32_t uv_offset; //packed
uint32_t multimesh_motion_vectors_current_offset;
uint32_t multimesh_motion_vectors_previous_offset;
+ PushConstantUbershader ubershader;
};
struct InstanceData {
@@ -450,6 +455,11 @@ class RenderForwardClustered : public RendererSceneRenderRD {
GeometryInstanceSurfaceDataCache *next = nullptr;
GeometryInstanceForwardClustered *owner = nullptr;
+ SelfList<GeometryInstanceSurfaceDataCache> compilation_dirty_element;
+ SelfList<GeometryInstanceSurfaceDataCache> compilation_all_element;
+
+ GeometryInstanceSurfaceDataCache() :
+ compilation_dirty_element(this), compilation_all_element(this) {}
};
class GeometryInstanceForwardClustered : public RenderGeometryInstanceBase {
@@ -500,16 +510,63 @@ class RenderForwardClustered : public RendererSceneRenderRD {
static void _geometry_instance_dependency_deleted(const RID &p_dependency, DependencyTracker *p_tracker);
SelfList<GeometryInstanceForwardClustered>::List geometry_instance_dirty_list;
+ SelfList<GeometryInstanceSurfaceDataCache>::List geometry_surface_compilation_dirty_list;
+ SelfList<GeometryInstanceSurfaceDataCache>::List geometry_surface_compilation_all_list;
PagedAllocator<GeometryInstanceForwardClustered> geometry_instance_alloc;
PagedAllocator<GeometryInstanceSurfaceDataCache> geometry_instance_surface_alloc;
PagedAllocator<GeometryInstanceLightmapSH> geometry_instance_lightmap_sh;
+ struct SurfacePipelineData {
+ void *mesh_surface = nullptr;
+ void *mesh_surface_shadow = nullptr;
+ SceneShaderForwardClustered::ShaderData *shader = nullptr;
+ SceneShaderForwardClustered::ShaderData *shader_shadow = nullptr;
+ bool instanced = false;
+ bool uses_opaque = false;
+ bool uses_transparent = false;
+ bool uses_depth = false;
+ bool can_use_lightmap = false;
+ };
+
+ struct GlobalPipelineData {
+ union {
+ struct {
+ uint32_t texture_samples : 3;
+ uint32_t use_reflection_probes : 1;
+ uint32_t use_separate_specular : 1;
+ uint32_t use_motion_vectors : 1;
+ uint32_t use_normal_and_roughness : 1;
+ uint32_t use_lightmaps : 1;
+ uint32_t use_voxelgi : 1;
+ uint32_t use_sdfgi : 1;
+ uint32_t use_multiview : 1;
+ uint32_t use_16_bit_shadows : 1;
+ uint32_t use_32_bit_shadows : 1;
+ uint32_t use_shadow_cubemaps : 1;
+ uint32_t use_shadow_dual_paraboloid : 1;
+ };
+
+ uint32_t key;
+ };
+ };
+
+ GlobalPipelineData global_pipeline_data_compiled = {};
+ GlobalPipelineData global_pipeline_data_required = {};
+
+ typedef Pair<SceneShaderForwardClustered::ShaderData *, SceneShaderForwardClustered::ShaderData::PipelineKey> ShaderPipelinePair;
+
+ void _update_global_pipeline_data_requirements_from_project();
+ void _update_global_pipeline_data_requirements_from_light_storage();
void _geometry_instance_add_surface_with_material(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, SceneShaderForwardClustered::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh);
void _geometry_instance_add_surface_with_material_chain(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, SceneShaderForwardClustered::MaterialData *p_material, RID p_mat_src, RID p_mesh);
void _geometry_instance_add_surface(GeometryInstanceForwardClustered *ginstance, uint32_t p_surface, RID p_material, RID p_mesh);
void _geometry_instance_update(RenderGeometryInstance *p_geometry_instance);
+ void _mesh_compile_pipeline_for_surface(SceneShaderForwardClustered::ShaderData *p_shader, void *p_mesh_surface, bool p_ubershader, bool p_instanced_surface, RS::PipelineSource p_source, SceneShaderForwardClustered::ShaderData::PipelineKey &r_pipeline_key, Vector<ShaderPipelinePair> *r_pipeline_pairs = nullptr);
+ void _mesh_compile_pipelines_for_surface(const SurfacePipelineData &p_surface, const GlobalPipelineData &p_global, RS::PipelineSource p_source, Vector<ShaderPipelinePair> *r_pipeline_pairs = nullptr);
+ void _mesh_generate_all_pipelines_for_surface_cache(GeometryInstanceSurfaceDataCache *p_surface_cache, const GlobalPipelineData &p_global);
void _update_dirty_geometry_instances();
+ void _update_dirty_geometry_pipelines();
/* Render List */
@@ -663,8 +720,15 @@ public:
virtual uint32_t geometry_instance_get_pair_mask() override;
+ /* PIPELINES */
+
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) override;
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override;
+
virtual bool free(RID p_rid) override;
+ virtual void update() override;
+
RenderForwardClustered();
~RenderForwardClustered();
};
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 6846c3f693..53982af590 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -41,9 +41,9 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
//compile
code = p_code;
- valid = false;
ubo_size = 0;
uniforms.clear();
+ _clear_vertex_input_mask_cache();
if (code.is_empty()) {
return; //just invalid, but no error
@@ -51,9 +51,9 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
ShaderCompiler::GeneratedCode gen_code;
- int blend_mode = BLEND_MODE_MIX;
- int depth_testi = DEPTH_TEST_ENABLED;
- int alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
+ blend_mode = BLEND_MODE_MIX;
+ depth_testi = DEPTH_TEST_ENABLED;
+ alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
int cull_modei = CULL_BACK;
uses_point_size = false;
@@ -66,8 +66,8 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
uses_roughness = false;
uses_normal = false;
uses_tangent = false;
- bool uses_normal_map = false;
- bool wireframe = false;
+ uses_normal_map = false;
+ wireframe = false;
unshaded = false;
uses_vertex = false;
@@ -90,7 +90,7 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MIX);
actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_mode, BLEND_MODE_SUB);
actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MUL);
- actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULT_ALPHA);
+ actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULTIPLIED_ALPHA);
actions.render_mode_values["alpha_to_coverage"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE);
actions.render_mode_values["alpha_to_coverage_and_one"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE);
@@ -141,12 +141,12 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
actions.uniforms = &uniforms;
- SceneShaderForwardClustered *shader_singleton = (SceneShaderForwardClustered *)SceneShaderForwardClustered::singleton;
- Error err = shader_singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code);
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ Error err = SceneShaderForwardClustered::singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code);
ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed.");
if (version.is_null()) {
- version = shader_singleton->shader.version_create();
+ version = SceneShaderForwardClustered::singleton->shader.version_create();
}
depth_draw = DepthDraw(depth_drawi);
@@ -178,95 +178,119 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]);
print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
- shader_singleton->shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
- ERR_FAIL_COND(!shader_singleton->shader.version_is_valid(version));
+ SceneShaderForwardClustered::singleton->shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
ubo_size = gen_code.uniform_total_size;
ubo_offsets = gen_code.uniform_offsets;
texture_uniforms = gen_code.texture_uniforms;
- //blend modes
+ pipeline_hash_map.clear_pipelines();
- // if any form of Alpha Antialiasing is enabled, set the blend mode to alpha to coverage
+ // If any form of Alpha Antialiasing is enabled, set the blend mode to alpha to coverage.
if (alpha_antialiasing_mode != ALPHA_ANTIALIASING_OFF) {
blend_mode = BLEND_MODE_ALPHA_TO_COVERAGE;
}
- RD::PipelineColorBlendState::Attachment blend_attachment;
+ uses_blend_alpha = blend_mode_uses_blend_alpha(BlendMode(blend_mode));
+}
- switch (blend_mode) {
- case BLEND_MODE_MIX: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+bool SceneShaderForwardClustered::ShaderData::is_animated() const {
+ return (uses_fragment_time && uses_discard) || (uses_vertex_time && uses_vertex);
+}
- } break;
- case BLEND_MODE_ADD: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- uses_blend_alpha = true; //force alpha used because of blend
+bool SceneShaderForwardClustered::ShaderData::casts_shadows() const {
+ bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
+ bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing)) || has_read_screen_alpha;
+ bool has_alpha = has_base_alpha || uses_blend_alpha;
- } break;
- case BLEND_MODE_SUB: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
- blend_attachment.color_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- uses_blend_alpha = true; //force alpha used because of blend
+ return !has_alpha || (uses_depth_prepass_alpha && !(depth_draw == DEPTH_DRAW_DISABLED || depth_test == DEPTH_TEST_DISABLED));
+}
+RS::ShaderNativeSourceCode SceneShaderForwardClustered::ShaderData::get_native_source_code() const {
+ if (version.is_valid()) {
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ return SceneShaderForwardClustered::singleton->shader.version_get_native_source_code(version);
+ } else {
+ return RS::ShaderNativeSourceCode();
+ }
+}
+
+SceneShaderForwardClustered::ShaderVersion SceneShaderForwardClustered::ShaderData::_get_shader_version(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) const {
+ uint32_t ubershader_base = p_ubershader ? SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL : 0;
+ switch (p_pipeline_version) {
+ case PIPELINE_VERSION_DEPTH_PASS:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_DP:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_DP + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_MULTIVIEW:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_MULTIVIEW + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW + ubershader_base);
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_MATERIAL:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL + SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL);
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_SDF:
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL + SHADER_VERSION_DEPTH_PASS_WITH_SDF);
+ case PIPELINE_VERSION_COLOR_PASS: {
+ int shader_flags = 0;
+
+ if (p_ubershader) {
+ shader_flags |= SHADER_COLOR_PASS_FLAG_UBERSHADER;
+ }
+
+ if (p_color_pass_flags & PIPELINE_COLOR_PASS_FLAG_SEPARATE_SPECULAR) {
+ shader_flags |= SHADER_COLOR_PASS_FLAG_SEPARATE_SPECULAR;
+ }
+
+ if (p_color_pass_flags & PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS) {
+ shader_flags |= SHADER_COLOR_PASS_FLAG_MOTION_VECTORS;
+ }
+
+ if (p_color_pass_flags & PIPELINE_COLOR_PASS_FLAG_LIGHTMAP) {
+ shader_flags |= SHADER_COLOR_PASS_FLAG_LIGHTMAP;
+ }
+
+ if (p_color_pass_flags & PIPELINE_COLOR_PASS_FLAG_MULTIVIEW) {
+ shader_flags |= SHADER_COLOR_PASS_FLAG_MULTIVIEW;
+ }
+
+ return ShaderVersion(SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL + SHADER_VERSION_COLOR_PASS + shader_flags);
} break;
- case BLEND_MODE_MUL: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_DST_COLOR;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ZERO;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_DST_ALPHA;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
- uses_blend_alpha = true; //force alpha used because of blend
- } break;
- case BLEND_MODE_ALPHA_TO_COVERAGE: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
- } break;
- case BLEND_MODE_PREMULT_ALPHA: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- uses_blend_alpha = true; // Force alpha used because of blend.
+ default: {
+ DEV_ASSERT(false && "Unknown pipeline version.");
+ return ShaderVersion(0);
} break;
}
+}
+
+void SceneShaderForwardClustered::ShaderData::_create_pipeline(PipelineKey p_pipeline_key) {
+#if PRINT_PIPELINE_COMPILATION_KEYS
+ print_line(
+ "HASH:", p_pipeline_key.hash(),
+ "VERSION:", version,
+ "VERTEX:", p_pipeline_key.vertex_format_id,
+ "FRAMEBUFFER:", p_pipeline_key.framebuffer_format_id,
+ "CULL:", p_pipeline_key.cull_mode,
+ "PRIMITIVE:", p_pipeline_key.primitive_type,
+ "VERSION:", p_pipeline_key.version,
+ "PASS FLAGS:", p_pipeline_key.color_pass_flags,
+ "SPEC PACKED #0:", p_pipeline_key.shader_specialization.packed_0,
+ "WIREFRAME:", p_pipeline_key.wireframe);
+#endif
// Color pass -> attachment 0: Color/Diffuse, attachment 1: Separate Specular, attachment 2: Motion Vectors
+ RD::PipelineColorBlendState::Attachment blend_attachment = blend_mode_to_blend_attachment(BlendMode(blend_mode));
RD::PipelineColorBlendState blend_state_color_blend;
blend_state_color_blend.attachments = { blend_attachment, RD::PipelineColorBlendState::Attachment(), RD::PipelineColorBlendState::Attachment() };
RD::PipelineColorBlendState blend_state_color_opaque = RD::PipelineColorBlendState::create_disabled(3);
RD::PipelineColorBlendState blend_state_depth_normal_roughness = RD::PipelineColorBlendState::create_disabled(1);
RD::PipelineColorBlendState blend_state_depth_normal_roughness_giprobe = RD::PipelineColorBlendState::create_disabled(2);
- //update pipelines
-
RD::PipelineDepthStencilState depth_stencil_state;
if (depth_test != DEPTH_TEST_DISABLED) {
@@ -276,171 +300,162 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) {
}
bool depth_pre_pass_enabled = bool(GLOBAL_GET("rendering/driver/depth_prepass/enable"));
- for (int i = 0; i < CULL_VARIANT_MAX; i++) {
- RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
- { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
- { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_BACK, RD::POLYGON_CULL_FRONT },
- { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED }
- };
+ RD::RenderPrimitive primitive_rd_table[RS::PRIMITIVE_MAX] = {
+ RD::RENDER_PRIMITIVE_POINTS,
+ RD::RENDER_PRIMITIVE_LINES,
+ RD::RENDER_PRIMITIVE_LINESTRIPS,
+ RD::RENDER_PRIMITIVE_TRIANGLES,
+ RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
+ };
+
+ RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[p_pipeline_key.primitive_type];
+
+ RD::PipelineRasterizationState raster_state;
+ raster_state.cull_mode = p_pipeline_key.cull_mode;
+ raster_state.wireframe = wireframe || p_pipeline_key.wireframe;
+
+ RD::PipelineMultisampleState multisample_state;
+ multisample_state.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_pipeline_key.framebuffer_format_id, 0);
+
+ RD::PipelineColorBlendState blend_state;
+ if (p_pipeline_key.version == PIPELINE_VERSION_COLOR_PASS) {
+ if (p_pipeline_key.color_pass_flags & PIPELINE_COLOR_PASS_FLAG_TRANSPARENT) {
+ if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE) {
+ multisample_state.enable_alpha_to_coverage = true;
+ } else if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE) {
+ multisample_state.enable_alpha_to_coverage = true;
+ multisample_state.enable_alpha_to_one = true;
+ }
- RD::PolygonCullMode cull_mode_rd = cull_mode_rd_table[i][cull_mode];
-
- for (int j = 0; j < RS::PRIMITIVE_MAX; j++) {
- RD::RenderPrimitive primitive_rd_table[RS::PRIMITIVE_MAX] = {
- RD::RENDER_PRIMITIVE_POINTS,
- RD::RENDER_PRIMITIVE_LINES,
- RD::RENDER_PRIMITIVE_LINESTRIPS,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
- };
-
- RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[j];
-
- for (int k = 0; k < PIPELINE_VERSION_MAX; k++) {
- ShaderVersion shader_version;
- static const ShaderVersion shader_version_table[PIPELINE_VERSION_MAX] = {
- SHADER_VERSION_DEPTH_PASS,
- SHADER_VERSION_DEPTH_PASS_DP,
- SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS,
- SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI,
- SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL,
- SHADER_VERSION_DEPTH_PASS_WITH_SDF,
- SHADER_VERSION_DEPTH_PASS_MULTIVIEW,
- SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW,
- SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW,
- SHADER_VERSION_COLOR_PASS,
- };
-
- shader_version = shader_version_table[k];
-
- if (!static_cast<SceneShaderForwardClustered *>(singleton)->shader.is_variant_enabled(shader_version)) {
- continue;
- }
- RD::PipelineRasterizationState raster_state;
- raster_state.cull_mode = cull_mode_rd;
- raster_state.wireframe = wireframe;
-
- if (k == PIPELINE_VERSION_COLOR_PASS) {
- for (int l = 0; l < PIPELINE_COLOR_PASS_FLAG_COUNT; l++) {
- if (!shader_singleton->valid_color_pass_pipelines[l]) {
- continue;
- }
-
- RD::PipelineDepthStencilState depth_stencil = depth_stencil_state;
-
- RD::PipelineColorBlendState blend_state;
- RD::PipelineMultisampleState multisample_state;
-
- int shader_flags = 0;
- if (l & PIPELINE_COLOR_PASS_FLAG_TRANSPARENT) {
- if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE) {
- multisample_state.enable_alpha_to_coverage = true;
- } else if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE) {
- multisample_state.enable_alpha_to_coverage = true;
- multisample_state.enable_alpha_to_one = true;
- }
-
- blend_state = blend_state_color_blend;
-
- if (depth_draw == DEPTH_DRAW_OPAQUE) {
- depth_stencil.enable_depth_write = false; //alpha does not draw depth
- }
- } else {
- blend_state = blend_state_color_opaque;
-
- if (depth_pre_pass_enabled) {
- // We already have a depth from the depth pre-pass, there is no need to write it again.
- // In addition we can use COMPARE_OP_EQUAL instead of COMPARE_OP_LESS_OR_EQUAL.
- // This way we can use the early depth test to discard transparent fragments before the fragment shader even starts.
- depth_stencil.depth_compare_operator = RD::COMPARE_OP_EQUAL;
- depth_stencil.enable_depth_write = false;
- }
-
- if (l & PIPELINE_COLOR_PASS_FLAG_SEPARATE_SPECULAR) {
- shader_flags |= SHADER_COLOR_PASS_FLAG_SEPARATE_SPECULAR;
- }
- }
-
- if (l & PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS) {
- shader_flags |= SHADER_COLOR_PASS_FLAG_MOTION_VECTORS;
- }
-
- if (l & PIPELINE_COLOR_PASS_FLAG_LIGHTMAP) {
- shader_flags |= SHADER_COLOR_PASS_FLAG_LIGHTMAP;
- }
-
- if (l & PIPELINE_COLOR_PASS_FLAG_MULTIVIEW) {
- shader_flags |= SHADER_COLOR_PASS_FLAG_MULTIVIEW;
- }
-
- int variant = shader_version + shader_flags;
-
- if (!static_cast<SceneShaderForwardClustered *>(singleton)->shader.is_variant_enabled(variant)) {
- continue;
- }
-
- RID shader_variant = shader_singleton->shader.version_get_shader(version, variant);
- color_pipelines[i][j][l].setup(shader_variant, primitive_rd, raster_state, multisample_state, depth_stencil, blend_state, 0, singleton->default_specialization_constants);
- }
- } else {
- RD::PipelineColorBlendState blend_state;
- RD::PipelineDepthStencilState depth_stencil = depth_stencil_state;
- RD::PipelineMultisampleState multisample_state;
-
- if (k == PIPELINE_VERSION_DEPTH_PASS || k == PIPELINE_VERSION_DEPTH_PASS_DP || k == PIPELINE_VERSION_DEPTH_PASS_MULTIVIEW) {
- //none, leave empty
- } else if (k == PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS || k == PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW) {
- blend_state = blend_state_depth_normal_roughness;
- } else if (k == PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI || k == PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW) {
- blend_state = blend_state_depth_normal_roughness_giprobe;
- } else if (k == PIPELINE_VERSION_DEPTH_PASS_WITH_MATERIAL) {
- blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
- } else if (k == PIPELINE_VERSION_DEPTH_PASS_WITH_SDF) {
- blend_state = RD::PipelineColorBlendState(); //no color targets for SDF
- }
-
- RID shader_variant = shader_singleton->shader.version_get_shader(version, shader_version);
- pipelines[i][j][k].setup(shader_variant, primitive_rd, raster_state, multisample_state, depth_stencil, blend_state, 0, singleton->default_specialization_constants);
- }
+ blend_state = blend_state_color_blend;
+
+ if (depth_draw == DEPTH_DRAW_OPAQUE) {
+ depth_stencil_state.enable_depth_write = false; //alpha does not draw depth
+ }
+ } else {
+ blend_state = blend_state_color_opaque;
+
+ if (depth_pre_pass_enabled) {
+ // We already have a depth from the depth pre-pass, there is no need to write it again.
+ // In addition we can use COMPARE_OP_EQUAL instead of COMPARE_OP_LESS_OR_EQUAL.
+ // This way we can use the early depth test to discard transparent fragments before the fragment shader even starts.
+ depth_stencil_state.depth_compare_operator = RD::COMPARE_OP_EQUAL;
+ depth_stencil_state.enable_depth_write = false;
}
}
+ } else {
+ switch (p_pipeline_key.version) {
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS:
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW:
+ blend_state = blend_state_depth_normal_roughness;
+ break;
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI:
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW:
+ blend_state = blend_state_depth_normal_roughness_giprobe;
+ break;
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_MATERIAL:
+ // Writes to normal and roughness in opaque way.
+ blend_state = RD::PipelineColorBlendState::create_disabled(5);
+ break;
+ case PIPELINE_VERSION_DEPTH_PASS:
+ case PIPELINE_VERSION_DEPTH_PASS_DP:
+ case PIPELINE_VERSION_DEPTH_PASS_MULTIVIEW:
+ case PIPELINE_VERSION_DEPTH_PASS_WITH_SDF:
+ default:
+ break;
+ }
}
- valid = true;
+ // Convert the specialization from the key to pipeline specialization constants.
+ Vector<RD::PipelineSpecializationConstant> specialization_constants;
+ RD::PipelineSpecializationConstant sc;
+ sc.constant_id = 0;
+ sc.int_value = p_pipeline_key.shader_specialization.packed_0;
+ sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
+ specialization_constants.push_back(sc);
+
+ RID shader_rid = get_shader_variant(p_pipeline_key.version, p_pipeline_key.color_pass_flags, p_pipeline_key.ubershader);
+ ERR_FAIL_COND(shader_rid.is_null());
+
+ RID pipeline = RD::get_singleton()->render_pipeline_create(shader_rid, p_pipeline_key.framebuffer_format_id, p_pipeline_key.vertex_format_id, primitive_rd, raster_state, multisample_state, depth_stencil_state, blend_state, 0, 0, specialization_constants);
+ ERR_FAIL_COND(pipeline.is_null());
+
+ pipeline_hash_map.add_compiled_pipeline(p_pipeline_key.hash(), pipeline);
}
-bool SceneShaderForwardClustered::ShaderData::is_animated() const {
- return (uses_fragment_time && uses_discard) || (uses_vertex_time && uses_vertex);
+RD::PolygonCullMode SceneShaderForwardClustered::ShaderData::get_cull_mode_from_cull_variant(CullVariant p_cull_variant) {
+ const RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
+ { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
+ { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_BACK, RD::POLYGON_CULL_FRONT },
+ { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED }
+ };
+
+ return cull_mode_rd_table[p_cull_variant][cull_mode];
}
-bool SceneShaderForwardClustered::ShaderData::casts_shadows() const {
- bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
- bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing)) || has_read_screen_alpha;
- bool has_alpha = has_base_alpha || uses_blend_alpha;
+RID SceneShaderForwardClustered::ShaderData::_get_shader_variant(ShaderVersion p_shader_version) const {
+ if (version.is_valid()) {
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ ERR_FAIL_NULL_V(SceneShaderForwardClustered::singleton, RID());
+ return SceneShaderForwardClustered::singleton->shader.version_get_shader(version, p_shader_version);
+ } else {
+ return RID();
+ }
+}
- return !has_alpha || (uses_depth_prepass_alpha && !(depth_draw == DEPTH_DRAW_DISABLED || depth_test == DEPTH_TEST_DISABLED));
+void SceneShaderForwardClustered::ShaderData::_clear_vertex_input_mask_cache() {
+ for (uint32_t i = 0; i < VERTEX_INPUT_MASKS_SIZE; i++) {
+ vertex_input_masks[i].store(0);
+ }
}
-RS::ShaderNativeSourceCode SceneShaderForwardClustered::ShaderData::get_native_source_code() const {
- SceneShaderForwardClustered *shader_singleton = (SceneShaderForwardClustered *)SceneShaderForwardClustered::singleton;
+RID SceneShaderForwardClustered::ShaderData::get_shader_variant(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) const {
+ return _get_shader_variant(_get_shader_version(p_pipeline_version, p_color_pass_flags, p_ubershader));
+}
+
+uint64_t SceneShaderForwardClustered::ShaderData::get_vertex_input_mask(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) {
+ // Vertex input masks require knowledge of the shader. Since querying the shader can be expensive due to high contention and the necessary mutex, we cache the result instead.
+ ShaderVersion shader_version = _get_shader_version(p_pipeline_version, p_color_pass_flags, p_ubershader);
+ uint64_t input_mask = vertex_input_masks[shader_version].load(std::memory_order_relaxed);
+ if (input_mask == 0) {
+ RID shader_rid = _get_shader_variant(shader_version);
+ ERR_FAIL_COND_V(shader_rid.is_null(), 0);
- return shader_singleton->shader.version_get_native_source_code(version);
+ input_mask = RD::get_singleton()->shader_get_vertex_input_attribute_mask(shader_rid);
+ vertex_input_masks[shader_version].store(input_mask, std::memory_order_relaxed);
+ }
+
+ return input_mask;
+}
+
+bool SceneShaderForwardClustered::ShaderData::is_valid() const {
+ if (version.is_valid()) {
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ ERR_FAIL_NULL_V(SceneShaderForwardClustered::singleton, false);
+ return SceneShaderForwardClustered::singleton->shader.version_is_valid(version);
+ } else {
+ return false;
+ }
}
SceneShaderForwardClustered::ShaderData::ShaderData() :
shader_list_element(this) {
+ pipeline_hash_map.set_creation_object_and_function(this, &ShaderData::_create_pipeline);
+ pipeline_hash_map.set_compilations(SceneShaderForwardClustered::singleton->pipeline_compilations, &SceneShaderForwardClustered::singleton_mutex);
}
SceneShaderForwardClustered::ShaderData::~ShaderData() {
- SceneShaderForwardClustered *shader_singleton = (SceneShaderForwardClustered *)SceneShaderForwardClustered::singleton;
- ERR_FAIL_NULL(shader_singleton);
- //pipeline variants will clear themselves if shader is gone
+ pipeline_hash_map.clear_pipelines();
+
if (version.is_valid()) {
- shader_singleton->shader.version_free(version);
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ ERR_FAIL_NULL(SceneShaderForwardClustered::singleton);
+ SceneShaderForwardClustered::singleton->shader.version_free(version);
}
}
RendererRD::MaterialStorage::ShaderData *SceneShaderForwardClustered::_create_shader_func() {
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
ShaderData *shader_data = memnew(ShaderData);
singleton->shader_list.add(&shader_data->shader_list_element);
return shader_data;
@@ -455,9 +470,12 @@ void SceneShaderForwardClustered::MaterialData::set_next_pass(RID p_pass) {
}
bool SceneShaderForwardClustered::MaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
- SceneShaderForwardClustered *shader_singleton = (SceneShaderForwardClustered *)SceneShaderForwardClustered::singleton;
-
- return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardClustered::MATERIAL_UNIFORM_SET, true, true);
+ if (shader_data->version.is_valid()) {
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, SceneShaderForwardClustered::singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardClustered::MATERIAL_UNIFORM_SET, true, true);
+ } else {
+ return false;
+ }
}
SceneShaderForwardClustered::MaterialData::~MaterialData() {
@@ -472,6 +490,7 @@ RendererRD::MaterialStorage::MaterialData *SceneShaderForwardClustered::_create_
}
SceneShaderForwardClustered *SceneShaderForwardClustered::singleton = nullptr;
+Mutex SceneShaderForwardClustered::singleton_mutex;
SceneShaderForwardClustered::SceneShaderForwardClustered() {
// there should be only one of these, contained within our RenderFM singleton.
@@ -498,17 +517,22 @@ void SceneShaderForwardClustered::init(const String p_defines) {
{
Vector<ShaderRD::VariantDefine> shader_versions;
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_BASE, "\n#define MODE_RENDER_DEPTH\n", true)); // SHADER_VERSION_DEPTH_PASS
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_BASE, "\n#define MODE_RENDER_DEPTH\n#define MODE_DUAL_PARABOLOID\n", true)); // SHADER_VERSION_DEPTH_PASS_DP
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_BASE, "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n", true)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_ADVANCED, "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n#define MODE_RENDER_VOXEL_GI\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI
+ for (uint32_t ubershader = 0; ubershader < 2; ubershader++) {
+ const String base_define = ubershader ? "\n#define UBERSHADER\n" : "";
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_BASE, base_define + "\n#define MODE_RENDER_DEPTH\n", true)); // SHADER_VERSION_DEPTH_PASS
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_BASE, base_define + "\n#define MODE_RENDER_DEPTH\n#define MODE_DUAL_PARABOLOID\n", true)); // SHADER_VERSION_DEPTH_PASS_DP
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_BASE, base_define + "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n", true)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_ADVANCED, base_define + "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n#define MODE_RENDER_VOXEL_GI\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_MULTIVIEW, base_define + "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n", false)); // SHADER_VERSION_DEPTH_PASS_MULTIVIEW
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_MULTIVIEW, base_define + "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW
+ shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_ADVANCED_MULTIVIEW, base_define + "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n#define MODE_RENDER_VOXEL_GI\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW
+ }
+
shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_ADVANCED, "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_MATERIAL\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL
shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_ADVANCED, "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_SDF\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_SDF
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_MULTIVIEW, "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n", false)); // SHADER_VERSION_DEPTH_PASS_MULTIVIEW
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_MULTIVIEW, "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW
- shader_versions.push_back(ShaderRD::VariantDefine(SHADER_GROUP_MULTIVIEW, "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_NORMAL_ROUGHNESS\n#define MODE_RENDER_VOXEL_GI\n", false)); // SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW
Vector<String> color_pass_flags = {
+ "\n#define UBERSHADER\n", // SHADER_COLOR_PASS_FLAG_UBERSHADER
"\n#define MODE_SEPARATE_SPECULAR\n", // SHADER_COLOR_PASS_FLAG_SEPARATE_SPECULAR
"\n#define USE_LIGHTMAP\n", // SHADER_COLOR_PASS_FLAG_LIGHTMAP
"\n#define USE_MULTIVIEW\n", // SHADER_COLOR_PASS_FLAG_MULTIVIEW
@@ -545,16 +569,6 @@ void SceneShaderForwardClustered::init(const String p_defines) {
}
}
- // Set flag to true if a combination is valid.
- // The only invalid combinations are those that include both TRANSPARENT and SEPARATE_SPECULAR.
- for (int i = 0; i < PIPELINE_COLOR_PASS_FLAG_COUNT; i++) {
- if ((i & PIPELINE_COLOR_PASS_FLAG_TRANSPARENT) && (i & PIPELINE_COLOR_PASS_FLAG_SEPARATE_SPECULAR)) {
- valid_color_pass_pipelines[i] = false;
- } else {
- valid_color_pass_pipelines[i] = true;
- }
- }
-
material_storage->shader_set_data_request_function(RendererRD::MaterialStorage::SHADER_TYPE_3D, _create_shader_funcs);
material_storage->material_set_data_request_function(RendererRD::MaterialStorage::SHADER_TYPE_3D, _create_material_funcs);
@@ -730,13 +744,20 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
actions.render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";
actions.render_mode_defines["unshaded"] = "#define MODE_UNSHADED\n";
+
+ bool force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
+ if (!force_vertex_shading) {
+ // If forcing vertex shading, this will be defined already.
+ actions.render_mode_defines["vertex_lighting"] = "#define USE_VERTEX_LIGHTING\n";
+ }
+
actions.render_mode_defines["debug_shadow_splits"] = "#define DEBUG_DRAW_PSSM_SPLITS\n";
actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";
actions.base_texture_binding_index = 1;
actions.texture_layout_set = RenderForwardClustered::MATERIAL_UNIFORM_SET;
actions.base_uniform_string = "material.";
- actions.base_varying_index = 12;
+ actions.base_varying_index = 14;
actions.default_filter = ShaderLanguage::FILTER_LINEAR_MIPMAP;
actions.default_repeat = ShaderLanguage::REPEAT_ENABLE;
@@ -772,8 +793,8 @@ void fragment() {
material_storage->material_set_shader(default_material, default_shader);
MaterialData *md = static_cast<MaterialData *>(material_storage->material_get_data(default_material, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- default_shader_rd = shader.version_get_shader(md->shader_data->version, SHADER_VERSION_COLOR_PASS);
- default_shader_sdfgi_rd = shader.version_get_shader(md->shader_data->version, SHADER_VERSION_DEPTH_PASS_WITH_SDF);
+ default_shader_rd = md->shader_data->get_shader_variant(PIPELINE_VERSION_COLOR_PASS, 0, false);
+ default_shader_sdfgi_rd = md->shader_data->get_shader_variant(PIPELINE_VERSION_DEPTH_PASS_WITH_SDF, 0, false);
default_material_shader_ptr = md->shader_data;
default_material_uniform_set = md->uniform_set;
@@ -848,19 +869,11 @@ void fragment() {
}
}
-void SceneShaderForwardClustered::set_default_specialization_constants(const Vector<RD::PipelineSpecializationConstant> &p_constants) {
- default_specialization_constants = p_constants;
+void SceneShaderForwardClustered::set_default_specialization(const ShaderSpecialization &p_specialization) {
+ default_specialization = p_specialization;
+
for (SelfList<ShaderData> *E = shader_list.first(); E; E = E->next()) {
- for (int i = 0; i < ShaderData::CULL_VARIANT_MAX; i++) {
- for (int j = 0; j < RS::PRIMITIVE_MAX; j++) {
- for (int k = 0; k < SHADER_VERSION_MAX; k++) {
- E->self()->pipelines[i][j][k].update_specialization_constants(default_specialization_constants);
- }
- for (int k = 0; k < PIPELINE_COLOR_PASS_FLAG_COUNT; k++) {
- E->self()->color_pipelines[i][j][k].update_specialization_constants(default_specialization_constants);
- }
- }
- }
+ E->self()->pipeline_hash_map.clear_pipelines();
}
}
@@ -870,3 +883,20 @@ void SceneShaderForwardClustered::enable_advanced_shader_group(bool p_needs_mult
}
shader.enable_group(SHADER_GROUP_ADVANCED);
}
+
+bool SceneShaderForwardClustered::is_multiview_shader_group_enabled() const {
+ return shader.is_group_enabled(SHADER_GROUP_MULTIVIEW);
+}
+
+bool SceneShaderForwardClustered::is_advanced_shader_group_enabled(bool p_multiview) const {
+ if (p_multiview) {
+ return shader.is_group_enabled(SHADER_GROUP_ADVANCED_MULTIVIEW);
+ } else {
+ return shader.is_group_enabled(SHADER_GROUP_ADVANCED);
+ }
+}
+
+uint32_t SceneShaderForwardClustered::get_pipeline_compilations(RS::PipelineSource p_source) {
+ MutexLock lock(SceneShaderForwardClustered::singleton_mutex);
+ return pipeline_compilations[p_source];
+}
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
index d5332032f9..136514588a 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
@@ -31,6 +31,7 @@
#ifndef SCENE_SHADER_FORWARD_CLUSTERED_H
#define SCENE_SHADER_FORWARD_CLUSTERED_H
+#include "servers/rendering/renderer_rd/pipeline_hash_map_rd.h"
#include "servers/rendering/renderer_rd/renderer_scene_render_rd.h"
#include "servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl.gen.h"
@@ -39,6 +40,7 @@ namespace RendererSceneRenderImplementation {
class SceneShaderForwardClustered {
private:
static SceneShaderForwardClustered *singleton;
+ static Mutex singleton_mutex;
public:
enum ShaderGroup {
@@ -53,21 +55,22 @@ public:
SHADER_VERSION_DEPTH_PASS_DP,
SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS,
SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI,
- SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL,
- SHADER_VERSION_DEPTH_PASS_WITH_SDF,
SHADER_VERSION_DEPTH_PASS_MULTIVIEW,
SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_MULTIVIEW,
SHADER_VERSION_DEPTH_PASS_WITH_NORMAL_AND_ROUGHNESS_AND_VOXEL_GI_MULTIVIEW,
+ SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL,
+ SHADER_VERSION_DEPTH_PASS_WITH_SDF,
SHADER_VERSION_COLOR_PASS,
SHADER_VERSION_MAX
};
enum ShaderColorPassFlags {
- SHADER_COLOR_PASS_FLAG_SEPARATE_SPECULAR = 1 << 0,
- SHADER_COLOR_PASS_FLAG_LIGHTMAP = 1 << 1,
- SHADER_COLOR_PASS_FLAG_MULTIVIEW = 1 << 2,
- SHADER_COLOR_PASS_FLAG_MOTION_VECTORS = 1 << 3,
- SHADER_COLOR_PASS_FLAG_COUNT = 1 << 4
+ SHADER_COLOR_PASS_FLAG_UBERSHADER = 1 << 0,
+ SHADER_COLOR_PASS_FLAG_SEPARATE_SPECULAR = 1 << 1,
+ SHADER_COLOR_PASS_FLAG_LIGHTMAP = 1 << 2,
+ SHADER_COLOR_PASS_FLAG_MULTIVIEW = 1 << 3,
+ SHADER_COLOR_PASS_FLAG_MOTION_VECTORS = 1 << 4,
+ SHADER_COLOR_PASS_FLAG_COUNT = 1 << 5
};
enum PipelineVersion {
@@ -90,26 +93,45 @@ public:
PIPELINE_COLOR_PASS_FLAG_LIGHTMAP = 1 << 2,
PIPELINE_COLOR_PASS_FLAG_MULTIVIEW = 1 << 3,
PIPELINE_COLOR_PASS_FLAG_MOTION_VECTORS = 1 << 4,
- PIPELINE_COLOR_PASS_FLAG_COUNT = 1 << 5,
+ PIPELINE_COLOR_PASS_FLAG_OPTIONS = 5,
+ PIPELINE_COLOR_PASS_FLAG_COMBINATIONS = 1 << PIPELINE_COLOR_PASS_FLAG_OPTIONS,
};
- enum ShaderSpecializations {
- SHADER_SPECIALIZATION_FORWARD_GI = 1 << 0,
- SHADER_SPECIALIZATION_PROJECTOR = 1 << 1,
- SHADER_SPECIALIZATION_SOFT_SHADOWS = 1 << 2,
- SHADER_SPECIALIZATION_DIRECTIONAL_SOFT_SHADOWS = 1 << 3,
+ struct ShaderSpecialization {
+ union {
+ struct {
+ uint32_t use_forward_gi : 1;
+ uint32_t use_light_projector : 1;
+ uint32_t use_light_soft_shadows : 1;
+ uint32_t use_directional_soft_shadows : 1;
+ uint32_t decal_use_mipmaps : 1;
+ uint32_t projector_use_mipmaps : 1;
+ uint32_t use_depth_fog : 1;
+ uint32_t use_lightmap_bicubic_filter : 1;
+ uint32_t soft_shadow_samples : 4;
+ uint32_t penumbra_shadow_samples : 4;
+ uint32_t directional_soft_shadow_samples : 4;
+ uint32_t directional_penumbra_shadow_samples : 4;
+ };
+
+ uint32_t packed_0;
+ };
+
+ uint32_t packed_1;
+ uint32_t packed_2;
};
- struct ShaderData : public RendererRD::MaterialStorage::ShaderData {
- enum BlendMode { //used internally
- BLEND_MODE_MIX,
- BLEND_MODE_ADD,
- BLEND_MODE_SUB,
- BLEND_MODE_MUL,
- BLEND_MODE_ALPHA_TO_COVERAGE,
- BLEND_MODE_PREMULT_ALPHA,
+ struct UbershaderConstants {
+ union {
+ struct {
+ uint32_t cull_mode : 2;
+ };
+
+ uint32_t packed_0;
};
+ };
+ struct ShaderData : public RendererRD::MaterialStorage::ShaderData {
enum DepthDraw {
DEPTH_DRAW_DISABLED,
DEPTH_DRAW_OPAQUE,
@@ -141,11 +163,40 @@ public:
ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE
};
- bool valid = false;
+ struct PipelineKey {
+ RD::VertexFormatID vertex_format_id;
+ RD::FramebufferFormatID framebuffer_format_id;
+ RD::PolygonCullMode cull_mode = RD::POLYGON_CULL_MAX;
+ RS::PrimitiveType primitive_type = RS::PRIMITIVE_MAX;
+ PipelineVersion version = PipelineVersion::PIPELINE_VERSION_MAX;
+ uint32_t color_pass_flags = 0;
+ ShaderSpecialization shader_specialization = {};
+ uint32_t wireframe = false;
+ uint32_t ubershader = false;
+
+ uint32_t hash() const {
+ uint32_t h = hash_murmur3_one_64(vertex_format_id);
+ h = hash_murmur3_one_32(framebuffer_format_id, h);
+ h = hash_murmur3_one_32(cull_mode, h);
+ h = hash_murmur3_one_32(primitive_type, h);
+ h = hash_murmur3_one_32(version, h);
+ h = hash_murmur3_one_32(color_pass_flags, h);
+ h = hash_murmur3_one_32(shader_specialization.packed_0, h);
+ h = hash_murmur3_one_32(shader_specialization.packed_1, h);
+ h = hash_murmur3_one_32(shader_specialization.packed_2, h);
+ h = hash_murmur3_one_32(wireframe, h);
+ h = hash_murmur3_one_32(ubershader, h);
+ return hash_fmix32(h);
+ }
+ };
+
+ void _create_pipeline(PipelineKey p_pipeline_key);
+ PipelineHashMapRD<PipelineKey, ShaderData, void (ShaderData::*)(PipelineKey)> pipeline_hash_map;
+
RID version;
- uint64_t vertex_input_mask = 0;
- PipelineCacheRD pipelines[CULL_VARIANT_MAX][RS::PRIMITIVE_MAX][PIPELINE_VERSION_MAX];
- PipelineCacheRD color_pipelines[CULL_VARIANT_MAX][RS::PRIMITIVE_MAX][PIPELINE_COLOR_PASS_FLAG_COUNT];
+
+ static const uint32_t VERTEX_INPUT_MASKS_SIZE = SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL + SHADER_VERSION_COLOR_PASS + SHADER_COLOR_PASS_FLAG_COUNT;
+ std::atomic<uint64_t> vertex_input_masks[VERTEX_INPUT_MASKS_SIZE] = {};
Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;
@@ -157,6 +208,10 @@ public:
DepthDraw depth_draw = DEPTH_DRAW_OPAQUE;
DepthTest depth_test = DEPTH_TEST_ENABLED;
+ int blend_mode = BLEND_MODE_MIX;
+ int depth_testi = DEPTH_TEST_ENABLED;
+ int alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
+
bool uses_point_size = false;
bool uses_alpha = false;
bool uses_blend_alpha = false;
@@ -168,6 +223,8 @@ public:
bool uses_normal = false;
bool uses_tangent = false;
bool uses_particle_trails = false;
+ bool uses_normal_map = false;
+ bool wireframe = false;
bool unshaded = false;
bool uses_vertex = false;
@@ -188,11 +245,39 @@ public:
uint64_t last_pass = 0;
uint32_t index = 0;
+ _FORCE_INLINE_ bool uses_alpha_pass() const {
+ bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
+ bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing)) || has_read_screen_alpha;
+ bool has_blend_alpha = uses_blend_alpha;
+ bool has_alpha = has_base_alpha || has_blend_alpha;
+ bool no_depth_draw = depth_draw == DEPTH_DRAW_DISABLED;
+ bool no_depth_test = depth_test == DEPTH_TEST_DISABLED;
+ return has_alpha || has_read_screen_alpha || no_depth_draw || no_depth_test;
+ }
+
+ _FORCE_INLINE_ bool uses_depth_in_alpha_pass() const {
+ bool no_depth_draw = depth_draw == DEPTH_DRAW_DISABLED;
+ bool no_depth_test = depth_test == DEPTH_TEST_DISABLED;
+ return (uses_depth_prepass_alpha || uses_alpha_antialiasing) && !(no_depth_draw || no_depth_test);
+ }
+
+ _FORCE_INLINE_ bool uses_shared_shadow_material() const {
+ bool backface_culling = cull_mode == CULL_BACK;
+ return !uses_particle_trails && !writes_modelview_or_projection && !uses_vertex && !uses_position && !uses_discard && !uses_depth_prepass_alpha && !uses_alpha_clip && !uses_alpha_antialiasing && backface_culling && !uses_point_size && !uses_world_coordinates;
+ }
+
virtual void set_code(const String &p_Code);
virtual bool is_animated() const;
virtual bool casts_shadows() const;
virtual RS::ShaderNativeSourceCode get_native_source_code() const;
+ ShaderVersion _get_shader_version(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) const;
+ RID _get_shader_variant(ShaderVersion p_shader_version) const;
+ void _clear_vertex_input_mask_cache();
+ RID get_shader_variant(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) const;
+ uint64_t get_vertex_input_mask(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader);
+ RD::PolygonCullMode get_cull_mode_from_cull_variant(CullVariant p_cull_variant);
+ bool is_valid() const;
SelfList<ShaderData> shader_list_element;
ShaderData();
@@ -250,14 +335,19 @@ public:
RID debug_shadow_splits_material_uniform_set;
ShaderData *debug_shadow_splits_material_shader_ptr = nullptr;
- Vector<RD::PipelineSpecializationConstant> default_specialization_constants;
- bool valid_color_pass_pipelines[PIPELINE_COLOR_PASS_FLAG_COUNT];
+ ShaderSpecialization default_specialization = {};
+
+ uint32_t pipeline_compilations[RS::PIPELINE_SOURCE_MAX] = {};
+
SceneShaderForwardClustered();
~SceneShaderForwardClustered();
void init(const String p_defines);
- void set_default_specialization_constants(const Vector<RD::PipelineSpecializationConstant> &p_constants);
+ void set_default_specialization(const ShaderSpecialization &p_specialization);
void enable_advanced_shader_group(bool p_needs_multiview = false);
+ bool is_multiview_shader_group_enabled() const;
+ bool is_advanced_shader_group_enabled(bool p_multiview) const;
+ uint32_t get_pipeline_compilations(RS::PipelineSource p_source);
};
} // namespace RendererSceneRenderImplementation
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 8a02ec0eb5..978ce097d3 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -38,6 +38,8 @@
#include "servers/rendering/rendering_device.h"
#include "servers/rendering/rendering_server_default.h"
+#define PRELOAD_PIPELINES_ON_SURFACE_CACHE_CONSTRUCTION 1
+
using namespace RendererSceneRenderImplementation;
RendererRD::ForwardID RenderForwardMobile::ForwardIDStorageMobile::allocate_forward_id(RendererRD::ForwardIDType p_type) {
@@ -277,6 +279,66 @@ void RenderForwardMobile::setup_render_buffer_data(Ref<RenderSceneBuffersRD> p_r
p_render_buffers->set_custom_data(RB_SCOPE_MOBILE, data);
}
+void RenderForwardMobile::mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) {
+ RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ RID shadow_mesh = mesh_storage->mesh_get_shadow_mesh(p_mesh);
+ uint32_t surface_count = 0;
+ const RID *materials = mesh_storage->mesh_get_surface_count_and_materials(p_mesh, surface_count);
+ Vector<ShaderPipelinePair> pipeline_pairs;
+ for (uint32_t i = 0; i < surface_count; i++) {
+ if (materials[i].is_null()) {
+ continue;
+ }
+
+ void *mesh_surface = mesh_storage->mesh_get_surface(p_mesh, i);
+ void *mesh_surface_shadow = mesh_surface;
+ SceneShaderForwardMobile::MaterialData *material = static_cast<SceneShaderForwardMobile::MaterialData *>(material_storage->material_get_data(materials[i], RendererRD::MaterialStorage::SHADER_TYPE_3D));
+ if (material == nullptr) {
+ continue;
+ }
+
+ SceneShaderForwardMobile::ShaderData *shader = material->shader_data;
+ SceneShaderForwardMobile::ShaderData *shader_shadow = shader;
+ if (material->shader_data->uses_shared_shadow_material()) {
+ SceneShaderForwardMobile::MaterialData *material_shadow = static_cast<SceneShaderForwardMobile::MaterialData *>(material_storage->material_get_data(scene_shader.default_material, RendererRD::MaterialStorage::SHADER_TYPE_3D));
+ if (material_shadow != nullptr) {
+ shader_shadow = material_shadow->shader_data;
+ if (shadow_mesh.is_valid()) {
+ mesh_surface_shadow = mesh_storage->mesh_get_surface(shadow_mesh, i);
+ }
+ }
+ }
+
+ if (!shader->is_valid()) {
+ continue;
+ }
+
+ SurfacePipelineData surface;
+ surface.mesh_surface = mesh_surface;
+ surface.mesh_surface_shadow = mesh_surface_shadow;
+ surface.shader = shader;
+ surface.shader_shadow = shader_shadow;
+ surface.instanced = mesh_storage->mesh_needs_instance(p_mesh, true);
+ surface.uses_opaque = !material->shader_data->uses_alpha_pass();
+ surface.uses_transparent = material->shader_data->uses_alpha_pass();
+ surface.uses_depth = surface.uses_opaque || (surface.uses_transparent && material->shader_data->uses_depth_in_alpha_pass());
+ surface.can_use_lightmap = mesh_storage->mesh_surface_get_format(mesh_surface) & RS::ARRAY_FORMAT_TEX_UV2;
+ _mesh_compile_pipelines_for_surface(surface, global_pipeline_data_required, RS::PIPELINE_SOURCE_MESH, &pipeline_pairs);
+ }
+
+ // Try to retrieve all the pipeline pairs that were compiled. This will force the loader to wait on all ubershader pipelines to be ready.
+ if (!p_background_compilation && !pipeline_pairs.is_empty()) {
+ for (ShaderPipelinePair pair : pipeline_pairs) {
+ pair.first->pipeline_hash_map.get_pipeline(pair.second, pair.second.hash(), true, RS::PIPELINE_SOURCE_MESH);
+ }
+ }
+}
+
+uint32_t RenderForwardMobile::get_pipeline_compilations(RS::PipelineSource p_source) {
+ return scene_shader.get_pipeline_compilations(p_source);
+}
+
bool RenderForwardMobile::free(RID p_rid) {
if (RendererSceneRenderRD::free(p_rid)) {
return true;
@@ -284,6 +346,12 @@ bool RenderForwardMobile::free(RID p_rid) {
return false;
}
+void RenderForwardMobile::update() {
+ RendererSceneRenderRD::update();
+ _update_global_pipeline_data_requirements_from_project();
+ _update_global_pipeline_data_requirements_from_light_storage();
+}
+
/* Render functions */
float RenderForwardMobile::_render_buffers_get_luminance_multiplier() {
@@ -777,6 +845,9 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
merge_transparent_pass = true; // we ignore our screen/depth texture here
using_subpass_post_process = false; // not applicable at all for reflection probes.
samplers = RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default();
+
+ // Indicate pipelines for reflection probes are required.
+ global_pipeline_data_required.use_reflection_probes = true;
} else if (rb_data.is_valid()) {
// setup rendering to render buffer
screen_size = p_render_data->render_buffers->get_internal_size();
@@ -807,6 +878,16 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
ERR_FAIL(); //bug?
}
+ if (p_render_data->scene_data->view_count > 1) {
+ global_pipeline_data_required.use_multiview = true;
+ }
+
+ if (scene_state.used_lightmap) {
+ global_pipeline_data_required.use_lightmaps = true;
+ }
+
+ _update_dirty_geometry_pipelines();
+
p_render_data->scene_data->emissive_exposure_normalization = -1.0;
RD::get_singleton()->draw_command_begin_label("Render Setup");
@@ -924,25 +1005,23 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
_pre_opaque_render(p_render_data);
- uint32_t spec_constant_base_flags = 0;
+ SceneShaderForwardMobile::ShaderSpecialization base_specialization = scene_shader.default_specialization;
{
//figure out spec constants
if (p_render_data->directional_light_count > 0) {
- if (p_render_data->directional_light_soft_shadows) {
- spec_constant_base_flags |= 1 << SPEC_CONSTANT_USING_DIRECTIONAL_SOFT_SHADOWS;
- }
+ base_specialization.use_directional_soft_shadows = p_render_data->directional_light_soft_shadows;
} else {
- spec_constant_base_flags |= 1 << SPEC_CONSTANT_DISABLE_DIRECTIONAL_LIGHTS;
+ base_specialization.disable_directional_lights = true;
}
if (!is_environment(p_render_data->environment) || !environment_get_fog_enabled(p_render_data->environment)) {
- spec_constant_base_flags |= 1 << SPEC_CONSTANT_DISABLE_FOG;
+ base_specialization.disable_fog = true;
}
if (p_render_data->environment.is_valid() && environment_get_fog_mode(p_render_data->environment) == RS::EnvironmentFogMode::ENV_FOG_MODE_DEPTH) {
- spec_constant_base_flags |= 1 << SPEC_CONSTANT_USE_DEPTH_FOG;
+ base_specialization.use_depth_fog = true;
}
}
@@ -1010,7 +1089,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
}
if (render_list[RENDER_LIST_OPAQUE].elements.size() > 0) {
- RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_OPAQUE].elements.ptr(), render_list[RENDER_LIST_OPAQUE].element_info.ptr(), render_list[RENDER_LIST_OPAQUE].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, base_specialization, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
render_list_params.framebuffer_format = fb_format;
render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
@@ -1037,7 +1116,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true);
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR_TRANSPARENT, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR_TRANSPARENT, rp_uniform_set, base_specialization, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
render_list_params.framebuffer_format = fb_format;
render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
@@ -1088,7 +1167,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
// this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation
//_setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false);
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, base_specialization, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count);
render_list_params.framebuffer_format = fb_format;
render_list_params.subpass = RD::get_singleton()->draw_list_get_current_pass(); // Should now always be 0.
@@ -1395,7 +1474,7 @@ void RenderForwardMobile::_render_shadow_end() {
RD::get_singleton()->draw_command_begin_label("Shadow Render");
for (SceneState::ShadowPass &shadow_pass : scene_state.shadow_passes) {
- RenderListParameters render_list_parameters(render_list[RENDER_LIST_SECONDARY].elements.ptr() + shadow_pass.element_from, render_list[RENDER_LIST_SECONDARY].element_info.ptr() + shadow_pass.element_from, shadow_pass.element_count, shadow_pass.flip_cull, shadow_pass.pass_mode, shadow_pass.rp_uniform_set, 0, false, Vector2(), shadow_pass.lod_distance_multiplier, shadow_pass.screen_mesh_lod_threshold, 1, shadow_pass.element_from);
+ RenderListParameters render_list_parameters(render_list[RENDER_LIST_SECONDARY].elements.ptr() + shadow_pass.element_from, render_list[RENDER_LIST_SECONDARY].element_info.ptr() + shadow_pass.element_from, shadow_pass.element_count, shadow_pass.flip_cull, shadow_pass.pass_mode, shadow_pass.rp_uniform_set, scene_shader.default_specialization, false, Vector2(), shadow_pass.lod_distance_multiplier, shadow_pass.screen_mesh_lod_threshold, 1, shadow_pass.element_from);
_render_list_with_draw_list(&render_list_parameters, shadow_pass.framebuffer, RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, shadow_pass.initial_depth_action, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0, 0, shadow_pass.rect);
}
@@ -1439,7 +1518,7 @@ void RenderForwardMobile::_render_material(const Transform3D &p_cam_transform, c
RENDER_TIMESTAMP("Render 3D Material");
{
- RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), true, pass_mode, rp_uniform_set, 0);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), true, pass_mode, rp_uniform_set, scene_shader.default_specialization);
//regular forward for now
Vector<Color> clear = {
Color(0, 0, 0, 0),
@@ -1484,7 +1563,7 @@ void RenderForwardMobile::_render_uv2(const PagedArray<RenderGeometryInstance *>
RENDER_TIMESTAMP("Render 3D Material");
{
- RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), true, pass_mode, rp_uniform_set, true, false);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), true, pass_mode, rp_uniform_set, scene_shader.default_specialization, false);
//regular forward for now
Vector<Color> clear = {
Color(0, 0, 0, 0),
@@ -1568,7 +1647,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const
{
//regular forward for now
- RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), false, pass_mode, rp_uniform_set, 0);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_SECONDARY].elements.ptr(), render_list[RENDER_LIST_SECONDARY].element_info.ptr(), render_list[RENDER_LIST_SECONDARY].elements.size(), false, pass_mode, rp_uniform_set, scene_shader.default_specialization);
_render_list_with_draw_list(&render_list_params, p_fb, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE);
}
RD::get_singleton()->draw_command_end_label();
@@ -1795,6 +1874,7 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
scene_state.used_screen_texture = false;
scene_state.used_normal_texture = false;
scene_state.used_depth_texture = false;
+ scene_state.used_lightmap = false;
}
uint32_t lightmap_captures_used = 0;
@@ -1943,6 +2023,7 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
if (uses_lightmap) {
surf->sort.uses_lightmap = 1; // This needs to become our lightmap index but we'll do that in a separate PR.
+ scene_state.used_lightmap = true;
}
if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_SUBSURFACE_SCATTERING) {
@@ -2036,14 +2117,21 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, p_params->render_pass_uniform_set, RENDER_PASS_UNIFORM_SET);
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, scene_shader.default_vec4_xform_uniform_set, TRANSFORMS_UNIFORM_SET);
+ RID material_uniform_set;
RID prev_material_uniform_set;
RID prev_vertex_array_rd;
RID prev_index_array_rd;
- RID prev_pipeline_rd;
RID prev_xforms_uniform_set;
bool should_request_redraw = false;
+ void *mesh_surface;
+ SceneShaderForwardMobile::ShaderData *shader = nullptr;
+ SceneShaderForwardMobile::ShaderData *prev_shader = nullptr;
+ SceneShaderForwardMobile::ShaderData::PipelineKey pipeline_key;
+ uint32_t pipeline_hash = 0;
+ uint32_t prev_pipeline_hash = 0;
+
bool shadow_pass = (p_params->pass_mode == PASS_MODE_SHADOW) || (p_params->pass_mode == PASS_MODE_SHADOW_DP);
for (uint32_t i = p_from_element; i < p_to_element; i++) {
@@ -2055,11 +2143,8 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
continue;
}
- uint32_t base_spec_constants = p_params->spec_constant_base_flags;
-
- if (bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH)) {
- base_spec_constants |= 1 << SPEC_CONSTANT_IS_MULTIMESH;
- }
+ SceneShaderForwardMobile::ShaderSpecialization pipeline_specialization = p_params->base_specialization;
+ pipeline_specialization.is_multimesh = bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH);
SceneState::PushConstant push_constant;
push_constant.base_index = i + p_params->element_offset;
@@ -2072,35 +2157,18 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
push_constant.uv_offset[1] = 0.0;
}
- RID material_uniform_set;
- SceneShaderForwardMobile::ShaderData *shader;
- void *mesh_surface;
-
if (shadow_pass) {
material_uniform_set = surf->material_uniform_set_shadow;
shader = surf->shader_shadow;
mesh_surface = surf->surface_shadow;
} else {
- if (inst->use_projector) {
- base_spec_constants |= 1 << SPEC_CONSTANT_USING_PROJECTOR;
- }
- if (inst->use_soft_shadow) {
- base_spec_constants |= 1 << SPEC_CONSTANT_USING_SOFT_SHADOWS;
- }
-
- if (inst->omni_light_count == 0) {
- base_spec_constants |= 1 << SPEC_CONSTANT_DISABLE_OMNI_LIGHTS;
- }
- if (inst->spot_light_count == 0) {
- base_spec_constants |= 1 << SPEC_CONSTANT_DISABLE_SPOT_LIGHTS;
- }
- if (inst->reflection_probe_count == 0) {
- base_spec_constants |= 1 << SPEC_CONSTANT_DISABLE_REFLECTION_PROBES;
- }
- if (inst->decals_count == 0) {
- base_spec_constants |= 1 << SPEC_CONSTANT_DISABLE_DECALS;
- }
+ pipeline_specialization.use_light_projector = inst->use_projector;
+ pipeline_specialization.use_light_soft_shadows = inst->use_soft_shadow;
+ pipeline_specialization.disable_omni_lights = inst->omni_light_count == 0;
+ pipeline_specialization.disable_spot_lights = inst->spot_light_count == 0;
+ pipeline_specialization.disable_reflection_probes = inst->reflection_probe_count == 0;
+ pipeline_specialization.disable_decals = inst->decals_count == 0;
#ifdef DEBUG_ENABLED
if (unlikely(get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_LIGHTING)) {
@@ -2145,89 +2213,136 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr
cull_variant = mirror ? SceneShaderForwardMobile::ShaderData::CULL_VARIANT_REVERSED : SceneShaderForwardMobile::ShaderData::CULL_VARIANT_NORMAL;
}
- RS::PrimitiveType primitive = surf->primitive;
+ pipeline_key.primitive_type = surf->primitive;
RID xforms_uniform_set = surf->owner->transforms_uniform_set;
- SceneShaderForwardMobile::ShaderVersion shader_version = SceneShaderForwardMobile::SHADER_VERSION_MAX; // Assigned to silence wrong -Wmaybe-initialized.
-
switch (p_params->pass_mode) {
case PASS_MODE_COLOR:
case PASS_MODE_COLOR_TRANSPARENT: {
if (element_info.uses_lightmap) {
- shader_version = p_params->view_count > 1 ? SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW : SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS;
+ pipeline_key.version = p_params->view_count > 1 ? SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW : SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS;
} else {
- shader_version = p_params->view_count > 1 ? SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS_MULTIVIEW : SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS;
+ pipeline_key.version = p_params->view_count > 1 ? SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS_MULTIVIEW : SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS;
}
} break;
case PASS_MODE_SHADOW: {
- shader_version = p_params->view_count > 1 ? SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS_MULTIVIEW : SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS;
+ pipeline_key.version = p_params->view_count > 1 ? SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS_MULTIVIEW : SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS;
} break;
case PASS_MODE_SHADOW_DP: {
ERR_FAIL_COND_MSG(p_params->view_count > 1, "Multiview not supported for shadow DP pass");
- shader_version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS_DP;
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS_DP;
} break;
case PASS_MODE_DEPTH_MATERIAL: {
ERR_FAIL_COND_MSG(p_params->view_count > 1, "Multiview not supported for material pass");
- shader_version = SceneShaderForwardMobile::SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL;
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL;
} break;
}
- PipelineCacheRD *pipeline = &shader->pipelines[cull_variant][primitive][shader_version];
+ pipeline_key.framebuffer_format_id = framebuffer_format;
+ pipeline_key.wireframe = p_params->force_wireframe;
+ pipeline_key.render_pass = p_params->subpass;
+ pipeline_key.ubershader = 0;
+ const RD::PolygonCullMode cull_mode = shader->get_cull_mode_from_cull_variant(cull_variant);
RD::VertexFormatID vertex_format = -1;
+ RID pipeline_rd;
RID vertex_array_rd;
RID index_array_rd;
+ const uint32_t ubershader_iterations = 2;
+ bool pipeline_valid = false;
+ while (pipeline_key.ubershader < ubershader_iterations) {
+ // Skeleton and blend shape.
+ uint64_t input_mask = shader->get_vertex_input_mask(pipeline_key.version, pipeline_key.ubershader);
+ if (surf->owner->mesh_instance.is_valid()) {
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, input_mask, false, vertex_array_rd, vertex_format);
+ } else {
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, input_mask, false, vertex_array_rd, vertex_format);
+ }
- //skeleton and blend shape
- if (surf->owner->mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(surf->owner->mesh_instance, surf->surface_index, pipeline->get_vertex_input_mask(), false, vertex_array_rd, vertex_format);
- } else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(mesh_surface, pipeline->get_vertex_input_mask(), false, vertex_array_rd, vertex_format);
- }
+ index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
+ pipeline_key.vertex_format_id = vertex_format;
- index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
+ if (pipeline_key.ubershader) {
+ pipeline_key.shader_specialization = {};
+ pipeline_key.cull_mode = RD::POLYGON_CULL_DISABLED;
+ } else {
+ pipeline_key.shader_specialization = pipeline_specialization;
+ pipeline_key.cull_mode = cull_mode;
+ }
- if (prev_vertex_array_rd != vertex_array_rd) {
- RD::get_singleton()->draw_list_bind_vertex_array(draw_list, vertex_array_rd);
- prev_vertex_array_rd = vertex_array_rd;
- }
+ pipeline_hash = pipeline_key.hash();
- if (prev_index_array_rd != index_array_rd) {
- if (index_array_rd.is_valid()) {
- RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array_rd);
+ if (shader != prev_shader || pipeline_hash != prev_pipeline_hash) {
+ RS::PipelineSource pipeline_source = pipeline_key.ubershader ? RS::PIPELINE_SOURCE_DRAW : RS::PIPELINE_SOURCE_SPECIALIZATION;
+ pipeline_rd = shader->pipeline_hash_map.get_pipeline(pipeline_key, pipeline_hash, pipeline_key.ubershader, pipeline_source);
+
+ if (pipeline_rd.is_valid()) {
+ pipeline_valid = true;
+ prev_shader = shader;
+ prev_pipeline_hash = pipeline_hash;
+ break;
+ } else {
+ pipeline_key.ubershader++;
+ }
+ } else {
+ // The same pipeline is bound already.
+ pipeline_valid = true;
+ break;
}
- prev_index_array_rd = index_array_rd;
}
- RID pipeline_rd = pipeline->get_render_pipeline(vertex_format, framebuffer_format, p_params->force_wireframe, p_params->subpass, base_spec_constants);
+ if (pipeline_valid) {
+ index_array_rd = mesh_storage->mesh_surface_get_index_array(mesh_surface, element_info.lod_index);
- if (pipeline_rd != prev_pipeline_rd) {
- RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline_rd);
- prev_pipeline_rd = pipeline_rd;
- }
+ if (prev_vertex_array_rd != vertex_array_rd) {
+ RD::get_singleton()->draw_list_bind_vertex_array(draw_list, vertex_array_rd);
+ prev_vertex_array_rd = vertex_array_rd;
+ }
- if (xforms_uniform_set.is_valid() && prev_xforms_uniform_set != xforms_uniform_set) {
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, xforms_uniform_set, TRANSFORMS_UNIFORM_SET);
- prev_xforms_uniform_set = xforms_uniform_set;
- }
+ if (prev_index_array_rd != index_array_rd) {
+ if (index_array_rd.is_valid()) {
+ RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array_rd);
+ }
+ prev_index_array_rd = index_array_rd;
+ }
- if (material_uniform_set != prev_material_uniform_set) {
- // Update uniform set.
- if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
- RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
+ if (!pipeline_rd.is_null()) {
+ RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, pipeline_rd);
}
- prev_material_uniform_set = material_uniform_set;
- }
+ if (xforms_uniform_set.is_valid() && prev_xforms_uniform_set != xforms_uniform_set) {
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, xforms_uniform_set, TRANSFORMS_UNIFORM_SET);
+ prev_xforms_uniform_set = xforms_uniform_set;
+ }
- RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(SceneState::PushConstant));
+ if (material_uniform_set != prev_material_uniform_set) {
+ // Update uniform set.
+ if (material_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(material_uniform_set)) { // Material may not have a uniform set.
+ RD::get_singleton()->draw_list_bind_uniform_set(draw_list, material_uniform_set, MATERIAL_UNIFORM_SET);
+ }
- uint32_t instance_count = surf->owner->instance_count > 1 ? surf->owner->instance_count : 1;
- if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS) {
- instance_count /= surf->owner->trail_steps;
- }
+ prev_material_uniform_set = material_uniform_set;
+ }
+
+ size_t push_constant_size = 0;
+ if (pipeline_key.ubershader) {
+ push_constant_size = sizeof(SceneState::PushConstant);
+ push_constant.ubershader.specialization = pipeline_specialization;
+ push_constant.ubershader.constants = {};
+ push_constant.ubershader.constants.cull_mode = cull_mode;
+ } else {
+ push_constant_size = sizeof(SceneState::PushConstant) - sizeof(SceneState::PushConstantUbershader);
+ }
+
+ RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, push_constant_size);
+
+ uint32_t instance_count = surf->owner->instance_count > 1 ? surf->owner->instance_count : 1;
+ if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_PARTICLE_TRAILS) {
+ instance_count /= surf->owner->trail_steps;
+ }
- RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+ RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count);
+ }
}
// Make the actual redraw request
@@ -2363,14 +2478,25 @@ void RenderForwardMobile::GeometryInstanceForwardMobile::_mark_dirty() {
RenderForwardMobile::get_singleton()->geometry_instance_dirty_list.add(&dirty_list_element);
}
-void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh) {
- RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+void RenderForwardMobile::_update_global_pipeline_data_requirements_from_project() {
+ const int msaa_2d_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_2d");
+ const int msaa_3d_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_3d");
+ const bool directional_shadow_16_bits = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/16_bits");
+ const bool positional_shadow_16_bits = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_16_bits");
+ global_pipeline_data_required.use_16_bit_shadows = directional_shadow_16_bits || positional_shadow_16_bits;
+ global_pipeline_data_required.use_32_bit_shadows = !directional_shadow_16_bits || !positional_shadow_16_bits;
+ global_pipeline_data_required.target_samples = RenderSceneBuffersRD::msaa_to_samples(RS::ViewportMSAA(msaa_2d_mode));
+ global_pipeline_data_required.texture_samples = RenderSceneBuffersRD::msaa_to_samples(RS::ViewportMSAA(msaa_3d_mode));
+}
- bool has_read_screen_alpha = p_material->shader_data->uses_screen_texture || p_material->shader_data->uses_depth_texture || p_material->shader_data->uses_normal_texture;
- bool has_base_alpha = p_material->shader_data->uses_alpha && (!p_material->shader_data->uses_alpha_clip || p_material->shader_data->uses_alpha_antialiasing);
- bool has_blend_alpha = p_material->shader_data->uses_blend_alpha;
- bool has_alpha = has_base_alpha || has_blend_alpha || has_read_screen_alpha;
+void RenderForwardMobile::_update_global_pipeline_data_requirements_from_light_storage() {
+ RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
+ global_pipeline_data_required.use_shadow_cubemaps = light_storage->get_shadow_cubemaps_used();
+ global_pipeline_data_required.use_shadow_dual_paraboloid = light_storage->get_shadow_dual_paraboloid_used();
+}
+void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
uint32_t flags = 0;
if (p_material->shader_data->uses_sss) {
@@ -2393,10 +2519,9 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI
flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_DOUBLE_SIDED_SHADOWS;
}
- if (has_alpha || p_material->shader_data->depth_draw == SceneShaderForwardMobile::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardMobile::ShaderData::DEPTH_TEST_DISABLED) {
- //material is only meant for alpha pass
+ if (p_material->shader_data->uses_alpha_pass()) {
flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA;
- if ((p_material->shader_data->uses_depth_prepass_alpha || p_material->shader_data->uses_alpha_antialiasing) && !(p_material->shader_data->depth_draw == SceneShaderForwardMobile::ShaderData::DEPTH_DRAW_DISABLED || p_material->shader_data->depth_test == SceneShaderForwardMobile::ShaderData::DEPTH_TEST_DISABLED)) {
+ if (p_material->shader_data->uses_depth_in_alpha_pass()) {
flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH;
flags |= GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW;
}
@@ -2412,7 +2537,7 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI
SceneShaderForwardMobile::MaterialData *material_shadow = nullptr;
void *surface_shadow = nullptr;
- if (!p_material->shader_data->uses_particle_trails && !p_material->shader_data->writes_modelview_or_projection && !p_material->shader_data->uses_vertex && !p_material->shader_data->uses_discard && !p_material->shader_data->uses_depth_prepass_alpha && !p_material->shader_data->uses_alpha_clip && !p_material->shader_data->uses_alpha_antialiasing && !p_material->shader_data->uses_world_coordinates) {
+ if (p_material->shader_data->uses_shared_shadow_material()) {
flags |= GeometryInstanceSurfaceDataCache::FLAG_USES_SHARED_SHADOW_MATERIAL;
material_shadow = static_cast<SceneShaderForwardMobile::MaterialData *>(RendererRD::MaterialStorage::get_singleton()->material_get_data(scene_shader.default_material, RendererRD::MaterialStorage::SHADER_TYPE_3D));
@@ -2471,6 +2596,16 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material(GeometryI
String mesh_path = mesh_storage->mesh_get_path(p_mesh).is_empty() ? "" : "(" + mesh_storage->mesh_get_path(p_mesh) + ")";
WARN_PRINT_ED(vformat("Attempting to use a shader %s that requires tangents with a mesh %s that doesn't contain tangents. Ensure that meshes are imported with the 'ensure_tangents' option. If creating your own meshes, add an `ARRAY_TANGENT` array (when using ArrayMesh) or call `generate_tangents()` (when using SurfaceTool).", shader_path, mesh_path));
}
+
+#if PRELOAD_PIPELINES_ON_SURFACE_CACHE_CONSTRUCTION
+ if (!sdcache->compilation_dirty_element.in_list()) {
+ geometry_surface_compilation_dirty_list.add(&sdcache->compilation_dirty_element);
+ }
+
+ if (!sdcache->compilation_all_element.in_list()) {
+ geometry_surface_compilation_all_list.add(&sdcache->compilation_all_element);
+ }
+#endif
}
void RenderForwardMobile::_geometry_instance_add_surface_with_material_chain(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, RID p_mat_src, RID p_mesh) {
@@ -2482,7 +2617,7 @@ void RenderForwardMobile::_geometry_instance_add_surface_with_material_chain(Geo
while (material->next_pass.is_valid()) {
RID next_pass = material->next_pass;
material = static_cast<SceneShaderForwardMobile::MaterialData *>(material_storage->material_get_data(next_pass, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- if (!material || !material->shader_data->valid) {
+ if (!material || !material->shader_data->is_valid()) {
break;
}
if (ginstance->data->dirty_dependencies) {
@@ -2502,7 +2637,7 @@ void RenderForwardMobile::_geometry_instance_add_surface(GeometryInstanceForward
if (m_src.is_valid()) {
material = static_cast<SceneShaderForwardMobile::MaterialData *>(material_storage->material_get_data(m_src, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- if (!material || !material->shader_data->valid) {
+ if (!material || !material->shader_data->is_valid()) {
material = nullptr;
}
}
@@ -2524,7 +2659,7 @@ void RenderForwardMobile::_geometry_instance_add_surface(GeometryInstanceForward
m_src = ginstance->data->material_overlay;
material = static_cast<SceneShaderForwardMobile::MaterialData *>(material_storage->material_get_data(m_src, RendererRD::MaterialStorage::SHADER_TYPE_3D));
- if (material && material->shader_data->valid) {
+ if (material && material->shader_data->is_valid()) {
if (ginstance->data->dirty_dependencies) {
material_storage->material_update_dependency(m_src, &ginstance->data->dependency_tracker);
}
@@ -2685,10 +2820,262 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge
ginstance->dirty_list_element.remove_from_list();
}
+static RD::FramebufferFormatID _get_color_framebuffer_format_for_pipeline(RD::DataFormat p_color_format, bool p_can_be_storage, RD::TextureSamples p_samples, RD::TextureSamples p_target_samples, bool p_vrs, bool p_post_pass, bool p_hdr, uint32_t p_view_count) {
+ const bool multisampling = p_samples > RD::TEXTURE_SAMPLES_1;
+ RD::AttachmentFormat attachment;
+
+ RD::AttachmentFormat unused_attachment;
+ unused_attachment.usage_flags = RD::AttachmentFormat::UNUSED_ATTACHMENT;
+
+ thread_local Vector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ // Color attachment.
+ attachment.samples = p_samples;
+ attachment.format = p_color_format;
+ attachment.usage_flags = RenderSceneBuffersRD::get_color_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+
+ // Depth attachment.
+ attachment.samples = p_samples;
+ attachment.format = RenderSceneBuffersRD::get_depth_format(false, multisampling, p_can_be_storage);
+ attachment.usage_flags = RenderSceneBuffersRD::get_depth_usage_bits(false, multisampling, p_can_be_storage);
+ attachments.push_back(attachment);
+
+ if (p_vrs) {
+ attachment.samples = RD::TEXTURE_SAMPLES_1;
+ attachment.format = RenderSceneBuffersRD::get_vrs_format();
+ attachment.usage_flags = RenderSceneBuffersRD::get_vrs_usage_bits();
+ }
+
+ if (multisampling) {
+ // Resolve attachment.
+ attachment.samples = RD::TEXTURE_SAMPLES_1;
+ attachment.format = p_color_format;
+ attachment.usage_flags = RenderSceneBuffersRD::get_color_usage_bits(true, false, p_can_be_storage);
+ attachments.push_back(attachment);
+ }
+
+ RD::FramebufferPass pass;
+ thread_local Vector<RD::FramebufferPass> passes;
+ passes.clear();
+ pass.color_attachments.clear();
+ pass.color_attachments.push_back(0);
+ pass.depth_attachment = 1;
+
+ if (p_vrs) {
+ pass.vrs_attachment = 2;
+ }
+
+ if (multisampling) {
+ pass.resolve_attachments.push_back(attachments.size() - 1);
+ }
+
+ passes.push_back(pass);
+
+ if (p_post_pass) {
+ attachment.format = RendererRD::TextureStorage::render_target_get_color_format(p_hdr, false);
+
+ if (p_view_count > 1 || p_target_samples == RD::TEXTURE_SAMPLES_1) {
+ attachment.samples = RD::TEXTURE_SAMPLES_1;
+ attachment.usage_flags = RendererRD::TextureStorage::render_target_get_color_usage_bits(false);
+ } else {
+ attachment.samples = p_target_samples;
+ attachment.usage_flags = RendererRD::TextureStorage::render_target_get_color_usage_bits(true);
+ }
+
+ attachments.push_back(attachment);
+
+ RD::FramebufferPass blit_pass;
+ blit_pass.input_attachments.push_back(multisampling ? (attachments.size() - 2) : 0);
+ blit_pass.color_attachments.push_back(attachments.size() - 1);
+ passes.push_back(blit_pass);
+ }
+
+ return RD::get_singleton()->framebuffer_format_create_multipass(attachments, passes, p_view_count);
+}
+
+static RD::FramebufferFormatID _get_reflection_probe_color_framebuffer_format_for_pipeline() {
+ RD::AttachmentFormat attachment;
+ thread_local Vector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ attachment.format = RendererRD::LightStorage::get_reflection_probe_color_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_reflection_probe_color_usage_bits();
+ attachments.push_back(attachment);
+
+ attachment.format = RendererRD::LightStorage::get_reflection_probe_depth_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_reflection_probe_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+static RD::FramebufferFormatID _get_shadow_cubemap_framebuffer_format_for_pipeline() {
+ thread_local LocalVector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ RD::AttachmentFormat attachment;
+ attachment.format = RendererRD::LightStorage::get_cubemap_depth_format();
+ attachment.usage_flags = RendererRD::LightStorage::get_cubemap_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+static RD::FramebufferFormatID _get_shadow_atlas_framebuffer_format_for_pipeline(bool p_use_16_bits) {
+ thread_local LocalVector<RD::AttachmentFormat> attachments;
+ attachments.clear();
+
+ RD::AttachmentFormat attachment;
+ attachment.format = RendererRD::LightStorage::get_shadow_atlas_depth_format(p_use_16_bits);
+ attachment.usage_flags = RendererRD::LightStorage::get_shadow_atlas_depth_usage_bits();
+ attachments.push_back(attachment);
+
+ return RD::get_singleton()->framebuffer_format_create(attachments);
+}
+
+void RenderForwardMobile::_mesh_compile_pipeline_for_surface(SceneShaderForwardMobile::ShaderData *p_shader, void *p_mesh_surface, bool p_instanced_surface, RS::PipelineSource p_source, SceneShaderForwardMobile::ShaderData::PipelineKey &r_pipeline_key, Vector<ShaderPipelinePair> *r_pipeline_pairs) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ uint64_t input_mask = p_shader->get_vertex_input_mask(r_pipeline_key.version, true);
+ r_pipeline_key.vertex_format_id = mesh_storage->mesh_surface_get_vertex_format(p_mesh_surface, input_mask, p_instanced_surface, false);
+ r_pipeline_key.ubershader = true;
+ p_shader->pipeline_hash_map.compile_pipeline(r_pipeline_key, r_pipeline_key.hash(), p_source);
+
+ if (r_pipeline_pairs != nullptr) {
+ r_pipeline_pairs->push_back({ p_shader, r_pipeline_key });
+ }
+}
+
+void RenderForwardMobile::_mesh_compile_pipelines_for_surface(const SurfacePipelineData &p_surface, const GlobalPipelineData &p_global, RS::PipelineSource p_source, Vector<ShaderPipelinePair> *r_pipeline_pairs) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+
+ // Set the attributes common to all pipelines.
+ SceneShaderForwardMobile::ShaderData::PipelineKey pipeline_key;
+ pipeline_key.cull_mode = RD::POLYGON_CULL_DISABLED;
+ pipeline_key.primitive_type = mesh_storage->mesh_surface_get_primitive(p_surface.mesh_surface);
+ pipeline_key.wireframe = false;
+
+ const bool multiview_enabled = p_global.use_multiview && scene_shader.is_multiview_enabled();
+ const RD::DataFormat buffers_color_format = _render_buffers_get_color_format();
+ const bool buffers_can_be_storage = _render_buffers_can_be_storage();
+ const uint32_t vrs_iterations = is_vrs_supported() ? 2 : 1;
+ for (uint32_t use_vrs = 0; use_vrs < vrs_iterations; use_vrs++) {
+ for (uint32_t use_post_pass = 0; use_post_pass < 2; use_post_pass++) {
+ const uint32_t hdr_iterations = use_post_pass ? 2 : 1;
+ for (uint32_t use_hdr = 0; use_hdr < hdr_iterations; use_hdr++) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS;
+ pipeline_key.framebuffer_format_id = _get_color_framebuffer_format_for_pipeline(buffers_color_format, buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), RD::TextureSamples(p_global.target_samples), use_vrs, use_post_pass, use_hdr, 1);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ if (p_global.use_lightmaps && p_surface.can_use_lightmap) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS;
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ if (multiview_enabled) {
+ // View count is assumed to be 2 as the configuration is dependent on the viewport. It's likely a safe assumption for stereo rendering.
+ const uint32_t view_count = 2;
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS_MULTIVIEW;
+ pipeline_key.framebuffer_format_id = _get_color_framebuffer_format_for_pipeline(buffers_color_format, buffers_can_be_storage, RD::TextureSamples(p_global.texture_samples), RD::TextureSamples(p_global.target_samples), use_vrs, use_post_pass, use_hdr, view_count);
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ if (p_global.use_lightmaps && p_surface.can_use_lightmap) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW;
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+ }
+ }
+ }
+ }
+
+ if (p_global.use_reflection_probes) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_COLOR_PASS;
+ pipeline_key.framebuffer_format_id = _get_reflection_probe_color_framebuffer_format_for_pipeline();
+ _mesh_compile_pipeline_for_surface(p_surface.shader, p_surface.mesh_surface, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ if (!p_surface.uses_depth) {
+ return;
+ }
+
+ if (p_global.use_shadow_cubemaps) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS;
+ pipeline_key.framebuffer_format_id = _get_shadow_cubemap_framebuffer_format_for_pipeline();
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ const uint32_t use_16_bits_start = p_global.use_32_bit_shadows ? 0 : 1;
+ const uint32_t use_16_bits_iterations = p_global.use_16_bit_shadows ? 2 : 1;
+ for (uint32_t use_16_bits = use_16_bits_start; use_16_bits < use_16_bits_iterations; use_16_bits++) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS;
+ pipeline_key.framebuffer_format_id = _get_shadow_atlas_framebuffer_format_for_pipeline(use_16_bits);
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+
+ if (p_global.use_shadow_dual_paraboloid) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS_DP;
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+
+ if (multiview_enabled) {
+ pipeline_key.version = SceneShaderForwardMobile::SHADER_VERSION_SHADOW_PASS_MULTIVIEW;
+ _mesh_compile_pipeline_for_surface(p_surface.shader_shadow, p_surface.mesh_surface_shadow, p_surface.instanced, p_source, pipeline_key, r_pipeline_pairs);
+ }
+ }
+}
+
+void RenderForwardMobile::_mesh_generate_all_pipelines_for_surface_cache(GeometryInstanceSurfaceDataCache *p_surface_cache, const GlobalPipelineData &p_global) {
+ bool uses_alpha_pass = (p_surface_cache->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_ALPHA) != 0;
+ SurfacePipelineData surface;
+ surface.mesh_surface = p_surface_cache->surface;
+ surface.mesh_surface_shadow = p_surface_cache->surface_shadow;
+ surface.shader = p_surface_cache->shader;
+ surface.shader_shadow = p_surface_cache->shader_shadow;
+ surface.instanced = p_surface_cache->owner->mesh_instance.is_valid();
+ surface.uses_opaque = !uses_alpha_pass;
+ surface.uses_transparent = uses_alpha_pass;
+ surface.uses_depth = (p_surface_cache->flags & (GeometryInstanceSurfaceDataCache::FLAG_PASS_DEPTH | GeometryInstanceSurfaceDataCache::FLAG_PASS_OPAQUE | GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW)) != 0;
+ surface.can_use_lightmap = p_surface_cache->owner->lightmap_instance.is_valid() || p_surface_cache->owner->lightmap_sh;
+ _mesh_compile_pipelines_for_surface(surface, p_global, RS::PIPELINE_SOURCE_SURFACE);
+}
+
void RenderForwardMobile::_update_dirty_geometry_instances() {
while (geometry_instance_dirty_list.first()) {
_geometry_instance_update(geometry_instance_dirty_list.first()->self());
}
+
+ _update_dirty_geometry_pipelines();
+}
+
+void RenderForwardMobile::_update_dirty_geometry_pipelines() {
+ if (global_pipeline_data_required.key != global_pipeline_data_compiled.key) {
+ // Go through the entire list of surfaces and compile pipelines for everything again.
+ SelfList<GeometryInstanceSurfaceDataCache> *list = geometry_surface_compilation_all_list.first();
+ while (list != nullptr) {
+ GeometryInstanceSurfaceDataCache *surface_cache = list->self();
+ _mesh_generate_all_pipelines_for_surface_cache(surface_cache, global_pipeline_data_required);
+
+ if (surface_cache->compilation_dirty_element.in_list()) {
+ // Remove any elements from the dirty list as they don't need to be processed again.
+ geometry_surface_compilation_dirty_list.remove(&surface_cache->compilation_dirty_element);
+ }
+
+ list = list->next();
+ }
+
+ global_pipeline_data_compiled.key = global_pipeline_data_required.key;
+ } else {
+ // Compile pipelines only for the dirty list.
+ if (!geometry_surface_compilation_dirty_list.first()) {
+ return;
+ }
+
+ while (geometry_surface_compilation_dirty_list.first() != nullptr) {
+ GeometryInstanceSurfaceDataCache *surface_cache = geometry_surface_compilation_dirty_list.first()->self();
+ _mesh_generate_all_pipelines_for_surface_cache(surface_cache, global_pipeline_data_compiled);
+ surface_cache->compilation_dirty_element.remove_from_list();
+ }
+ }
}
void RenderForwardMobile::_geometry_instance_dependency_changed(Dependency::DependencyChangedNotification p_notification, DependencyTracker *p_tracker) {
@@ -2734,54 +3121,26 @@ uint32_t RenderForwardMobile::get_max_elements() const {
RenderForwardMobile *RenderForwardMobile::singleton = nullptr;
void RenderForwardMobile::_update_shader_quality_settings() {
- Vector<RD::PipelineSpecializationConstant> spec_constants;
-
- RD::PipelineSpecializationConstant sc;
- sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
-
- sc.constant_id = SPEC_CONSTANT_SOFT_SHADOW_SAMPLES;
- sc.int_value = soft_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_PENUMBRA_SHADOW_SAMPLES;
- sc.int_value = penumbra_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_DIRECTIONAL_SOFT_SHADOW_SAMPLES;
- sc.int_value = directional_soft_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_DIRECTIONAL_PENUMBRA_SHADOW_SAMPLES;
- sc.int_value = directional_penumbra_shadow_samples_get();
-
- spec_constants.push_back(sc);
-
- sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_BOOL;
- sc.constant_id = SPEC_CONSTANT_DECAL_USE_MIPMAPS;
- sc.bool_value = decals_get_filter() == RS::DECAL_FILTER_NEAREST_MIPMAPS ||
+ SceneShaderForwardMobile::ShaderSpecialization specialization = {};
+ specialization.soft_shadow_samples = soft_shadow_samples_get();
+ specialization.penumbra_shadow_samples = penumbra_shadow_samples_get();
+ specialization.directional_soft_shadow_samples = directional_soft_shadow_samples_get();
+ specialization.directional_penumbra_shadow_samples = directional_penumbra_shadow_samples_get();
+ specialization.decal_use_mipmaps =
+ decals_get_filter() == RS::DECAL_FILTER_NEAREST_MIPMAPS ||
decals_get_filter() == RS::DECAL_FILTER_LINEAR_MIPMAPS ||
decals_get_filter() == RS::DECAL_FILTER_NEAREST_MIPMAPS_ANISOTROPIC ||
decals_get_filter() == RS::DECAL_FILTER_LINEAR_MIPMAPS_ANISOTROPIC;
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_PROJECTOR_USE_MIPMAPS;
- sc.bool_value = light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_NEAREST_MIPMAPS ||
+ specialization.projector_use_mipmaps =
+ light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_NEAREST_MIPMAPS ||
light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_LINEAR_MIPMAPS ||
light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_NEAREST_MIPMAPS_ANISOTROPIC ||
light_projectors_get_filter() == RS::LIGHT_PROJECTOR_FILTER_LINEAR_MIPMAPS_ANISOTROPIC;
- spec_constants.push_back(sc);
-
- sc.constant_id = SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER;
- sc.bool_value = lightmap_filter_bicubic_get();
-
- spec_constants.push_back(sc);
-
- scene_shader.set_default_specialization_constants(spec_constants);
+ specialization.use_lightmap_bicubic_filter = lightmap_filter_bicubic_get();
+ specialization.luminance_multiplier = 2.0f;
+ scene_shader.set_default_specialization(specialization);
base_uniforms_changed(); //also need this
}
@@ -2800,6 +3159,11 @@ RenderForwardMobile::RenderForwardMobile() {
// defines += "\n#define SDFGI_OCT_SIZE " + itos(gi.sdfgi_get_lightprobe_octahedron_size()) + "\n";
defines += "\n#define MAX_DIRECTIONAL_LIGHT_DATA_STRUCTS " + itos(MAX_DIRECTIONAL_LIGHTS) + "\n";
+ bool force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
+ if (force_vertex_shading) {
+ defines += "\n#define USE_VERTEX_LIGHTING\n";
+ }
+
{
//lightmaps
scene_state.max_lightmaps = 2;
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index 9e429d598a..96f535ef45 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -60,32 +60,6 @@ private:
};
enum {
-
- SPEC_CONSTANT_USING_PROJECTOR = 0,
- SPEC_CONSTANT_USING_SOFT_SHADOWS = 1,
- SPEC_CONSTANT_USING_DIRECTIONAL_SOFT_SHADOWS = 2,
-
- SPEC_CONSTANT_SOFT_SHADOW_SAMPLES = 3,
- SPEC_CONSTANT_PENUMBRA_SHADOW_SAMPLES = 4,
- SPEC_CONSTANT_DIRECTIONAL_SOFT_SHADOW_SAMPLES = 5,
- SPEC_CONSTANT_DIRECTIONAL_PENUMBRA_SHADOW_SAMPLES = 6,
-
- SPEC_CONSTANT_DECAL_USE_MIPMAPS = 7,
- SPEC_CONSTANT_PROJECTOR_USE_MIPMAPS = 8,
-
- SPEC_CONSTANT_DISABLE_OMNI_LIGHTS = 9,
- SPEC_CONSTANT_DISABLE_SPOT_LIGHTS = 10,
- SPEC_CONSTANT_DISABLE_REFLECTION_PROBES = 11,
- SPEC_CONSTANT_DISABLE_DIRECTIONAL_LIGHTS = 12,
-
- SPEC_CONSTANT_DISABLE_DECALS = 13,
- SPEC_CONSTANT_DISABLE_FOG = 14,
- SPEC_CONSTANT_USE_DEPTH_FOG = 16,
- SPEC_CONSTANT_IS_MULTIMESH = 17,
- SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER = 18,
- };
-
- enum {
MAX_LIGHTMAPS = 8,
MAX_RDL_CULL = 8, // maximum number of reflection probes, decals or lights we can cull per geometry instance
INSTANCE_DATA_BUFFER_MIN_SIZE = 4096
@@ -152,14 +126,14 @@ private:
RID render_pass_uniform_set;
bool force_wireframe = false;
Vector2 uv_offset;
- uint32_t spec_constant_base_flags = 0;
+ SceneShaderForwardMobile::ShaderSpecialization base_specialization;
float lod_distance_multiplier = 0.0;
float screen_mesh_lod_threshold = 0.0;
RD::FramebufferFormatID framebuffer_format = 0;
uint32_t element_offset = 0;
uint32_t subpass = 0;
- RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, RID p_render_pass_uniform_set, uint32_t p_spec_constant_base_flags = 0, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), float p_lod_distance_multiplier = 0.0, float p_screen_mesh_lod_threshold = 0.0, uint32_t p_view_count = 1, uint32_t p_element_offset = 0) {
+ RenderListParameters(GeometryInstanceSurfaceDataCache **p_elements, RenderElementInfo *p_element_info, int p_element_count, bool p_reverse_cull, PassMode p_pass_mode, RID p_render_pass_uniform_set, SceneShaderForwardMobile::ShaderSpecialization p_base_specialization, bool p_force_wireframe = false, const Vector2 &p_uv_offset = Vector2(), float p_lod_distance_multiplier = 0.0, float p_screen_mesh_lod_threshold = 0.0, uint32_t p_view_count = 1, uint32_t p_element_offset = 0) {
elements = p_elements;
element_info = p_element_info;
element_count = p_element_count;
@@ -173,7 +147,7 @@ private:
lod_distance_multiplier = p_lod_distance_multiplier;
screen_mesh_lod_threshold = p_screen_mesh_lod_threshold;
element_offset = p_element_offset;
- spec_constant_base_flags = p_spec_constant_base_flags;
+ base_specialization = p_base_specialization;
}
};
@@ -222,10 +196,16 @@ private:
struct SceneState {
LocalVector<RID> uniform_buffers;
+ struct PushConstantUbershader {
+ SceneShaderForwardMobile::ShaderSpecialization specialization;
+ SceneShaderForwardMobile::UbershaderConstants constants;
+ };
+
struct PushConstant {
float uv_offset[2];
uint32_t base_index;
uint32_t pad;
+ PushConstantUbershader ubershader;
};
struct InstanceData {
@@ -264,6 +244,7 @@ private:
bool used_normal_texture = false;
bool used_depth_texture = false;
bool used_sss = false;
+ bool used_lightmap = false;
struct ShadowPass {
uint32_t element_from;
@@ -455,6 +436,12 @@ protected:
GeometryInstanceSurfaceDataCache *next = nullptr;
GeometryInstanceForwardMobile *owner = nullptr;
+
+ SelfList<GeometryInstanceSurfaceDataCache> compilation_dirty_element;
+ SelfList<GeometryInstanceSurfaceDataCache> compilation_all_element;
+
+ GeometryInstanceSurfaceDataCache() :
+ compilation_dirty_element(this), compilation_all_element(this) {}
};
class GeometryInstanceForwardMobile : public RenderGeometryInstanceBase {
@@ -560,24 +547,74 @@ public:
static void _geometry_instance_dependency_deleted(const RID &p_dependency, DependencyTracker *p_tracker);
SelfList<GeometryInstanceForwardMobile>::List geometry_instance_dirty_list;
+ SelfList<GeometryInstanceSurfaceDataCache>::List geometry_surface_compilation_dirty_list;
+ SelfList<GeometryInstanceSurfaceDataCache>::List geometry_surface_compilation_all_list;
PagedAllocator<GeometryInstanceForwardMobile> geometry_instance_alloc;
PagedAllocator<GeometryInstanceSurfaceDataCache> geometry_instance_surface_alloc;
PagedAllocator<GeometryInstanceLightmapSH> geometry_instance_lightmap_sh;
+ struct SurfacePipelineData {
+ void *mesh_surface = nullptr;
+ void *mesh_surface_shadow = nullptr;
+ SceneShaderForwardMobile::ShaderData *shader = nullptr;
+ SceneShaderForwardMobile::ShaderData *shader_shadow = nullptr;
+ bool instanced = false;
+ bool uses_opaque = false;
+ bool uses_transparent = false;
+ bool uses_depth = false;
+ bool can_use_lightmap = false;
+ };
+
+ struct GlobalPipelineData {
+ union {
+ struct {
+ uint32_t texture_samples : 3;
+ uint32_t target_samples : 3;
+ uint32_t use_reflection_probes : 1;
+ uint32_t use_lightmaps : 1;
+ uint32_t use_multiview : 1;
+ uint32_t use_16_bit_shadows : 1;
+ uint32_t use_32_bit_shadows : 1;
+ uint32_t use_shadow_cubemaps : 1;
+ uint32_t use_shadow_dual_paraboloid : 1;
+ };
+
+ uint32_t key;
+ };
+ };
+
+ GlobalPipelineData global_pipeline_data_compiled = {};
+ GlobalPipelineData global_pipeline_data_required = {};
+
+ typedef Pair<SceneShaderForwardMobile::ShaderData *, SceneShaderForwardMobile::ShaderData::PipelineKey> ShaderPipelinePair;
+
+ void _update_global_pipeline_data_requirements_from_project();
+ void _update_global_pipeline_data_requirements_from_light_storage();
void _geometry_instance_add_surface_with_material(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, uint32_t p_material_id, uint32_t p_shader_id, RID p_mesh);
void _geometry_instance_add_surface_with_material_chain(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, SceneShaderForwardMobile::MaterialData *p_material, RID p_mat_src, RID p_mesh);
void _geometry_instance_add_surface(GeometryInstanceForwardMobile *ginstance, uint32_t p_surface, RID p_material, RID p_mesh);
void _geometry_instance_update(RenderGeometryInstance *p_geometry_instance);
+ void _mesh_compile_pipeline_for_surface(SceneShaderForwardMobile::ShaderData *p_shader, void *p_mesh_surface, bool p_instanced_surface, RS::PipelineSource p_source, SceneShaderForwardMobile::ShaderData::PipelineKey &r_pipeline_key, Vector<ShaderPipelinePair> *r_pipeline_pairs = nullptr);
+ void _mesh_compile_pipelines_for_surface(const SurfacePipelineData &p_surface, const GlobalPipelineData &p_global, RS::PipelineSource p_source, Vector<ShaderPipelinePair> *r_pipeline_pairs = nullptr);
+ void _mesh_generate_all_pipelines_for_surface_cache(GeometryInstanceSurfaceDataCache *p_surface_cache, const GlobalPipelineData &p_global);
void _update_dirty_geometry_instances();
+ void _update_dirty_geometry_pipelines();
virtual RenderGeometryInstance *geometry_instance_create(RID p_base) override;
virtual void geometry_instance_free(RenderGeometryInstance *p_geometry_instance) override;
virtual uint32_t geometry_instance_get_pair_mask() override;
+ /* PIPELINES */
+
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) override;
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override;
+
virtual bool free(RID p_rid) override;
+ virtual void update() override;
+
virtual void base_uniforms_changed() override;
virtual bool is_dynamic_gi_supported() const override;
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index 08982096c5..69f084f4c0 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -43,9 +43,9 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
//compile
code = p_code;
- valid = false;
ubo_size = 0;
uniforms.clear();
+ _clear_vertex_input_mask_cache();
if (code.is_empty()) {
return; //just invalid, but no error
@@ -53,10 +53,10 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
ShaderCompiler::GeneratedCode gen_code;
- int blend_mode = BLEND_MODE_MIX;
- int depth_testi = DEPTH_TEST_ENABLED;
- int alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
- int cull = CULL_BACK;
+ blend_mode = BLEND_MODE_MIX;
+ depth_testi = DEPTH_TEST_ENABLED;
+ alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
+ cull_mode = CULL_BACK;
uses_point_size = false;
uses_alpha = false;
@@ -68,8 +68,8 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
uses_roughness = false;
uses_normal = false;
uses_tangent = false;
- bool uses_normal_map = false;
- bool wireframe = false;
+ uses_normal_map = false;
+ wireframe = false;
unshaded = false;
uses_vertex = false;
@@ -91,7 +91,7 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MIX);
actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_mode, BLEND_MODE_SUB);
actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MUL);
- actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULT_ALPHA);
+ actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULTIPLIED_ALPHA);
actions.render_mode_values["alpha_to_coverage"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE);
actions.render_mode_values["alpha_to_coverage_and_one"] = Pair<int *, int>(&alpha_antialiasing_mode, ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE);
@@ -102,9 +102,9 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
actions.render_mode_values["depth_test_disabled"] = Pair<int *, int>(&depth_testi, DEPTH_TEST_DISABLED);
- actions.render_mode_values["cull_disabled"] = Pair<int *, int>(&cull, CULL_DISABLED);
- actions.render_mode_values["cull_front"] = Pair<int *, int>(&cull, CULL_FRONT);
- actions.render_mode_values["cull_back"] = Pair<int *, int>(&cull, CULL_BACK);
+ actions.render_mode_values["cull_disabled"] = Pair<int *, int>(&cull_mode, CULL_DISABLED);
+ actions.render_mode_values["cull_front"] = Pair<int *, int>(&cull_mode, CULL_FRONT);
+ actions.render_mode_values["cull_back"] = Pair<int *, int>(&cull_mode, CULL_BACK);
actions.render_mode_flags["unshaded"] = &unshaded;
actions.render_mode_flags["wireframe"] = &wireframe;
@@ -141,13 +141,12 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
actions.uniforms = &uniforms;
- SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
-
- Error err = shader_singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code);
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ Error err = SceneShaderForwardMobile::singleton->compiler.compile(RS::SHADER_SPATIAL, code, &actions, path, gen_code);
ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed.");
if (version.is_null()) {
- version = shader_singleton->shader.version_create();
+ version = SceneShaderForwardMobile::singleton->shader.version_create();
}
depth_draw = DepthDraw(depth_drawi);
@@ -189,86 +188,60 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
- shader_singleton->shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
- ERR_FAIL_COND(!shader_singleton->shader.version_is_valid(version));
+ SceneShaderForwardMobile::singleton->shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
ubo_size = gen_code.uniform_total_size;
ubo_offsets = gen_code.uniform_offsets;
texture_uniforms = gen_code.texture_uniforms;
- //blend modes
+ pipeline_hash_map.clear_pipelines();
- // if any form of Alpha Antialiasing is enabled, set the blend mode to alpha to coverage
+ // If any form of Alpha Antialiasing is enabled, set the blend mode to alpha to coverage.
if (alpha_antialiasing_mode != ALPHA_ANTIALIASING_OFF) {
blend_mode = BLEND_MODE_ALPHA_TO_COVERAGE;
}
- RD::PipelineColorBlendState::Attachment blend_attachment;
-
- switch (blend_mode) {
- case BLEND_MODE_MIX: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- } break;
- case BLEND_MODE_ADD: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- uses_blend_alpha = true; //force alpha used because of blend
-
- } break;
- case BLEND_MODE_SUB: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
- blend_attachment.color_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- uses_blend_alpha = true; //force alpha used because of blend
-
- } break;
- case BLEND_MODE_MUL: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_DST_COLOR;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ZERO;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_DST_ALPHA;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
- uses_blend_alpha = true; //force alpha used because of blend
- } break;
- case BLEND_MODE_ALPHA_TO_COVERAGE: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
- } break;
- case BLEND_MODE_PREMULT_ALPHA: {
- blend_attachment.enable_blend = true;
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- uses_blend_alpha = true; // Force alpha used because of blend.
- } break;
+ uses_blend_alpha = blend_mode_uses_blend_alpha(BlendMode(blend_mode));
+}
+
+bool SceneShaderForwardMobile::ShaderData::is_animated() const {
+ return (uses_fragment_time && uses_discard) || (uses_vertex_time && uses_vertex);
+}
+
+bool SceneShaderForwardMobile::ShaderData::casts_shadows() const {
+ bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
+ bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing)) || has_read_screen_alpha;
+ bool has_alpha = has_base_alpha || uses_blend_alpha;
+
+ return !has_alpha || (uses_depth_prepass_alpha && !(depth_draw == DEPTH_DRAW_DISABLED || depth_test == DEPTH_TEST_DISABLED));
+}
+
+RS::ShaderNativeSourceCode SceneShaderForwardMobile::ShaderData::get_native_source_code() const {
+ if (version.is_valid()) {
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ return SceneShaderForwardMobile::singleton->shader.version_get_native_source_code(version);
+ } else {
+ return RS::ShaderNativeSourceCode();
}
+}
+
+void SceneShaderForwardMobile::ShaderData::_create_pipeline(PipelineKey p_pipeline_key) {
+#if PRINT_PIPELINE_COMPILATION_KEYS
+ print_line(
+ "HASH:", p_pipeline_key.hash(),
+ "VERSION:", version,
+ "VERTEX:", p_pipeline_key.vertex_format_id,
+ "FRAMEBUFFER:", p_pipeline_key.framebuffer_format_id,
+ "CULL:", p_pipeline_key.cull_mode,
+ "PRIMITIVE:", p_pipeline_key.primitive_type,
+ "VERSION:", p_pipeline_key.version,
+ "SPEC PACKED #0:", p_pipeline_key.shader_specialization.packed_0,
+ "SPEC PACKED #1:", p_pipeline_key.shader_specialization.packed_1,
+ "RENDER PASS:", p_pipeline_key.render_pass,
+ "WIREFRAME:", p_pipeline_key.wireframe);
+#endif
+ RD::PipelineColorBlendState::Attachment blend_attachment = blend_mode_to_blend_attachment(BlendMode(blend_mode));
RD::PipelineColorBlendState blend_state_blend;
blend_state_blend.attachments.push_back(blend_attachment);
RD::PipelineColorBlendState blend_state_opaque = RD::PipelineColorBlendState::create_disabled(1);
@@ -286,113 +259,151 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) {
depth_stencil_state.enable_depth_write = depth_draw != DEPTH_DRAW_DISABLED ? true : false;
}
- for (int i = 0; i < CULL_VARIANT_MAX; i++) {
- RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
- { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
- { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_BACK, RD::POLYGON_CULL_FRONT },
- { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED }
- };
-
- RD::PolygonCullMode cull_mode_rd = cull_mode_rd_table[i][cull];
-
- for (int j = 0; j < RS::PRIMITIVE_MAX; j++) {
- RD::RenderPrimitive primitive_rd_table[RS::PRIMITIVE_MAX] = {
- RD::RENDER_PRIMITIVE_POINTS,
- RD::RENDER_PRIMITIVE_LINES,
- RD::RENDER_PRIMITIVE_LINESTRIPS,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
- };
-
- RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[j];
-
- for (int k = 0; k < SHADER_VERSION_MAX; k++) {
- if (!static_cast<SceneShaderForwardMobile *>(singleton)->shader.is_variant_enabled(k)) {
- continue;
- }
- RD::PipelineRasterizationState raster_state;
- raster_state.cull_mode = cull_mode_rd;
- raster_state.wireframe = wireframe;
-
- RD::PipelineColorBlendState blend_state;
- RD::PipelineDepthStencilState depth_stencil = depth_stencil_state;
- RD::PipelineMultisampleState multisample_state;
-
- if (uses_alpha || uses_blend_alpha) {
- // only allow these flags to go through if we have some form of msaa
- if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE) {
- multisample_state.enable_alpha_to_coverage = true;
- } else if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE) {
- multisample_state.enable_alpha_to_coverage = true;
- multisample_state.enable_alpha_to_one = true;
- }
-
- if (k == SHADER_VERSION_COLOR_PASS || k == SHADER_VERSION_COLOR_PASS_MULTIVIEW || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW) {
- blend_state = blend_state_blend;
- if (depth_draw == DEPTH_DRAW_OPAQUE && !uses_alpha_clip) {
- depth_stencil.enable_depth_write = false; //alpha does not draw depth
- }
- } else if (k == SHADER_VERSION_SHADOW_PASS || k == SHADER_VERSION_SHADOW_PASS_MULTIVIEW || k == SHADER_VERSION_SHADOW_PASS_DP) {
- //none, blend state contains nothing
- } else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
- blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
- } else {
- pipelines[i][j][k].clear();
- continue; // do not use this version (will error if using it is attempted)
- }
- } else {
- if (k == SHADER_VERSION_COLOR_PASS || k == SHADER_VERSION_COLOR_PASS_MULTIVIEW || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS || k == SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW) {
- blend_state = blend_state_opaque;
- } else if (k == SHADER_VERSION_SHADOW_PASS || k == SHADER_VERSION_SHADOW_PASS_MULTIVIEW || k == SHADER_VERSION_SHADOW_PASS_DP) {
- //none, leave empty
- } else if (k == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
- blend_state = RD::PipelineColorBlendState::create_disabled(5); //writes to normal and roughness in opaque way
- } else {
- // ???
- }
- }
-
- RID shader_variant = shader_singleton->shader.version_get_shader(version, k);
- pipelines[i][j][k].setup(shader_variant, primitive_rd, raster_state, multisample_state, depth_stencil, blend_state, 0, singleton->default_specialization_constants);
+ RD::RenderPrimitive primitive_rd_table[RS::PRIMITIVE_MAX] = {
+ RD::RENDER_PRIMITIVE_POINTS,
+ RD::RENDER_PRIMITIVE_LINES,
+ RD::RENDER_PRIMITIVE_LINESTRIPS,
+ RD::RENDER_PRIMITIVE_TRIANGLES,
+ RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
+ };
+
+ RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[p_pipeline_key.primitive_type];
+
+ RD::PipelineRasterizationState raster_state;
+ raster_state.cull_mode = p_pipeline_key.cull_mode;
+ raster_state.wireframe = wireframe || p_pipeline_key.wireframe;
+
+ RD::PipelineMultisampleState multisample_state;
+ multisample_state.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_pipeline_key.framebuffer_format_id, 0);
+
+ RD::PipelineColorBlendState blend_state;
+ if (uses_alpha || uses_blend_alpha) {
+ // These flags should only go through if we have some form of MSAA.
+ if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE) {
+ multisample_state.enable_alpha_to_coverage = true;
+ } else if (alpha_antialiasing_mode == ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE) {
+ multisample_state.enable_alpha_to_coverage = true;
+ multisample_state.enable_alpha_to_one = true;
+ }
+
+ if (p_pipeline_key.version == SHADER_VERSION_COLOR_PASS || p_pipeline_key.version == SHADER_VERSION_COLOR_PASS_MULTIVIEW || p_pipeline_key.version == SHADER_VERSION_LIGHTMAP_COLOR_PASS || p_pipeline_key.version == SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW) {
+ blend_state = blend_state_blend;
+ if (depth_draw == DEPTH_DRAW_OPAQUE && !uses_alpha_clip) {
+ // Alpha does not write to depth.
+ depth_stencil_state.enable_depth_write = false;
}
+ } else if (p_pipeline_key.version == SHADER_VERSION_SHADOW_PASS || p_pipeline_key.version == SHADER_VERSION_SHADOW_PASS_MULTIVIEW || p_pipeline_key.version == SHADER_VERSION_SHADOW_PASS_DP) {
+ // Contains nothing.
+ } else if (p_pipeline_key.version == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
+ // Writes to normal and roughness in opaque way.
+ blend_state = RD::PipelineColorBlendState::create_disabled(5);
+ } else {
+ // Do not use this version (error case).
+ }
+ } else {
+ if (p_pipeline_key.version == SHADER_VERSION_COLOR_PASS || p_pipeline_key.version == SHADER_VERSION_COLOR_PASS_MULTIVIEW || p_pipeline_key.version == SHADER_VERSION_LIGHTMAP_COLOR_PASS || p_pipeline_key.version == SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW) {
+ blend_state = blend_state_opaque;
+ } else if (p_pipeline_key.version == SHADER_VERSION_SHADOW_PASS || p_pipeline_key.version == SHADER_VERSION_SHADOW_PASS_MULTIVIEW || p_pipeline_key.version == SHADER_VERSION_SHADOW_PASS_DP) {
+ // Contains nothing.
+ } else if (p_pipeline_key.version == SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL) {
+ // Writes to normal and roughness in opaque way.
+ blend_state = RD::PipelineColorBlendState::create_disabled(5);
+ } else {
+ // Unknown pipeline version.
}
}
- valid = true;
+ // Convert the specialization from the key to pipeline specialization constants.
+ Vector<RD::PipelineSpecializationConstant> specialization_constants;
+ RD::PipelineSpecializationConstant sc;
+ sc.constant_id = 0;
+ sc.int_value = p_pipeline_key.shader_specialization.packed_0;
+ sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
+ specialization_constants.push_back(sc);
+
+ sc.constant_id = 1;
+ sc.float_value = p_pipeline_key.shader_specialization.packed_1;
+ sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_FLOAT;
+ specialization_constants.push_back(sc);
+
+ RID shader_rid = get_shader_variant(p_pipeline_key.version, p_pipeline_key.ubershader);
+ ERR_FAIL_COND(shader_rid.is_null());
+
+ RID pipeline = RD::get_singleton()->render_pipeline_create(shader_rid, p_pipeline_key.framebuffer_format_id, p_pipeline_key.vertex_format_id, primitive_rd, raster_state, multisample_state, depth_stencil_state, blend_state, 0, p_pipeline_key.render_pass, specialization_constants);
+ ERR_FAIL_COND(pipeline.is_null());
+
+ pipeline_hash_map.add_compiled_pipeline(p_pipeline_key.hash(), pipeline);
}
-bool SceneShaderForwardMobile::ShaderData::is_animated() const {
- return (uses_fragment_time && uses_discard) || (uses_vertex_time && uses_vertex);
+RD::PolygonCullMode SceneShaderForwardMobile::ShaderData::get_cull_mode_from_cull_variant(CullVariant p_cull_variant) {
+ const RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
+ { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
+ { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_BACK, RD::POLYGON_CULL_FRONT },
+ { RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED }
+ };
+
+ return cull_mode_rd_table[p_cull_variant][cull_mode];
}
-bool SceneShaderForwardMobile::ShaderData::casts_shadows() const {
- bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
- bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing)) || has_read_screen_alpha;
- bool has_alpha = has_base_alpha || uses_blend_alpha;
+void SceneShaderForwardMobile::ShaderData::_clear_vertex_input_mask_cache() {
+ for (uint32_t i = 0; i < VERTEX_INPUT_MASKS_SIZE; i++) {
+ vertex_input_masks[i].store(0);
+ }
+}
- return !has_alpha || (uses_depth_prepass_alpha && !(depth_draw == DEPTH_DRAW_DISABLED || depth_test == DEPTH_TEST_DISABLED));
+RID SceneShaderForwardMobile::ShaderData::get_shader_variant(ShaderVersion p_shader_version, bool p_ubershader) const {
+ if (version.is_valid()) {
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ ERR_FAIL_NULL_V(SceneShaderForwardMobile::singleton, RID());
+ return SceneShaderForwardMobile::singleton->shader.version_get_shader(version, p_shader_version + (p_ubershader ? SHADER_VERSION_MAX : 0));
+ } else {
+ return RID();
+ }
}
-RS::ShaderNativeSourceCode SceneShaderForwardMobile::ShaderData::get_native_source_code() const {
- SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
+uint64_t SceneShaderForwardMobile::ShaderData::get_vertex_input_mask(ShaderVersion p_shader_version, bool p_ubershader) {
+ // Vertex input masks require knowledge of the shader. Since querying the shader can be expensive due to high contention and the necessary mutex, we cache the result instead.
+ uint32_t input_mask_index = p_shader_version + (p_ubershader ? SHADER_VERSION_MAX : 0);
+ uint64_t input_mask = vertex_input_masks[input_mask_index].load(std::memory_order_relaxed);
+ if (input_mask == 0) {
+ RID shader_rid = get_shader_variant(p_shader_version, p_ubershader);
+ ERR_FAIL_COND_V(shader_rid.is_null(), 0);
+
+ input_mask = RD::get_singleton()->shader_get_vertex_input_attribute_mask(shader_rid);
+ vertex_input_masks[input_mask_index].store(input_mask, std::memory_order_relaxed);
+ }
+
+ return input_mask;
+}
- return shader_singleton->shader.version_get_native_source_code(version);
+bool SceneShaderForwardMobile::ShaderData::is_valid() const {
+ if (version.is_valid()) {
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ ERR_FAIL_NULL_V(SceneShaderForwardMobile::singleton, false);
+ return SceneShaderForwardMobile::singleton->shader.version_is_valid(version);
+ } else {
+ return false;
+ }
}
SceneShaderForwardMobile::ShaderData::ShaderData() :
shader_list_element(this) {
+ pipeline_hash_map.set_creation_object_and_function(this, &ShaderData::_create_pipeline);
+ pipeline_hash_map.set_compilations(SceneShaderForwardMobile::singleton->pipeline_compilations, &SceneShaderForwardMobile::singleton_mutex);
}
SceneShaderForwardMobile::ShaderData::~ShaderData() {
- SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
- ERR_FAIL_NULL(shader_singleton);
- //pipeline variants will clear themselves if shader is gone
+ pipeline_hash_map.clear_pipelines();
+
if (version.is_valid()) {
- shader_singleton->shader.version_free(version);
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ ERR_FAIL_NULL(SceneShaderForwardMobile::singleton);
+ SceneShaderForwardMobile::singleton->shader.version_free(version);
}
}
RendererRD::MaterialStorage::ShaderData *SceneShaderForwardMobile::_create_shader_func() {
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
ShaderData *shader_data = memnew(ShaderData);
singleton->shader_list.add(&shader_data->shader_list_element);
return shader_data;
@@ -407,9 +418,12 @@ void SceneShaderForwardMobile::MaterialData::set_next_pass(RID p_pass) {
}
bool SceneShaderForwardMobile::MaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
- SceneShaderForwardMobile *shader_singleton = (SceneShaderForwardMobile *)SceneShaderForwardMobile::singleton;
-
- return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardMobile::MATERIAL_UNIFORM_SET, true, true);
+ if (shader_data->version.is_valid()) {
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ return update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, SceneShaderForwardMobile::singleton->shader.version_get_shader(shader_data->version, 0), RenderForwardMobile::MATERIAL_UNIFORM_SET, true, true);
+ } else {
+ return false;
+ }
}
SceneShaderForwardMobile::MaterialData::~MaterialData() {
@@ -426,6 +440,7 @@ RendererRD::MaterialStorage::MaterialData *SceneShaderForwardMobile::_create_mat
/* Scene Shader */
SceneShaderForwardMobile *SceneShaderForwardMobile::singleton = nullptr;
+Mutex SceneShaderForwardMobile::singleton_mutex;
SceneShaderForwardMobile::SceneShaderForwardMobile() {
// there should be only one of these, contained within our RenderForwardMobile singleton.
@@ -439,23 +454,29 @@ void SceneShaderForwardMobile::init(const String p_defines) {
{
Vector<String> shader_versions;
- shader_versions.push_back(""); // SHADER_VERSION_COLOR_PASS
- shader_versions.push_back("\n#define USE_LIGHTMAP\n"); // SHADER_VERSION_LIGHTMAP_COLOR_PASS
- shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n"); // SHADER_VERSION_SHADOW_PASS, should probably change this to MODE_RENDER_SHADOW because we don't have a depth pass here...
- shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n#define MODE_DUAL_PARABOLOID\n"); // SHADER_VERSION_SHADOW_PASS_DP
- shader_versions.push_back("\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_MATERIAL\n"); // SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL
-
- // multiview versions of our shaders
- shader_versions.push_back("\n#define USE_MULTIVIEW\n"); // SHADER_VERSION_COLOR_PASS_MULTIVIEW
- shader_versions.push_back("\n#define USE_MULTIVIEW\n#define USE_LIGHTMAP\n"); // SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW
- shader_versions.push_back("\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n"); // SHADER_VERSION_SHADOW_PASS_MULTIVIEW
+ for (uint32_t ubershader = 0; ubershader < 2; ubershader++) {
+ const String base_define = ubershader ? "\n#define UBERSHADER\n" : "";
+ shader_versions.push_back(base_define + ""); // SHADER_VERSION_COLOR_PASS
+ shader_versions.push_back(base_define + "\n#define USE_LIGHTMAP\n"); // SHADER_VERSION_LIGHTMAP_COLOR_PASS
+ shader_versions.push_back(base_define + "\n#define MODE_RENDER_DEPTH\n"); // SHADER_VERSION_SHADOW_PASS, should probably change this to MODE_RENDER_SHADOW because we don't have a depth pass here...
+ shader_versions.push_back(base_define + "\n#define MODE_RENDER_DEPTH\n#define MODE_DUAL_PARABOLOID\n"); // SHADER_VERSION_SHADOW_PASS_DP
+ shader_versions.push_back(base_define + "\n#define MODE_RENDER_DEPTH\n#define MODE_RENDER_MATERIAL\n"); // SHADER_VERSION_DEPTH_PASS_WITH_MATERIAL
+
+ // Multiview versions of our shaders.
+ shader_versions.push_back(base_define + "\n#define USE_MULTIVIEW\n"); // SHADER_VERSION_COLOR_PASS_MULTIVIEW
+ shader_versions.push_back(base_define + "\n#define USE_MULTIVIEW\n#define USE_LIGHTMAP\n"); // SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW
+ shader_versions.push_back(base_define + "\n#define USE_MULTIVIEW\n#define MODE_RENDER_DEPTH\n"); // SHADER_VERSION_SHADOW_PASS_MULTIVIEW
+ }
shader.initialize(shader_versions, p_defines);
if (!RendererCompositorRD::get_singleton()->is_xr_enabled()) {
- shader.set_variant_enabled(SHADER_VERSION_COLOR_PASS_MULTIVIEW, false);
- shader.set_variant_enabled(SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW, false);
- shader.set_variant_enabled(SHADER_VERSION_SHADOW_PASS_MULTIVIEW, false);
+ for (uint32_t ubershader = 0; ubershader < 2; ubershader++) {
+ uint32_t base_variant = ubershader ? SHADER_VERSION_MAX : 0;
+ shader.set_variant_enabled(base_variant + SHADER_VERSION_COLOR_PASS_MULTIVIEW, false);
+ shader.set_variant_enabled(base_variant + SHADER_VERSION_LIGHTMAP_COLOR_PASS_MULTIVIEW, false);
+ shader.set_variant_enabled(base_variant + SHADER_VERSION_SHADOW_PASS_MULTIVIEW, false);
+ }
}
}
@@ -633,6 +654,13 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
actions.render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";
actions.render_mode_defines["unshaded"] = "#define MODE_UNSHADED\n";
+
+ bool force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
+ if (!force_vertex_shading) {
+ // If forcing vertex shading, this will be defined already.
+ actions.render_mode_defines["vertex_lighting"] = "#define USE_VERTEX_LIGHTING\n";
+ }
+
actions.render_mode_defines["debug_shadow_splits"] = "#define DEBUG_DRAW_PSSM_SPLITS\n";
actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";
@@ -752,19 +780,23 @@ void fragment() {
}
}
-void SceneShaderForwardMobile::set_default_specialization_constants(const Vector<RD::PipelineSpecializationConstant> &p_constants) {
- default_specialization_constants = p_constants;
+void SceneShaderForwardMobile::set_default_specialization(const ShaderSpecialization &p_specialization) {
+ default_specialization = p_specialization;
+
for (SelfList<ShaderData> *E = shader_list.first(); E; E = E->next()) {
- for (int i = 0; i < ShaderData::CULL_VARIANT_MAX; i++) {
- for (int j = 0; j < RS::PRIMITIVE_MAX; j++) {
- for (int k = 0; k < SHADER_VERSION_MAX; k++) {
- E->self()->pipelines[i][j][k].update_specialization_constants(default_specialization_constants);
- }
- }
- }
+ E->self()->pipeline_hash_map.clear_pipelines();
}
}
+uint32_t SceneShaderForwardMobile::get_pipeline_compilations(RS::PipelineSource p_source) {
+ MutexLock lock(SceneShaderForwardMobile::singleton_mutex);
+ return pipeline_compilations[p_source];
+}
+
+bool SceneShaderForwardMobile::is_multiview_enabled() const {
+ return shader.is_variant_enabled(SHADER_VERSION_COLOR_PASS_MULTIVIEW);
+}
+
SceneShaderForwardMobile::~SceneShaderForwardMobile() {
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
index 833b06c1e3..c1095d29dc 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.h
@@ -31,6 +31,7 @@
#ifndef SCENE_SHADER_FORWARD_MOBILE_H
#define SCENE_SHADER_FORWARD_MOBILE_H
+#include "servers/rendering/renderer_rd/pipeline_hash_map_rd.h"
#include "servers/rendering/renderer_rd/renderer_scene_render_rd.h"
#include "servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl.gen.h"
@@ -39,6 +40,7 @@ namespace RendererSceneRenderImplementation {
class SceneShaderForwardMobile {
private:
static SceneShaderForwardMobile *singleton;
+ static Mutex singleton_mutex;
public:
enum ShaderVersion {
@@ -55,16 +57,52 @@ public:
SHADER_VERSION_MAX
};
- struct ShaderData : public RendererRD::MaterialStorage::ShaderData {
- enum BlendMode { //used internally
- BLEND_MODE_MIX,
- BLEND_MODE_ADD,
- BLEND_MODE_SUB,
- BLEND_MODE_MUL,
- BLEND_MODE_PREMULT_ALPHA,
- BLEND_MODE_ALPHA_TO_COVERAGE
+ struct ShaderSpecialization {
+ union {
+ struct {
+ uint32_t use_light_projector : 1;
+ uint32_t use_light_soft_shadows : 1;
+ uint32_t use_directional_soft_shadows : 1;
+ uint32_t decal_use_mipmaps : 1;
+ uint32_t projector_use_mipmaps : 1;
+ uint32_t disable_omni_lights : 1;
+ uint32_t disable_spot_lights : 1;
+ uint32_t disable_reflection_probes : 1;
+ uint32_t disable_directional_lights : 1;
+ uint32_t disable_decals : 1;
+ uint32_t disable_fog : 1;
+ uint32_t use_depth_fog : 1;
+ uint32_t is_multimesh : 1;
+ uint32_t use_lightmap_bicubic_filter : 1;
+ uint32_t pad : 2;
+ uint32_t soft_shadow_samples : 4;
+ uint32_t penumbra_shadow_samples : 4;
+ uint32_t directional_soft_shadow_samples : 4;
+ uint32_t directional_penumbra_shadow_samples : 4;
+ };
+
+ uint32_t packed_0;
+ };
+
+ union {
+ float luminance_multiplier;
+ float packed_1;
};
+ uint32_t packed_2;
+ };
+
+ struct UbershaderConstants {
+ union {
+ struct {
+ uint32_t cull_mode : 2;
+ };
+
+ uint32_t packed_0;
+ };
+ };
+
+ struct ShaderData : public RendererRD::MaterialStorage::ShaderData {
enum DepthDraw {
DEPTH_DRAW_DISABLED,
DEPTH_DRAW_OPAQUE,
@@ -96,10 +134,40 @@ public:
ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE
};
- bool valid = false;
+ struct PipelineKey {
+ RD::VertexFormatID vertex_format_id;
+ RD::FramebufferFormatID framebuffer_format_id;
+ RD::PolygonCullMode cull_mode = RD::POLYGON_CULL_MAX;
+ RS::PrimitiveType primitive_type = RS::PRIMITIVE_MAX;
+ ShaderSpecialization shader_specialization = {};
+ ShaderVersion version = SHADER_VERSION_MAX;
+ uint32_t render_pass = 0;
+ uint32_t wireframe = false;
+ uint32_t ubershader = false;
+
+ uint32_t hash() const {
+ uint32_t h = hash_murmur3_one_32(vertex_format_id);
+ h = hash_murmur3_one_32(framebuffer_format_id, h);
+ h = hash_murmur3_one_32(cull_mode, h);
+ h = hash_murmur3_one_32(primitive_type, h);
+ h = hash_murmur3_one_32(shader_specialization.packed_0, h);
+ h = hash_murmur3_one_float(shader_specialization.packed_1, h);
+ h = hash_murmur3_one_32(shader_specialization.packed_2, h);
+ h = hash_murmur3_one_32(version, h);
+ h = hash_murmur3_one_32(render_pass, h);
+ h = hash_murmur3_one_32(wireframe, h);
+ h = hash_murmur3_one_32(ubershader, h);
+ return hash_fmix32(h);
+ }
+ };
+
+ void _create_pipeline(PipelineKey p_pipeline_key);
+ PipelineHashMapRD<PipelineKey, ShaderData, void (ShaderData::*)(PipelineKey)> pipeline_hash_map;
+
RID version;
- uint64_t vertex_input_mask = 0;
- PipelineCacheRD pipelines[CULL_VARIANT_MAX][RS::PRIMITIVE_MAX][SHADER_VERSION_MAX];
+
+ static const uint32_t VERTEX_INPUT_MASKS_SIZE = SHADER_VERSION_MAX * 2;
+ std::atomic<uint64_t> vertex_input_masks[VERTEX_INPUT_MASKS_SIZE] = {};
Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;
@@ -111,6 +179,11 @@ public:
DepthDraw depth_draw;
DepthTest depth_test;
+ int blend_mode = BLEND_MODE_MIX;
+ int depth_testi = DEPTH_TEST_ENABLED;
+ int alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;
+ int cull_mode = CULL_BACK;
+
bool uses_point_size = false;
bool uses_alpha = false;
bool uses_blend_alpha = false;
@@ -122,6 +195,8 @@ public:
bool uses_normal = false;
bool uses_tangent = false;
bool uses_particle_trails = false;
+ bool uses_normal_map = false;
+ bool wireframe = false;
bool unshaded = false;
bool uses_vertex = false;
@@ -140,10 +215,35 @@ public:
uint64_t last_pass = 0;
uint32_t index = 0;
+ _FORCE_INLINE_ bool uses_alpha_pass() const {
+ bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
+ bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing));
+ bool has_blend_alpha = uses_blend_alpha;
+ bool has_alpha = has_base_alpha || has_blend_alpha;
+ bool no_depth_draw = depth_draw == DEPTH_DRAW_DISABLED;
+ bool no_depth_test = depth_test == DEPTH_TEST_DISABLED;
+ return has_alpha || has_read_screen_alpha || no_depth_draw || no_depth_test;
+ }
+
+ _FORCE_INLINE_ bool uses_depth_in_alpha_pass() const {
+ bool no_depth_draw = depth_draw == DEPTH_DRAW_DISABLED;
+ bool no_depth_test = depth_test == DEPTH_TEST_DISABLED;
+ return (uses_depth_prepass_alpha || uses_alpha_antialiasing) && !(no_depth_draw || no_depth_test);
+ }
+
+ _FORCE_INLINE_ bool uses_shared_shadow_material() const {
+ return !uses_particle_trails && !writes_modelview_or_projection && !uses_vertex && !uses_discard && !uses_depth_prepass_alpha && !uses_alpha_clip && !uses_alpha_antialiasing && !uses_world_coordinates;
+ }
+
virtual void set_code(const String &p_Code);
virtual bool is_animated() const;
virtual bool casts_shadows() const;
virtual RS::ShaderNativeSourceCode get_native_source_code() const;
+ RD::PolygonCullMode get_cull_mode_from_cull_variant(CullVariant p_cull_variant);
+ void _clear_vertex_input_mask_cache();
+ RID get_shader_variant(ShaderVersion p_shader_version, bool p_ubershader) const;
+ uint64_t get_vertex_input_mask(ShaderVersion p_shader_version, bool p_ubershader);
+ bool is_valid() const;
SelfList<ShaderData> shader_list_element;
@@ -204,10 +304,14 @@ public:
SceneShaderForwardMobile();
~SceneShaderForwardMobile();
- Vector<RD::PipelineSpecializationConstant> default_specialization_constants;
+ ShaderSpecialization default_specialization = {};
+
+ uint32_t pipeline_compilations[RS::PIPELINE_SOURCE_MAX] = {};
void init(const String p_defines);
- void set_default_specialization_constants(const Vector<RD::PipelineSpecializationConstant> &p_constants);
+ void set_default_specialization(const ShaderSpecialization &p_specialization);
+ uint32_t get_pipeline_compilations(RS::PipelineSource p_source);
+ bool is_multiview_enabled() const;
};
} // namespace RendererSceneRenderImplementation
diff --git a/servers/rendering/renderer_rd/pipeline_cache_rd.cpp b/servers/rendering/renderer_rd/pipeline_cache_rd.cpp
index c00e5f8b5e..ed03143812 100644
--- a/servers/rendering/renderer_rd/pipeline_cache_rd.cpp
+++ b/servers/rendering/renderer_rd/pipeline_cache_rd.cpp
@@ -89,7 +89,6 @@ void PipelineCacheRD::setup(RID p_shader, RD::RenderPrimitive p_primitive, const
ERR_FAIL_COND(p_shader.is_null());
_clear();
shader = p_shader;
- input_mask = 0;
render_primitive = p_primitive;
rasterization_state = p_rasterization_state;
multisample_state = p_multisample;
@@ -112,13 +111,11 @@ void PipelineCacheRD::update_shader(RID p_shader) {
void PipelineCacheRD::clear() {
_clear();
shader = RID(); //clear shader
- input_mask = 0;
}
PipelineCacheRD::PipelineCacheRD() {
version_count = 0;
versions = nullptr;
- input_mask = 0;
}
PipelineCacheRD::~PipelineCacheRD() {
diff --git a/servers/rendering/renderer_rd/pipeline_cache_rd.h b/servers/rendering/renderer_rd/pipeline_cache_rd.h
index 0ebebd0540..64e6b5078a 100644
--- a/servers/rendering/renderer_rd/pipeline_cache_rd.h
+++ b/servers/rendering/renderer_rd/pipeline_cache_rd.h
@@ -38,7 +38,6 @@ class PipelineCacheRD {
SpinLock spin_lock;
RID shader;
- uint64_t input_mask;
RD::RenderPrimitive render_primitive;
RD::PipelineRasterizationState rasterization_state;
@@ -92,11 +91,8 @@ public:
}
_FORCE_INLINE_ uint64_t get_vertex_input_mask() {
- if (input_mask == 0) {
- ERR_FAIL_COND_V(shader.is_null(), 0);
- input_mask = RD::get_singleton()->shader_get_vertex_input_attribute_mask(shader);
- }
- return input_mask;
+ ERR_FAIL_COND_V(shader.is_null(), 0);
+ return RD::get_singleton()->shader_get_vertex_input_attribute_mask(shader);
}
void clear();
PipelineCacheRD();
diff --git a/servers/rendering/renderer_rd/pipeline_hash_map_rd.h b/servers/rendering/renderer_rd/pipeline_hash_map_rd.h
new file mode 100644
index 0000000000..b76f8ae5e6
--- /dev/null
+++ b/servers/rendering/renderer_rd/pipeline_hash_map_rd.h
@@ -0,0 +1,218 @@
+/**************************************************************************/
+/* pipeline_hash_map_rd.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef PIPELINE_HASH_MAP_RD_H
+#define PIPELINE_HASH_MAP_RD_H
+
+#include "servers/rendering/rendering_device.h"
+#include "servers/rendering_server.h"
+
+#define PRINT_PIPELINE_COMPILATION_KEYS 0
+
+template <typename Key, typename CreationClass, typename CreationFunction>
+class PipelineHashMapRD {
+private:
+ CreationClass *creation_object = nullptr;
+ CreationFunction creation_function = nullptr;
+ Mutex *compilations_mutex = nullptr;
+ uint32_t *compilations = nullptr;
+ RBMap<uint32_t, RID> hash_map;
+ LocalVector<Pair<uint32_t, RID>> compiled_queue;
+ Mutex compiled_queue_mutex;
+ HashMap<uint32_t, WorkerThreadPool::TaskID> compilation_tasks;
+ Mutex local_mutex;
+
+ bool _add_new_pipelines_to_map() {
+ thread_local Vector<uint32_t> hashes_added;
+ hashes_added.clear();
+
+ {
+ MutexLock lock(compiled_queue_mutex);
+ for (const Pair<uint32_t, RID> &pair : compiled_queue) {
+ hash_map[pair.first] = pair.second;
+ hashes_added.push_back(pair.first);
+ }
+
+ compiled_queue.clear();
+ }
+
+ {
+ MutexLock local_lock(local_mutex);
+ for (uint32_t hash : hashes_added) {
+ HashMap<uint32_t, WorkerThreadPool::TaskID>::Iterator task_it = compilation_tasks.find(hash);
+ if (task_it != compilation_tasks.end()) {
+ compilation_tasks.remove(task_it);
+ }
+ }
+ }
+
+ return !hashes_added.is_empty();
+ }
+
+ void _wait_for_compilation() {
+ MutexLock local_lock(local_mutex);
+ for (KeyValue<uint32_t, WorkerThreadPool::TaskID> key_value : compilation_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(key_value.value);
+ }
+ }
+
+public:
+ void add_compiled_pipeline(uint32_t p_hash, RID p_pipeline) {
+ compiled_queue_mutex.lock();
+ compiled_queue.push_back({ p_hash, p_pipeline });
+ compiled_queue_mutex.unlock();
+ }
+
+ // Start compilation of a pipeline ahead of time in the background. Returns true if the compilation was started, false if it wasn't required. Source is only used for collecting statistics.
+ bool compile_pipeline(const Key &p_key, uint32_t p_key_hash, RS::PipelineSource p_source) {
+ DEV_ASSERT((creation_object != nullptr) && (creation_function != nullptr) && "Creation object and function was not set before attempting to compile a pipeline.");
+
+ // Check if the pipeline was already compiled.
+ compiled_queue_mutex.lock();
+ bool already_exists = hash_map.has(p_key_hash);
+ compiled_queue_mutex.unlock();
+ if (already_exists) {
+ return false;
+ }
+
+ // Check if the pipeline is currently being compiled.
+ MutexLock local_lock(local_mutex);
+ if (compilation_tasks.has(p_key_hash)) {
+ return false;
+ }
+
+ if (compilations_mutex != nullptr) {
+ MutexLock compilations_lock(*compilations_mutex);
+ compilations[p_source]++;
+ }
+
+#if PRINT_PIPELINE_COMPILATION_KEYS
+ String source_name = "UNKNOWN";
+ switch (p_source) {
+ case RS::PIPELINE_SOURCE_CANVAS:
+ source_name = "CANVAS";
+ break;
+ case RS::PIPELINE_SOURCE_MESH:
+ source_name = "MESH";
+ break;
+ case RS::PIPELINE_SOURCE_SURFACE:
+ source_name = "SURFACE";
+ break;
+ case RS::PIPELINE_SOURCE_DRAW:
+ source_name = "DRAW";
+ break;
+ case RS::PIPELINE_SOURCE_SPECIALIZATION:
+ source_name = "SPECIALIZATION";
+ break;
+ }
+
+ print_line("HASH:", p_key_hash, "SOURCE:", source_name);
+#endif
+
+ // Queue a background compilation task.
+ WorkerThreadPool::TaskID task_id = WorkerThreadPool::get_singleton()->add_template_task(creation_object, creation_function, p_key, false, "PipelineCompilation");
+ compilation_tasks.insert(p_key_hash, task_id);
+
+ return true;
+ }
+
+ // Retrieve a pipeline. It'll return an empty pipeline if it's not available yet, but it'll be guaranteed to succeed if 'wait for compilation' is true and stall as necessary. Source is just an optional number to aid debugging.
+ RID get_pipeline(const Key &p_key, uint32_t p_key_hash, bool p_wait_for_compilation, RS::PipelineSource p_source) {
+ RBMap<uint32_t, RID>::Element *e = hash_map.find(p_key_hash);
+
+ if (e == nullptr) {
+ // Check if there's any new pipelines that need to be added and try again. This method triggers a mutex lock.
+ if (_add_new_pipelines_to_map()) {
+ e = hash_map.find(p_key_hash);
+ }
+ }
+
+ if (e == nullptr) {
+ // Lock access to the compilation maps.
+ MutexLock local_lock(local_mutex);
+
+ // Request compilation. The method will ignore the request if it's already being compiled.
+ compile_pipeline(p_key, p_key_hash, p_source);
+
+ if (p_wait_for_compilation) {
+ if (compilation_tasks.has(p_key_hash)) {
+ // If a background compilation task was used, wait for it.
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(compilation_tasks[p_key_hash]);
+ }
+
+ _add_new_pipelines_to_map();
+
+ e = hash_map.find(p_key_hash);
+ if (e != nullptr) {
+ return e->value();
+ } else {
+ // Pipeline could not be compiled due to an internal error. Store an empty RID so compilation is not attempted again.
+ hash_map[p_key_hash] = RID();
+ return RID();
+ }
+ } else {
+ return RID();
+ }
+ } else {
+ return e->value();
+ }
+ }
+
+ // Delete all cached pipelines. Can stall if background compilation is in progress.
+ void clear_pipelines() {
+ _wait_for_compilation();
+ _add_new_pipelines_to_map();
+
+ for (KeyValue<uint32_t, RID> entry : hash_map) {
+ RD::get_singleton()->free(entry.value);
+ }
+
+ hash_map.clear();
+ }
+
+ // Set the external pipeline compilations array to increase the counters on every time a pipeline is compiled.
+ void set_compilations(uint32_t *p_compilations, Mutex *p_compilations_mutex) {
+ compilations = p_compilations;
+ compilations_mutex = p_compilations_mutex;
+ }
+
+ void set_creation_object_and_function(CreationClass *p_creation_object, CreationFunction p_creation_function) {
+ creation_object = p_creation_object;
+ creation_function = p_creation_function;
+ }
+
+ PipelineHashMapRD() {}
+
+ ~PipelineHashMapRD() {
+ clear_pipelines();
+ }
+};
+
+#endif // PIPELINE_HASH_MAP_RD_H
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 57497eb207..979f590c4c 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -351,6 +351,23 @@ void RendererCanvasRenderRD::free_polygon(PolygonID p_polygon) {
////////////////////
+static RD::RenderPrimitive _primitive_type_to_render_primitive(RS::PrimitiveType p_primitive) {
+ switch (p_primitive) {
+ case RS::PRIMITIVE_POINTS:
+ return RD::RENDER_PRIMITIVE_POINTS;
+ case RS::PRIMITIVE_LINES:
+ return RD::RENDER_PRIMITIVE_LINES;
+ case RS::PRIMITIVE_LINE_STRIP:
+ return RD::RENDER_PRIMITIVE_LINESTRIPS;
+ case RS::PRIMITIVE_TRIANGLES:
+ return RD::RENDER_PRIMITIVE_TRIANGLES;
+ case RS::PRIMITIVE_TRIANGLE_STRIP:
+ return RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS;
+ default:
+ return RD::RENDER_PRIMITIVE_MAX;
+ }
+}
+
_FORCE_INLINE_ static uint32_t _indices_to_primitives(RS::PrimitiveType p_primitive, uint32_t p_indices) {
static const uint32_t divisor[RS::PRIMITIVE_MAX] = { 1, 2, 1, 3, 1 };
static const uint32_t subtractor[RS::PRIMITIVE_MAX] = { 0, 0, 1, 0, 1 };
@@ -450,6 +467,42 @@ RID RendererCanvasRenderRD::_create_base_uniform_set(RID p_to_render_target, boo
return uniform_set;
}
+RID RendererCanvasRenderRD::_get_pipeline_specialization_or_ubershader(CanvasShaderData *p_shader_data, PipelineKey &r_pipeline_key, PushConstant &r_push_constant, RID p_mesh_instance, void *p_surface, uint32_t p_surface_index, RID *r_vertex_array) {
+ r_pipeline_key.ubershader = 0;
+
+ const uint32_t ubershader_iterations = 1;
+ while (r_pipeline_key.ubershader < ubershader_iterations) {
+ if (r_vertex_array != nullptr) {
+ RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
+ uint64_t input_mask = p_shader_data->get_vertex_input_mask(r_pipeline_key.variant, r_pipeline_key.ubershader);
+ if (p_mesh_instance.is_valid()) {
+ mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(p_mesh_instance, p_surface_index, input_mask, false, *r_vertex_array, r_pipeline_key.vertex_format_id);
+ } else {
+ mesh_storage->mesh_surface_get_vertex_arrays_and_format(p_surface, input_mask, false, *r_vertex_array, r_pipeline_key.vertex_format_id);
+ }
+ }
+
+ if (r_pipeline_key.ubershader) {
+ r_push_constant.shader_specialization = r_pipeline_key.shader_specialization;
+ r_pipeline_key.shader_specialization = {};
+ } else {
+ r_push_constant.shader_specialization = {};
+ }
+
+ bool wait_for_compilation = r_pipeline_key.ubershader || ubershader_iterations == 1;
+ RS::PipelineSource source = RS::PIPELINE_SOURCE_CANVAS;
+ RID pipeline = p_shader_data->pipeline_hash_map.get_pipeline(r_pipeline_key, r_pipeline_key.hash(), wait_for_compilation, source);
+ if (pipeline.is_valid()) {
+ return pipeline;
+ }
+
+ r_pipeline_key.ubershader++;
+ }
+
+ // This case should never be reached unless the shader wasn't available.
+ return RID();
+}
+
void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_light_list, const Transform2D &p_canvas_transform, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info) {
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
@@ -717,7 +770,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
if (material.is_valid()) {
CanvasMaterialData *md = static_cast<CanvasMaterialData *>(material_storage->material_get_data(material, RendererRD::MaterialStorage::SHADER_TYPE_2D));
- if (md && md->shader_data->valid) {
+ if (md && md->shader_data->is_valid()) {
if (md->shader_data->uses_screen_texture && canvas_group_owner == nullptr) {
if (!material_screen_texture_cached) {
backbuffer_copy = true;
@@ -1355,17 +1408,75 @@ void RendererCanvasRenderRD::occluder_polygon_set_cull_mode(RID p_occluder, RS::
oc->cull_mode = p_mode;
}
+void RendererCanvasRenderRD::CanvasShaderData::_clear_vertex_input_mask_cache() {
+ for (uint32_t i = 0; i < VERTEX_INPUT_MASKS_SIZE; i++) {
+ vertex_input_masks[i].store(0);
+ }
+}
+
+void RendererCanvasRenderRD::CanvasShaderData::_create_pipeline(PipelineKey p_pipeline_key) {
+#if PRINT_PIPELINE_COMPILATION_KEYS
+ print_line(
+ "HASH:", p_pipeline_key.hash(),
+ "VERSION:", version,
+ "VARIANT:", p_pipeline_key.variant,
+ "FRAMEBUFFER:", p_pipeline_key.framebuffer_format_id,
+ "VERTEX:", p_pipeline_key.vertex_format_id,
+ "PRIMITIVE:", p_pipeline_key.render_primitive,
+ "SPEC PACKED #0:", p_pipeline_key.shader_specialization.packed_0,
+ "LCD:", p_pipeline_key.lcd_blend);
+#endif
+
+ RendererRD::MaterialStorage::ShaderData::BlendMode blend_mode_rd = RendererRD::MaterialStorage::ShaderData::BlendMode(blend_mode);
+ RD::PipelineColorBlendState blend_state;
+ RD::PipelineColorBlendState::Attachment attachment;
+ uint32_t dynamic_state_flags = 0;
+ if (p_pipeline_key.lcd_blend) {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+ attachment.color_blend_op = RD::BLEND_OP_ADD;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_CONSTANT_COLOR;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ dynamic_state_flags = RD::DYNAMIC_STATE_BLEND_CONSTANTS;
+ } else {
+ attachment = RendererRD::MaterialStorage::ShaderData::blend_mode_to_blend_attachment(blend_mode_rd);
+ }
+
+ blend_state.attachments.push_back(attachment);
+
+ RD::PipelineMultisampleState multisample_state;
+ multisample_state.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_pipeline_key.framebuffer_format_id, 0);
+
+ // Convert the specialization from the key to pipeline specialization constants.
+ Vector<RD::PipelineSpecializationConstant> specialization_constants;
+ RD::PipelineSpecializationConstant sc;
+ sc.constant_id = 0;
+ sc.int_value = p_pipeline_key.shader_specialization.packed_0;
+ sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
+ specialization_constants.push_back(sc);
+
+ RID shader_rid = get_shader(p_pipeline_key.variant, p_pipeline_key.ubershader);
+ ERR_FAIL_COND(shader_rid.is_null());
+
+ RID pipeline = RD::get_singleton()->render_pipeline_create(shader_rid, p_pipeline_key.framebuffer_format_id, p_pipeline_key.vertex_format_id, p_pipeline_key.render_primitive, RD::PipelineRasterizationState(), multisample_state, RD::PipelineDepthStencilState(), blend_state, dynamic_state_flags, 0, specialization_constants);
+ ERR_FAIL_COND(pipeline.is_null());
+
+ pipeline_hash_map.add_compiled_pipeline(p_pipeline_key.hash(), pipeline);
+}
+
void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
//compile
code = p_code;
- valid = false;
ubo_size = 0;
uniforms.clear();
uses_screen_texture = false;
uses_screen_texture_mipmaps = false;
uses_sdf = false;
uses_time = false;
+ _clear_vertex_input_mask_cache();
if (code.is_empty()) {
return; //just invalid, but no error
@@ -1373,7 +1484,7 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
ShaderCompiler::GeneratedCode gen_code;
- int blend_mode = BLEND_MODE_MIX;
+ blend_mode = BLEND_MODE_MIX;
ShaderCompiler::IdentifierActions actions;
actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
@@ -1384,7 +1495,7 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
actions.render_mode_values["blend_mix"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MIX);
actions.render_mode_values["blend_sub"] = Pair<int *, int>(&blend_mode, BLEND_MODE_SUB);
actions.render_mode_values["blend_mul"] = Pair<int *, int>(&blend_mode, BLEND_MODE_MUL);
- actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PMALPHA);
+ actions.render_mode_values["blend_premul_alpha"] = Pair<int *, int>(&blend_mode, BLEND_MODE_PREMULTIPLIED_ALPHA);
actions.render_mode_values["blend_disabled"] = Pair<int *, int>(&blend_mode, BLEND_MODE_DISABLED);
actions.usage_flag_pointers["texture_sdf"] = &uses_sdf;
@@ -1393,6 +1504,7 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
actions.uniforms = &uniforms;
RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
+ MutexLock lock(canvas_singleton->shader.mutex);
Error err = canvas_singleton->shader.compiler.compile(RS::SHADER_CANVAS_ITEM, code, &actions, path, gen_code);
ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed.");
@@ -1400,6 +1512,8 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps;
uses_screen_texture = gen_code.uses_screen_texture;
+ pipeline_hash_map.clear_pipelines();
+
if (version.is_null()) {
version = canvas_singleton->shader.canvas_shader.version_create();
}
@@ -1422,168 +1536,70 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) {
print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]);
#endif
canvas_singleton->shader.canvas_shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines);
- ERR_FAIL_COND(!canvas_singleton->shader.canvas_shader.version_is_valid(version));
ubo_size = gen_code.uniform_total_size;
ubo_offsets = gen_code.uniform_offsets;
texture_uniforms = gen_code.texture_uniforms;
+}
- //update them pipelines
-
- RD::PipelineColorBlendState::Attachment attachment;
-
- switch (blend_mode) {
- case BLEND_MODE_DISABLED: {
- // nothing to do here, disabled by default
-
- } break;
- case BLEND_MODE_MIX: {
- attachment.enable_blend = true;
- attachment.color_blend_op = RD::BLEND_OP_ADD;
- attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- } break;
- case BLEND_MODE_ADD: {
- attachment.enable_blend = true;
- attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- attachment.color_blend_op = RD::BLEND_OP_ADD;
- attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
-
- } break;
- case BLEND_MODE_SUB: {
- attachment.enable_blend = true;
- attachment.alpha_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
- attachment.color_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
- attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+bool RendererCanvasRenderRD::CanvasShaderData::is_animated() const {
+ return false;
+}
- } break;
- case BLEND_MODE_MUL: {
- attachment.enable_blend = true;
- attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- attachment.color_blend_op = RD::BLEND_OP_ADD;
- attachment.src_color_blend_factor = RD::BLEND_FACTOR_DST_COLOR;
- attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ZERO;
- attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_DST_ALPHA;
- attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
+bool RendererCanvasRenderRD::CanvasShaderData::casts_shadows() const {
+ return false;
+}
- } break;
- case BLEND_MODE_PMALPHA: {
- attachment.enable_blend = true;
- attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- attachment.color_blend_op = RD::BLEND_OP_ADD;
- attachment.src_color_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
- attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+RS::ShaderNativeSourceCode RendererCanvasRenderRD::CanvasShaderData::get_native_source_code() const {
+ RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
+ MutexLock lock(canvas_singleton->shader.mutex);
+ return canvas_singleton->shader.canvas_shader.version_get_native_source_code(version);
+}
- } break;
+RID RendererCanvasRenderRD::CanvasShaderData::get_shader(ShaderVariant p_shader_variant, bool p_ubershader) const {
+ if (version.is_valid()) {
+ uint32_t variant_index = p_shader_variant + (p_ubershader ? SHADER_VARIANT_MAX : 0);
+ RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
+ MutexLock lock(canvas_singleton->shader.mutex);
+ return canvas_singleton->shader.canvas_shader.version_get_shader(version, variant_index);
+ } else {
+ return RID();
}
+}
- RD::PipelineColorBlendState blend_state;
- blend_state.attachments.push_back(attachment);
+uint64_t RendererCanvasRenderRD::CanvasShaderData::get_vertex_input_mask(ShaderVariant p_shader_variant, bool p_ubershader) {
+ // Vertex input masks require knowledge of the shader. Since querying the shader can be expensive due to high contention and the necessary mutex, we cache the result instead.
+ uint32_t input_mask_index = p_shader_variant + (p_ubershader ? SHADER_VARIANT_MAX : 0);
+ uint64_t input_mask = vertex_input_masks[input_mask_index].load(std::memory_order_relaxed);
+ if (input_mask == 0) {
+ RID shader_rid = get_shader(p_shader_variant, p_ubershader);
+ ERR_FAIL_COND_V(shader_rid.is_null(), 0);
- RD::PipelineColorBlendState::Attachment attachment_lcd;
- attachment_lcd.enable_blend = true;
- attachment_lcd.alpha_blend_op = RD::BLEND_OP_ADD;
- attachment_lcd.color_blend_op = RD::BLEND_OP_ADD;
- attachment_lcd.src_color_blend_factor = RD::BLEND_FACTOR_CONSTANT_COLOR;
- attachment_lcd.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
- attachment_lcd.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment_lcd.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- RD::PipelineColorBlendState blend_state_lcd;
- blend_state_lcd.attachments.push_back(attachment_lcd);
-
- //update pipelines
-
- for (int i = 0; i < PIPELINE_LIGHT_MODE_MAX; i++) {
- for (int j = 0; j < PIPELINE_VARIANT_MAX; j++) {
- RD::RenderPrimitive primitive[PIPELINE_VARIANT_MAX] = {
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_LINES,
- RD::RENDER_PRIMITIVE_POINTS,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
- RD::RENDER_PRIMITIVE_LINES,
- RD::RENDER_PRIMITIVE_LINESTRIPS,
- RD::RENDER_PRIMITIVE_POINTS,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- };
-
- ShaderVariant shader_variants[PIPELINE_LIGHT_MODE_MAX][PIPELINE_VARIANT_MAX] = {
- {
- //non lit
- SHADER_VARIANT_QUAD,
- SHADER_VARIANT_NINEPATCH,
- SHADER_VARIANT_PRIMITIVE,
- SHADER_VARIANT_PRIMITIVE,
- SHADER_VARIANT_PRIMITIVE_POINTS,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES_POINTS,
- SHADER_VARIANT_QUAD,
- },
- {
- //lit
- SHADER_VARIANT_QUAD_LIGHT,
- SHADER_VARIANT_NINEPATCH_LIGHT,
- SHADER_VARIANT_PRIMITIVE_LIGHT,
- SHADER_VARIANT_PRIMITIVE_LIGHT,
- SHADER_VARIANT_PRIMITIVE_POINTS_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_POINTS_LIGHT,
- SHADER_VARIANT_QUAD_LIGHT,
- },
- };
-
- RID shader_variant = canvas_singleton->shader.canvas_shader.version_get_shader(version, shader_variants[i][j]);
- if (j == PIPELINE_VARIANT_QUAD_LCD_BLEND) {
- pipeline_variants.variants[i][j].setup(shader_variant, primitive[j], RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state_lcd, RD::DYNAMIC_STATE_BLEND_CONSTANTS);
- } else {
- pipeline_variants.variants[i][j].setup(shader_variant, primitive[j], RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state, 0);
- }
- }
+ input_mask = RD::get_singleton()->shader_get_vertex_input_attribute_mask(shader_rid);
+ vertex_input_masks[input_mask_index].store(input_mask, std::memory_order_relaxed);
}
- valid = true;
+ return input_mask;
}
-bool RendererCanvasRenderRD::CanvasShaderData::is_animated() const {
- return false;
-}
-
-bool RendererCanvasRenderRD::CanvasShaderData::casts_shadows() const {
- return false;
+bool RendererCanvasRenderRD::CanvasShaderData::is_valid() const {
+ RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
+ MutexLock lock(canvas_singleton->shader.mutex);
+ return canvas_singleton->shader.canvas_shader.version_is_valid(version);
}
-RS::ShaderNativeSourceCode RendererCanvasRenderRD::CanvasShaderData::get_native_source_code() const {
+RendererCanvasRenderRD::CanvasShaderData::CanvasShaderData() {
RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
- return canvas_singleton->shader.canvas_shader.version_get_native_source_code(version);
+ pipeline_hash_map.set_creation_object_and_function(this, &CanvasShaderData::_create_pipeline);
+ pipeline_hash_map.set_compilations(&canvas_singleton->shader.pipeline_compilations[0], &canvas_singleton->shader.mutex);
}
RendererCanvasRenderRD::CanvasShaderData::~CanvasShaderData() {
- RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
- ERR_FAIL_NULL(canvas_singleton);
- //pipeline variants will clear themselves if shader is gone
+ pipeline_hash_map.clear_pipelines();
+
if (version.is_valid()) {
+ RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
+ MutexLock lock(canvas_singleton->shader.mutex);
canvas_singleton->shader.canvas_shader.version_free(version);
}
}
@@ -1595,8 +1611,10 @@ RendererRD::MaterialStorage::ShaderData *RendererCanvasRenderRD::_create_shader_
bool RendererCanvasRenderRD::CanvasMaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
- bool uniform_set_changed = update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, canvas_singleton->shader.canvas_shader.version_get_shader(shader_data->version, 0), MATERIAL_UNIFORM_SET, true, false);
- bool uniform_set_srgb_changed = update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set_srgb, canvas_singleton->shader.canvas_shader.version_get_shader(shader_data->version, 0), MATERIAL_UNIFORM_SET, false, false);
+ MutexLock lock(canvas_singleton->shader.mutex);
+ RID shader_to_update = canvas_singleton->shader.canvas_shader.version_get_shader(shader_data->version, 0);
+ bool uniform_set_changed = update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set, shader_to_update, MATERIAL_UNIFORM_SET, true, false);
+ bool uniform_set_srgb_changed = update_parameters_uniform_set(p_parameters, p_uniform_dirty, p_textures_dirty, shader_data->uniforms, shader_data->ubo_offsets.ptr(), shader_data->texture_uniforms, shader_data->default_texture_params, shader_data->ubo_size, uniform_set_srgb, shader_to_update, MATERIAL_UNIFORM_SET, false, false);
return uniform_set_changed || uniform_set_srgb_changed;
}
@@ -1647,107 +1665,23 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() {
state.light_uniforms = memnew_arr(LightUniform, state.max_lights_per_render);
Vector<String> variants;
- //non light variants
- variants.push_back(""); //none by default is first variant
- variants.push_back("#define USE_NINEPATCH\n"); //ninepatch is the second variant
- variants.push_back("#define USE_PRIMITIVE\n"); //primitive is the third
- variants.push_back("#define USE_PRIMITIVE\n#define USE_POINT_SIZE\n"); //points need point size
- variants.push_back("#define USE_ATTRIBUTES\n"); // attributes for vertex arrays
- variants.push_back("#define USE_ATTRIBUTES\n#define USE_POINT_SIZE\n"); //attributes with point size
- //light variants
- variants.push_back("#define USE_LIGHTING\n"); //none by default is first variant
- variants.push_back("#define USE_LIGHTING\n#define USE_NINEPATCH\n"); //ninepatch is the second variant
- variants.push_back("#define USE_LIGHTING\n#define USE_PRIMITIVE\n"); //primitive is the third
- variants.push_back("#define USE_LIGHTING\n#define USE_PRIMITIVE\n#define USE_POINT_SIZE\n"); //points need point size
- variants.push_back("#define USE_LIGHTING\n#define USE_ATTRIBUTES\n"); // attributes for vertex arrays
- variants.push_back("#define USE_LIGHTING\n#define USE_ATTRIBUTES\n#define USE_POINT_SIZE\n"); //attributes with point size
+ const uint32_t ubershader_iterations = 1;
+ for (uint32_t ubershader = 0; ubershader < ubershader_iterations; ubershader++) {
+ const String base_define = ubershader ? "\n#define UBERSHADER\n" : "";
+ variants.push_back(base_define + ""); // SHADER_VARIANT_QUAD
+ variants.push_back(base_define + "#define USE_NINEPATCH\n"); // SHADER_VARIANT_NINEPATCH
+ variants.push_back(base_define + "#define USE_PRIMITIVE\n"); // SHADER_VARIANT_PRIMITIVE
+ variants.push_back(base_define + "#define USE_PRIMITIVE\n#define USE_POINT_SIZE\n"); // SHADER_VARIANT_PRIMITIVE_POINTS
+ variants.push_back(base_define + "#define USE_ATTRIBUTES\n"); // SHADER_VARIANT_ATTRIBUTES
+ variants.push_back(base_define + "#define USE_ATTRIBUTES\n#define USE_POINT_SIZE\n"); // SHADER_VARIANT_ATTRIBUTES_POINTS
+ }
shader.canvas_shader.initialize(variants, global_defines);
- shader.default_version = shader.canvas_shader.version_create();
- shader.default_version_rd_shader = shader.canvas_shader.version_get_shader(shader.default_version, SHADER_VARIANT_QUAD);
-
- RD::PipelineColorBlendState blend_state;
- RD::PipelineColorBlendState::Attachment blend_attachment;
-
- blend_attachment.enable_blend = true;
- blend_attachment.color_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
- blend_attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- blend_attachment.alpha_blend_op = RD::BLEND_OP_ADD;
- blend_attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- blend_attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- blend_state.attachments.push_back(blend_attachment);
-
- RD::PipelineColorBlendState::Attachment attachment_lcd;
- attachment_lcd.enable_blend = true;
- attachment_lcd.alpha_blend_op = RD::BLEND_OP_ADD;
- attachment_lcd.color_blend_op = RD::BLEND_OP_ADD;
- attachment_lcd.src_color_blend_factor = RD::BLEND_FACTOR_CONSTANT_COLOR;
- attachment_lcd.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
- attachment_lcd.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
- attachment_lcd.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-
- RD::PipelineColorBlendState blend_state_lcd;
- blend_state_lcd.attachments.push_back(attachment_lcd);
-
- for (int i = 0; i < PIPELINE_LIGHT_MODE_MAX; i++) {
- for (int j = 0; j < PIPELINE_VARIANT_MAX; j++) {
- RD::RenderPrimitive primitive[PIPELINE_VARIANT_MAX] = {
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_LINES,
- RD::RENDER_PRIMITIVE_POINTS,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
- RD::RENDER_PRIMITIVE_LINES,
- RD::RENDER_PRIMITIVE_LINESTRIPS,
- RD::RENDER_PRIMITIVE_POINTS,
- RD::RENDER_PRIMITIVE_TRIANGLES,
- };
-
- ShaderVariant shader_variants[PIPELINE_LIGHT_MODE_MAX][PIPELINE_VARIANT_MAX] = {
- {
- //non lit
- SHADER_VARIANT_QUAD,
- SHADER_VARIANT_NINEPATCH,
- SHADER_VARIANT_PRIMITIVE,
- SHADER_VARIANT_PRIMITIVE,
- SHADER_VARIANT_PRIMITIVE_POINTS,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES,
- SHADER_VARIANT_ATTRIBUTES_POINTS,
- SHADER_VARIANT_QUAD,
- },
- {
- //lit
- SHADER_VARIANT_QUAD_LIGHT,
- SHADER_VARIANT_NINEPATCH_LIGHT,
- SHADER_VARIANT_PRIMITIVE_LIGHT,
- SHADER_VARIANT_PRIMITIVE_LIGHT,
- SHADER_VARIANT_PRIMITIVE_POINTS_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_POINTS_LIGHT,
- SHADER_VARIANT_QUAD_LIGHT,
- },
- };
-
- RID shader_variant = shader.canvas_shader.version_get_shader(shader.default_version, shader_variants[i][j]);
- if (j == PIPELINE_VARIANT_QUAD_LCD_BLEND) {
- shader.pipeline_variants.variants[i][j].setup(shader_variant, primitive[j], RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state_lcd, RD::DYNAMIC_STATE_BLEND_CONSTANTS);
- } else {
- shader.pipeline_variants.variants[i][j].setup(shader_variant, primitive[j], RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), blend_state, 0);
- }
- }
- }
+ shader.default_version_data = memnew(CanvasShaderData);
+ shader.default_version_data->version = shader.canvas_shader.version_create();
+ shader.default_version_data->blend_mode = RendererRD::MaterialStorage::ShaderData::BLEND_MODE_MIX;
+ shader.default_version_rd_shader = shader.default_version_data->get_shader(SHADER_VARIANT_QUAD, false);
}
{
@@ -1974,6 +1908,12 @@ RendererCanvasRenderRD::RendererCanvasRenderRD() {
default_canvas_texture = texture_storage->canvas_texture_allocate();
texture_storage->canvas_texture_initialize(default_canvas_texture);
+ RendererRD::TextureStorage::CanvasTextureInfo info = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_info(default_canvas_texture, default_filter, default_repeat, false, false);
+ default_texture_info.diffuse = info.diffuse;
+ default_texture_info.normal = info.normal;
+ default_texture_info.specular = info.specular;
+ default_texture_info.sampler = info.sampler;
+
state.shadow_texture_size = GLOBAL_GET("rendering/2d/shadow_atlas/size");
//create functions for shader and material
@@ -2095,6 +2035,12 @@ void RendererCanvasRenderRD::set_debug_redraw(bool p_enabled, double p_time, con
debug_redraw_color = p_color;
}
+uint32_t RendererCanvasRenderRD::get_pipeline_compilations(RS::PipelineSource p_source) {
+ RendererCanvasRenderRD *canvas_singleton = static_cast<RendererCanvasRenderRD *>(RendererCanvasRender::singleton);
+ MutexLock lock(canvas_singleton->shader.mutex);
+ return shader.pipeline_compilations[p_source];
+}
+
void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) {
// Record batches
uint32_t instance_index = 0;
@@ -2219,7 +2165,7 @@ void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, state.default_transforms_uniform_set, TRANSFORMS_UNIFORM_SET);
Item *current_clip = nullptr;
- state.current_tex_uniform_set = RID();
+ state.current_batch_uniform_set = RID();
for (uint32_t i = 0; i <= state.current_batch_index; i++) {
Batch *current_batch = &state.canvas_instance_batches[i];
@@ -2238,12 +2184,11 @@ void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target
}
}
- PipelineVariants *pipeline_variants = &shader.pipeline_variants;
-
+ CanvasShaderData *shader_data = shader.default_version_data;
CanvasMaterialData *material_data = current_batch->material_data;
if (material_data) {
- if (material_data->shader_data->version.is_valid() && material_data->shader_data->valid) {
- pipeline_variants = &material_data->shader_data->pipeline_variants;
+ if (material_data->shader_data->version.is_valid() && material_data->shader_data->is_valid()) {
+ shader_data = material_data->shader_data;
// Update uniform set.
RID uniform_set = texture_storage->render_target_is_using_hdr(p_to_render_target.render_target) ? material_data->uniform_set : material_data->uniform_set_srgb;
if (uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(uniform_set)) { // Material may not have a uniform set.
@@ -2253,7 +2198,7 @@ void RendererCanvasRenderRD::_render_batch_items(RenderTarget p_to_render_target
}
}
- _render_batch(draw_list, pipeline_variants, fb_format, p_lights, current_batch, r_render_info);
+ _render_batch(draw_list, shader_data, fb_format, p_lights, current_batch, r_render_info);
}
RD::get_singleton()->draw_list_end();
@@ -2285,7 +2230,6 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
uint32_t lights[4] = { 0, 0, 0, 0 };
uint16_t light_count = 0;
- PipelineLightMode light_mode;
{
Light *light = p_lights;
@@ -2307,11 +2251,11 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
base_flags |= light_count << FLAGS_LIGHT_COUNT_SHIFT;
}
- light_mode = (light_count > 0 || using_directional_lights) ? PIPELINE_LIGHT_MODE_ENABLED : PIPELINE_LIGHT_MODE_DISABLED;
+ bool use_lighting = (light_count > 0 || using_directional_lights);
- if (light_mode != r_current_batch->light_mode) {
+ if (use_lighting != r_current_batch->use_lighting) {
r_current_batch = _new_batch(r_batch_broken);
- r_current_batch->light_mode = light_mode;
+ r_current_batch->use_lighting = use_lighting;
}
// new_instance_data should be called after the current_batch is set.
@@ -2337,11 +2281,11 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
instance_data->world[i] = world[i];
}
- instance_data->flags = base_flags | r_current_batch->tex_flags; // Reset on each command for safety, keep canvas texture binding config.
+ instance_data->flags = base_flags | r_current_batch->tex_info.flags; // Reset on each command for safety, keep canvas texture binding config.
- instance_data->color_texture_pixel_size[0] = r_current_batch->tex_texpixel_size.width;
- instance_data->color_texture_pixel_size[1] = r_current_batch->tex_texpixel_size.height;
- instance_data->specular_shininess = r_current_batch->tex_specular_shininess;
+ instance_data->color_texture_pixel_size[0] = r_current_batch->tex_info.texpixel_size.width;
+ instance_data->color_texture_pixel_size[1] = r_current_batch->tex_info.texpixel_size.height;
+ instance_data->specular_shininess = r_current_batch->tex_info.specular_shininess;
return instance_data;
};
@@ -2363,7 +2307,8 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
r_current_batch->command_type = Item::Command::TYPE_RECT;
r_current_batch->command = c;
// default variant
- r_current_batch->pipeline_variant = PIPELINE_VARIANT_QUAD;
+ r_current_batch->shader_variant = SHADER_VARIANT_QUAD;
+ r_current_batch->render_primitive = RD::RENDER_PRIMITIVE_TRIANGLES;
}
if (bool(rect->flags & CANVAS_RECT_TILE)) {
@@ -2373,10 +2318,10 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
bool has_msdf = bool(rect->flags & CANVAS_RECT_MSDF);
TextureState tex_state(rect->texture, texture_filter, texture_repeat, has_msdf, use_linear_colors);
- if (tex_state != r_current_batch->tex_state) {
+ if (tex_state != r_current_batch->tex_info.state) {
r_current_batch = _new_batch(r_batch_broken);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, rect->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, rect->texture);
}
Color modulated = rect->modulate * base_color;
@@ -2391,7 +2336,8 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
r_current_batch = _new_batch(r_batch_broken);
r_current_batch->has_blend = has_blend;
r_current_batch->modulate = modulated;
- r_current_batch->pipeline_variant = has_blend ? PIPELINE_VARIANT_QUAD_LCD_BLEND : PIPELINE_VARIANT_QUAD;
+ r_current_batch->shader_variant = SHADER_VARIANT_QUAD;
+ r_current_batch->render_primitive = RD::RENDER_PRIMITIVE_TRIANGLES;
}
InstanceData *instance_data = new_instance_data();
@@ -2399,7 +2345,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
Rect2 dst_rect;
if (rect->texture.is_valid()) {
- src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * r_current_batch->tex_texpixel_size, rect->source.size * r_current_batch->tex_texpixel_size) : Rect2(0, 0, 1, 1);
+ src_rect = (rect->flags & CANVAS_RECT_REGION) ? Rect2(rect->source.position * r_current_batch->tex_info.texpixel_size, rect->source.size * r_current_batch->tex_info.texpixel_size) : Rect2(0, 0, 1, 1);
dst_rect = Rect2(rect->rect.position, rect->rect.size);
if (dst_rect.size.width < 0) {
@@ -2480,14 +2426,15 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
r_current_batch->command_type = Item::Command::TYPE_NINEPATCH;
r_current_batch->command = c;
r_current_batch->has_blend = false;
- r_current_batch->pipeline_variant = PipelineVariant::PIPELINE_VARIANT_NINEPATCH;
+ r_current_batch->shader_variant = SHADER_VARIANT_NINEPATCH;
+ r_current_batch->render_primitive = RD::RENDER_PRIMITIVE_TRIANGLES;
}
TextureState tex_state(np->texture, texture_filter, texture_repeat, false, use_linear_colors);
- if (tex_state != r_current_batch->tex_state) {
+ if (tex_state != r_current_batch->tex_info.state) {
r_current_batch = _new_batch(r_batch_broken);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, np->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, np->texture);
}
InstanceData *instance_data = new_instance_data();
@@ -2499,7 +2446,7 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
src_rect = Rect2(0, 0, 1, 1);
} else {
if (np->source != Rect2()) {
- src_rect = Rect2(np->source.position.x * r_current_batch->tex_texpixel_size.width, np->source.position.y * r_current_batch->tex_texpixel_size.height, np->source.size.x * r_current_batch->tex_texpixel_size.width, np->source.size.y * r_current_batch->tex_texpixel_size.height);
+ src_rect = Rect2(np->source.position.x * r_current_batch->tex_info.texpixel_size.width, np->source.position.y * r_current_batch->tex_info.texpixel_size.height, np->source.size.x * r_current_batch->tex_info.texpixel_size.width, np->source.size.y * r_current_batch->tex_info.texpixel_size.height);
instance_data->color_texture_pixel_size[0] = 1.0 / np->source.size.width;
instance_data->color_texture_pixel_size[1] = 1.0 / np->source.size.height;
} else {
@@ -2553,17 +2500,17 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
r_current_batch->command = c;
TextureState tex_state(polygon->texture, texture_filter, texture_repeat, false, use_linear_colors);
- if (tex_state != r_current_batch->tex_state) {
+ if (tex_state != r_current_batch->tex_info.state) {
r_current_batch = _new_batch(r_batch_broken);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, polygon->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, polygon->texture);
}
// pipeline variant
{
- static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
ERR_CONTINUE(polygon->primitive < 0 || polygon->primitive >= RS::PRIMITIVE_MAX);
- r_current_batch->pipeline_variant = variant[polygon->primitive];
+ r_current_batch->shader_variant = polygon->primitive == RS::PRIMITIVE_POINTS ? SHADER_VARIANT_ATTRIBUTES_POINTS : SHADER_VARIANT_ATTRIBUTES;
+ r_current_batch->render_primitive = _primitive_type_to_render_primitive(polygon->primitive);
}
InstanceData *instance_data = new_instance_data();
@@ -2591,15 +2538,32 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
r_current_batch->command = c;
r_current_batch->primitive_points = primitive->point_count;
- static const PipelineVariant variant[4] = { PIPELINE_VARIANT_PRIMITIVE_POINTS, PIPELINE_VARIANT_PRIMITIVE_LINES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES, PIPELINE_VARIANT_PRIMITIVE_TRIANGLES };
ERR_CONTINUE(primitive->point_count == 0 || primitive->point_count > 4);
- r_current_batch->pipeline_variant = variant[primitive->point_count - 1];
+
+ switch (primitive->point_count) {
+ case 1:
+ r_current_batch->shader_variant = SHADER_VARIANT_PRIMITIVE_POINTS;
+ r_current_batch->render_primitive = RD::RENDER_PRIMITIVE_POINTS;
+ break;
+ case 2:
+ r_current_batch->shader_variant = SHADER_VARIANT_PRIMITIVE;
+ r_current_batch->render_primitive = RD::RENDER_PRIMITIVE_LINES;
+ break;
+ case 3:
+ case 4:
+ r_current_batch->shader_variant = SHADER_VARIANT_PRIMITIVE;
+ r_current_batch->render_primitive = RD::RENDER_PRIMITIVE_TRIANGLES;
+ break;
+ default:
+ // Unknown point count.
+ break;
+ };
TextureState tex_state(primitive->texture, texture_filter, texture_repeat, false, use_linear_colors);
- if (tex_state != r_current_batch->tex_state) {
+ if (tex_state != r_current_batch->tex_info.state) {
r_current_batch = _new_batch(r_batch_broken);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, primitive->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, primitive->texture);
}
}
@@ -2657,8 +2621,8 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
if (c->type == Item::Command::TYPE_MESH) {
const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(c);
TextureState tex_state(m->texture, texture_filter, texture_repeat, false, use_linear_colors);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, m->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, m->texture);
instance_data = new_instance_data();
r_current_batch->mesh_instance_count = 1;
@@ -2680,8 +2644,8 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
}
TextureState tex_state(mm->texture, texture_filter, texture_repeat, false, use_linear_colors);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, mm->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, mm->texture);
instance_data = new_instance_data();
instance_data->flags |= 1; // multimesh, trails disabled
@@ -2698,8 +2662,8 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(c);
TextureState tex_state(pt->texture, texture_filter, texture_repeat, false, use_linear_colors);
- r_current_batch->set_tex_state(tex_state);
- _prepare_batch_texture(r_current_batch, pt->texture);
+ r_current_batch->tex_info.state = tex_state;
+ _prepare_batch_texture_info(r_current_batch, pt->texture);
instance_data = new_instance_data();
@@ -2789,34 +2753,47 @@ void RendererCanvasRenderRD::_record_item_commands(const Item *p_item, RenderTar
}
}
-void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info) {
+void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, CanvasShaderData *p_shader_data, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info) {
UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
ERR_FAIL_NULL(uniform_set_cache);
ERR_FAIL_NULL(p_batch->command);
- _bind_canvas_texture(p_draw_list, p_batch->tex_uniform_set);
+ {
+ RD::Uniform u_diffuse(RD::UNIFORM_TYPE_TEXTURE, 0, p_batch->tex_info.diffuse);
+ RD::Uniform u_normal(RD::UNIFORM_TYPE_TEXTURE, 1, p_batch->tex_info.normal);
+ RD::Uniform u_specular(RD::UNIFORM_TYPE_TEXTURE, 2, p_batch->tex_info.specular);
+ RD::Uniform u_sampler(RD::UNIFORM_TYPE_SAMPLER, 3, p_batch->tex_info.sampler);
+ RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 4, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
+
+ RID uniform_set = uniform_set_cache->get_cache(shader.default_version_rd_shader, BATCH_UNIFORM_SET, u_diffuse, u_normal, u_specular, u_sampler, u_instance_data);
+
+ if (state.current_batch_uniform_set != uniform_set) {
+ state.current_batch_uniform_set = uniform_set;
+ RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, uniform_set, BATCH_UNIFORM_SET);
+ }
+ }
+
+ RID pipeline;
+ PipelineKey pipeline_key;
+ PushConstant push_constant;
+ pipeline_key.framebuffer_format_id = p_framebuffer_format;
+ pipeline_key.variant = p_batch->shader_variant;
+ pipeline_key.render_primitive = p_batch->render_primitive;
+ pipeline_key.shader_specialization.use_lighting = p_batch->use_lighting;
+ pipeline_key.lcd_blend = p_batch->has_blend;
switch (p_batch->command_type) {
case Item::Command::TYPE_RECT:
case Item::Command::TYPE_NINEPATCH: {
- RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ pipeline = _get_pipeline_specialization_or_ubershader(p_shader_data, pipeline_key, push_constant);
RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
if (p_batch->has_blend) {
- DEV_ASSERT(p_batch->pipeline_variant == PIPELINE_VARIANT_QUAD_LCD_BLEND);
RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, p_batch->modulate);
}
- PushConstant push_constant;
push_constant.base_instance_index = p_batch->start;
RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
-
- RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
- RD::get_singleton()->draw_list_bind_uniform_set(
- p_draw_list,
- uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
- INSTANCE_DATA_UNIFORM_SET);
-
RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
RD::get_singleton()->draw_list_draw(p_draw_list, true, p_batch->instance_count);
@@ -2833,19 +2810,12 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineV
PolygonBuffers *pb = polygon_buffers.polygons.getptr(polygon->polygon.polygon_id);
ERR_FAIL_NULL(pb);
- RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(pb->vertex_format_id, p_framebuffer_format);
+ pipeline_key.vertex_format_id = pb->vertex_format_id;
+ pipeline = _get_pipeline_specialization_or_ubershader(p_shader_data, pipeline_key, push_constant);
RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- PushConstant push_constant;
push_constant.base_instance_index = p_batch->start;
RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
-
- RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
- RD::get_singleton()->draw_list_bind_uniform_set(
- p_draw_list,
- uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
- INSTANCE_DATA_UNIFORM_SET);
-
RD::get_singleton()->draw_list_bind_vertex_array(p_draw_list, pb->vertex_array);
if (pb->indices.is_valid()) {
RD::get_singleton()->draw_list_bind_index_array(p_draw_list, pb->indices);
@@ -2862,19 +2832,11 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineV
case Item::Command::TYPE_PRIMITIVE: {
const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(p_batch->command);
- RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][p_batch->pipeline_variant].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ pipeline = _get_pipeline_specialization_or_ubershader(p_shader_data, pipeline_key, push_constant);
RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- PushConstant push_constant;
push_constant.base_instance_index = p_batch->start;
RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
-
- RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
- RD::get_singleton()->draw_list_bind_uniform_set(
- p_draw_list,
- uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
- INSTANCE_DATA_UNIFORM_SET);
-
RD::get_singleton()->draw_list_bind_index_array(p_draw_list, primitive_arrays.index_array[MIN(3u, primitive->point_count) - 1]);
uint32_t instance_count = p_batch->instance_count;
RD::get_singleton()->draw_list_draw(p_draw_list, true, instance_count);
@@ -2934,14 +2896,7 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineV
break;
}
- RD::Uniform u_instance_data(RD::UNIFORM_TYPE_STORAGE_BUFFER, 0, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[p_batch->instance_buffer_index]);
- RD::get_singleton()->draw_list_bind_uniform_set(
- p_draw_list,
- uniform_set_cache->get_cache(shader.default_version_rd_shader, INSTANCE_DATA_UNIFORM_SET, u_instance_data),
- INSTANCE_DATA_UNIFORM_SET);
-
uint32_t surf_count = mesh_storage->mesh_get_surface_count(mesh);
- static const PipelineVariant variant[RS::PRIMITIVE_MAX] = { PIPELINE_VARIANT_ATTRIBUTE_POINTS, PIPELINE_VARIANT_ATTRIBUTE_LINES, PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES, PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP };
for (uint32_t j = 0; j < surf_count; j++) {
void *surface = mesh_storage->mesh_get_surface(mesh, j);
@@ -2949,21 +2904,14 @@ void RendererCanvasRenderRD::_render_batch(RD::DrawListID p_draw_list, PipelineV
RS::PrimitiveType primitive = mesh_storage->mesh_surface_get_primitive(surface);
ERR_CONTINUE(primitive < 0 || primitive >= RS::PRIMITIVE_MAX);
- uint64_t input_mask = p_pipeline_variants->variants[p_batch->light_mode][variant[primitive]].get_vertex_input_mask();
-
RID vertex_array;
- RD::VertexFormatID vertex_format = RD::INVALID_FORMAT_ID;
-
- if (mesh_instance.is_valid()) {
- mesh_storage->mesh_instance_surface_get_vertex_arrays_and_format(mesh_instance, j, input_mask, false, vertex_array, vertex_format);
- } else {
- mesh_storage->mesh_surface_get_vertex_arrays_and_format(surface, input_mask, false, vertex_array, vertex_format);
- }
+ pipeline_key.variant = primitive == RS::PRIMITIVE_POINTS ? SHADER_VARIANT_ATTRIBUTES_POINTS : SHADER_VARIANT_ATTRIBUTES;
+ pipeline_key.render_primitive = _primitive_type_to_render_primitive(primitive);
+ pipeline_key.vertex_format_id = RD::INVALID_FORMAT_ID;
- RID pipeline = p_pipeline_variants->variants[p_batch->light_mode][variant[primitive]].get_render_pipeline(vertex_format, p_framebuffer_format);
+ pipeline = _get_pipeline_specialization_or_ubershader(p_shader_data, pipeline_key, push_constant, mesh_instance, surface, j, &vertex_array);
RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
- PushConstant push_constant;
push_constant.base_instance_index = p_batch->start;
RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
@@ -3046,60 +2994,46 @@ void RendererCanvasRenderRD::_allocate_instance_buffer() {
state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.push_back(buf);
}
-void RendererCanvasRenderRD::_prepare_batch_texture(Batch *p_current_batch, RID p_texture) const {
+void RendererCanvasRenderRD::_prepare_batch_texture_info(Batch *p_current_batch, RID p_texture) const {
if (p_texture.is_null()) {
p_texture = default_canvas_texture;
}
- Color specular_shininess;
- bool use_normal;
- bool use_specular;
- Size2i size;
- bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(
- p_texture,
- p_current_batch->tex_state.texture_filter(),
- p_current_batch->tex_state.texture_repeat(),
- shader.default_version_rd_shader,
- CANVAS_TEXTURE_UNIFORM_SET,
- p_current_batch->tex_state.linear_colors(),
- p_current_batch->tex_uniform_set,
- size,
- specular_shininess,
- use_normal,
- use_specular,
- p_current_batch->tex_state.texture_is_data());
+ RendererRD::TextureStorage::CanvasTextureInfo info =
+ RendererRD::TextureStorage::get_singleton()->canvas_texture_get_info(
+ p_texture,
+ p_current_batch->tex_info.state.texture_filter(),
+ p_current_batch->tex_info.state.texture_repeat(),
+ p_current_batch->tex_info.state.linear_colors(),
+ p_current_batch->tex_info.state.texture_is_data());
+
// something odd happened
- if (!success) {
- _prepare_batch_texture(p_current_batch, default_canvas_texture);
+ if (info.is_null()) {
+ _prepare_batch_texture_info(p_current_batch, default_canvas_texture);
return;
}
+ p_current_batch->tex_info.diffuse = info.diffuse;
+ p_current_batch->tex_info.normal = info.normal;
+ p_current_batch->tex_info.specular = info.specular;
+ p_current_batch->tex_info.sampler = info.sampler;
+
// cache values to be copied to instance data
- if (specular_shininess.a < 0.999) {
- p_current_batch->tex_flags |= FLAGS_DEFAULT_SPECULAR_MAP_USED;
+ if (info.specular_color.a < 0.999) {
+ p_current_batch->tex_info.flags |= FLAGS_DEFAULT_SPECULAR_MAP_USED;
}
- if (use_normal) {
- p_current_batch->tex_flags |= FLAGS_DEFAULT_NORMAL_MAP_USED;
+ if (info.use_normal) {
+ p_current_batch->tex_info.flags |= FLAGS_DEFAULT_NORMAL_MAP_USED;
}
- uint8_t a = uint8_t(CLAMP(specular_shininess.a * 255.0, 0.0, 255.0));
- uint8_t b = uint8_t(CLAMP(specular_shininess.b * 255.0, 0.0, 255.0));
- uint8_t g = uint8_t(CLAMP(specular_shininess.g * 255.0, 0.0, 255.0));
- uint8_t r = uint8_t(CLAMP(specular_shininess.r * 255.0, 0.0, 255.0));
- p_current_batch->tex_specular_shininess = uint32_t(a) << 24 | uint32_t(b) << 16 | uint32_t(g) << 8 | uint32_t(r);
+ uint8_t a = uint8_t(CLAMP(info.specular_color.a * 255.0, 0.0, 255.0));
+ uint8_t b = uint8_t(CLAMP(info.specular_color.b * 255.0, 0.0, 255.0));
+ uint8_t g = uint8_t(CLAMP(info.specular_color.g * 255.0, 0.0, 255.0));
+ uint8_t r = uint8_t(CLAMP(info.specular_color.r * 255.0, 0.0, 255.0));
+ p_current_batch->tex_info.specular_shininess = uint32_t(a) << 24 | uint32_t(b) << 16 | uint32_t(g) << 8 | uint32_t(r);
- p_current_batch->tex_texpixel_size = Vector2(1.0 / float(size.width), 1.0 / float(size.height));
-}
-
-void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set) {
- if (state.current_tex_uniform_set == p_uniform_set) {
- return;
- }
-
- state.current_tex_uniform_set = p_uniform_set;
-
- RD::get_singleton()->draw_list_bind_uniform_set(p_draw_list, p_uniform_set, CANVAS_TEXTURE_UNIFORM_SET);
+ p_current_batch->tex_info.texpixel_size = Vector2(1.0 / float(info.size.width), 1.0 / float(info.size.height));
}
RendererCanvasRenderRD::~RendererCanvasRenderRD() {
@@ -3127,11 +3061,6 @@ RendererCanvasRenderRD::~RendererCanvasRenderRD() {
//this will also automatically clear all pipelines
RD::get_singleton()->free(state.shadow_sampler);
}
- //bindings
-
- //shaders
-
- shader.canvas_shader.version_free(shader.default_version);
//buffers
{
@@ -3154,4 +3083,6 @@ RendererCanvasRenderRD::~RendererCanvasRenderRD() {
RendererRD::TextureStorage::get_singleton()->canvas_texture_free(default_canvas_texture);
//pipelines don't need freeing, they are all gone after shaders are gone
+
+ memdelete(shader.default_version_data);
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index 8d90cd23ce..07445b5c53 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -34,6 +34,7 @@
#include "servers/rendering/renderer_canvas_render.h"
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering/renderer_rd/pipeline_cache_rd.h"
+#include "servers/rendering/renderer_rd/pipeline_hash_map_rd.h"
#include "servers/rendering/renderer_rd/shaders/canvas.glsl.gen.h"
#include "servers/rendering/renderer_rd/shaders/canvas_occlusion.glsl.gen.h"
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"
@@ -45,8 +46,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
BASE_UNIFORM_SET = 0,
MATERIAL_UNIFORM_SET = 1,
TRANSFORMS_UNIFORM_SET = 2,
- CANVAS_TEXTURE_UNIFORM_SET = 3,
- INSTANCE_DATA_UNIFORM_SET = 4,
+ BATCH_UNIFORM_SET = 3,
};
const int SAMPLERS_BINDING_FIRST_INDEX = 10;
@@ -58,12 +58,6 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
SHADER_VARIANT_PRIMITIVE_POINTS,
SHADER_VARIANT_ATTRIBUTES,
SHADER_VARIANT_ATTRIBUTES_POINTS,
- SHADER_VARIANT_QUAD_LIGHT,
- SHADER_VARIANT_NINEPATCH_LIGHT,
- SHADER_VARIANT_PRIMITIVE_LIGHT,
- SHADER_VARIANT_PRIMITIVE_POINTS_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_LIGHT,
- SHADER_VARIANT_ATTRIBUTES_POINTS_LIGHT,
SHADER_VARIANT_MAX
};
@@ -85,14 +79,14 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
FLAGS_NINEPATCH_V_MODE_SHIFT = 18,
FLAGS_LIGHT_COUNT_SHIFT = 20,
- FLAGS_DEFAULT_NORMAL_MAP_USED = (1 << 26),
- FLAGS_DEFAULT_SPECULAR_MAP_USED = (1 << 27),
+ FLAGS_DEFAULT_NORMAL_MAP_USED = (1 << 24),
+ FLAGS_DEFAULT_SPECULAR_MAP_USED = (1 << 25),
- FLAGS_USE_MSDF = (1 << 28),
- FLAGS_USE_LCD = (1 << 29),
+ FLAGS_USE_MSDF = (1 << 26),
+ FLAGS_USE_LCD = (1 << 27),
- FLAGS_FLIP_H = (1 << 30),
- FLAGS_FLIP_V = (1 << 31),
+ FLAGS_FLIP_H = (1 << 28),
+ FLAGS_FLIP_V = (1 << 29),
};
enum {
@@ -119,76 +113,82 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
/**** SHADER ****/
/****************/
- enum PipelineVariant {
- PIPELINE_VARIANT_QUAD,
- PIPELINE_VARIANT_NINEPATCH,
- PIPELINE_VARIANT_PRIMITIVE_TRIANGLES,
- PIPELINE_VARIANT_PRIMITIVE_LINES,
- PIPELINE_VARIANT_PRIMITIVE_POINTS,
- PIPELINE_VARIANT_ATTRIBUTE_TRIANGLES,
- PIPELINE_VARIANT_ATTRIBUTE_TRIANGLE_STRIP,
- PIPELINE_VARIANT_ATTRIBUTE_LINES,
- PIPELINE_VARIANT_ATTRIBUTE_LINES_STRIP,
- PIPELINE_VARIANT_ATTRIBUTE_POINTS,
- PIPELINE_VARIANT_QUAD_LCD_BLEND,
- PIPELINE_VARIANT_MAX
- };
- enum PipelineLightMode {
- PIPELINE_LIGHT_MODE_DISABLED,
- PIPELINE_LIGHT_MODE_ENABLED,
- PIPELINE_LIGHT_MODE_MAX
- };
+ struct ShaderSpecialization {
+ union {
+ struct {
+ uint32_t use_lighting : 1;
+ };
- struct PipelineVariants {
- PipelineCacheRD variants[PIPELINE_LIGHT_MODE_MAX][PIPELINE_VARIANT_MAX];
+ uint32_t packed_0;
+ };
};
- struct {
- CanvasShaderRD canvas_shader;
- RID default_version;
- RID default_version_rd_shader;
- RID quad_index_buffer;
- RID quad_index_array;
- PipelineVariants pipeline_variants;
-
- ShaderCompiler compiler;
- } shader;
+ struct PipelineKey {
+ ShaderVariant variant = SHADER_VARIANT_MAX;
+ RD::FramebufferFormatID framebuffer_format_id = RD::INVALID_FORMAT_ID;
+ RD::VertexFormatID vertex_format_id = RD::INVALID_ID;
+ RD::RenderPrimitive render_primitive = RD::RENDER_PRIMITIVE_MAX;
+ ShaderSpecialization shader_specialization = {};
+ uint32_t lcd_blend = 0;
+ uint32_t ubershader = 0;
+
+ uint32_t hash() const {
+ uint32_t h = hash_murmur3_one_32(variant);
+ h = hash_murmur3_one_32(framebuffer_format_id, h);
+ h = hash_murmur3_one_32(vertex_format_id, h);
+ h = hash_murmur3_one_32(render_primitive, h);
+ h = hash_murmur3_one_32(shader_specialization.packed_0, h);
+ h = hash_murmur3_one_32(lcd_blend, h);
+ h = hash_murmur3_one_32(ubershader, h);
+ return hash_fmix32(h);
+ }
+ };
struct CanvasShaderData : public RendererRD::MaterialStorage::ShaderData {
- enum BlendMode { //used internally
- BLEND_MODE_MIX,
- BLEND_MODE_ADD,
- BLEND_MODE_SUB,
- BLEND_MODE_MUL,
- BLEND_MODE_PMALPHA,
- BLEND_MODE_DISABLED,
- };
-
- bool valid = false;
- RID version;
- PipelineVariants pipeline_variants;
-
Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;
+ int blend_mode = 0;
Vector<uint32_t> ubo_offsets;
uint32_t ubo_size = 0;
String code;
+ RID version;
+ PipelineHashMapRD<PipelineKey, CanvasShaderData, void (CanvasShaderData::*)(PipelineKey)> pipeline_hash_map;
+
+ static const uint32_t VERTEX_INPUT_MASKS_SIZE = SHADER_VARIANT_MAX * 2;
+ std::atomic<uint64_t> vertex_input_masks[VERTEX_INPUT_MASKS_SIZE] = {};
bool uses_screen_texture = false;
bool uses_screen_texture_mipmaps = false;
bool uses_sdf = false;
bool uses_time = false;
+ void _clear_vertex_input_mask_cache();
+ void _create_pipeline(PipelineKey p_pipeline_key);
virtual void set_code(const String &p_Code);
virtual bool is_animated() const;
virtual bool casts_shadows() const;
virtual RS::ShaderNativeSourceCode get_native_source_code() const;
+ RID get_shader(ShaderVariant p_shader_variant, bool p_ubershader) const;
+ uint64_t get_vertex_input_mask(ShaderVariant p_shader_variant, bool p_ubershader);
+ bool is_valid() const;
- CanvasShaderData() {}
+ CanvasShaderData();
virtual ~CanvasShaderData();
};
+ struct {
+ // Data must be guaranteed to be erased before the rest on the destructor.
+ CanvasShaderData *default_version_data = nullptr;
+ CanvasShaderRD canvas_shader;
+ RID default_version_rd_shader;
+ RID quad_index_buffer;
+ RID quad_index_array;
+ ShaderCompiler compiler;
+ uint32_t pipeline_compilations[RS::PIPELINE_SOURCE_MAX] = {};
+ Mutex mutex;
+ } shader;
+
RendererRD::MaterialStorage::ShaderData *_create_shader_func();
static RendererRD::MaterialStorage::ShaderData *_create_shader_funcs() {
return static_cast<RendererCanvasRenderRD *>(singleton)->_create_shader_func();
@@ -365,7 +365,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
struct PushConstant {
uint32_t base_instance_index;
- uint32_t pad1;
+ ShaderSpecialization shader_specialization;
uint32_t pad2;
uint32_t pad3;
};
@@ -423,24 +423,25 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
}
};
+ struct TextureInfo {
+ TextureState state;
+ uint32_t specular_shininess = 0;
+ uint32_t flags = 0;
+ Vector2 texpixel_size;
+
+ RID diffuse;
+ RID normal;
+ RID specular;
+ RID sampler;
+ };
+
struct Batch {
// Position in the UBO measured in bytes
uint32_t start = 0;
uint32_t instance_count = 0;
uint32_t instance_buffer_index = 0;
- TextureState tex_state;
- RID tex_uniform_set;
-
- // The following tex_ prefixed fields are used to cache the texture data for the current batch.
- // These values are applied to new InstanceData for the batch
-
- // The cached specular shininess derived from the current texture.
- uint32_t tex_specular_shininess = 0;
- // The cached texture flags, such as FLAGS_DEFAULT_SPECULAR_MAP_USED and FLAGS_DEFAULT_NORMAL_MAP_USED
- uint32_t tex_flags = 0;
- // The cached texture pixel size.
- Vector2 tex_texpixel_size;
+ TextureInfo tex_info;
Color modulate = Color(1.0, 1.0, 1.0, 1.0);
@@ -448,11 +449,12 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
RID material;
CanvasMaterialData *material_data = nullptr;
- PipelineLightMode light_mode = PipelineLightMode::PIPELINE_LIGHT_MODE_DISABLED;
- PipelineVariant pipeline_variant = PipelineVariant::PIPELINE_VARIANT_QUAD;
const Item::Command *command = nullptr;
Item::Command::Type command_type = Item::Command::TYPE_ANIMATION_SLICE; // Can default to any type that doesn't form a batch.
+ ShaderVariant shader_variant = SHADER_VARIANT_QUAD;
+ RD::RenderPrimitive render_primitive = RD::RENDER_PRIMITIVE_TRIANGLES;
+ bool use_lighting = false;
// batch-specific data
union {
@@ -462,14 +464,6 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
uint32_t mesh_instance_count;
};
bool has_blend = false;
-
- void set_tex_state(TextureState &p_tex_state) {
- tex_state = p_tex_state;
- tex_uniform_set = RID();
- tex_texpixel_size = Size2();
- tex_specular_shininess = 0;
- tex_flags = 0;
- }
};
struct DataBuffer {
@@ -509,7 +503,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
uint32_t max_instances_per_buffer = 16384;
uint32_t max_instance_buffer_size = 16384 * sizeof(InstanceData);
- RID current_tex_uniform_set;
+ RID current_batch_uniform_set;
LightUniform *light_uniforms = nullptr;
@@ -532,6 +526,8 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
Item *items[MAX_RENDER_ITEMS];
+ TextureInfo default_texture_info;
+
bool using_directional_lights = false;
RID default_canvas_texture;
@@ -558,11 +554,11 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
uint32_t base_flags = 0;
};
+ inline RID _get_pipeline_specialization_or_ubershader(CanvasShaderData *p_shader_data, PipelineKey &r_pipeline_key, PushConstant &r_push_constant, RID p_mesh_instance = RID(), void *p_surface = nullptr, uint32_t p_surface_index = 0, RID *r_vertex_array = nullptr);
void _render_batch_items(RenderTarget p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
void _record_item_commands(const Item *p_item, RenderTarget p_render_target, const Transform2D &p_base_transform, Item *&r_current_clip, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, Batch *&r_current_batch);
- void _render_batch(RD::DrawListID p_draw_list, PipelineVariants *p_pipeline_variants, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _prepare_batch_texture(Batch *p_current_batch, RID p_texture) const;
- void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_uniform_set);
+ void _render_batch(RD::DrawListID p_draw_list, CanvasShaderData *p_shader_data, RenderingDevice::FramebufferFormatID p_framebuffer_format, Light *p_lights, Batch const *p_batch, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _prepare_batch_texture_info(Batch *p_current_batch, RID p_texture) const;
[[nodiscard]] Batch *_new_batch(bool &r_batch_broken);
void _add_to_batch(uint32_t &r_index, bool &r_batch_broken, Batch *&r_current_batch);
void _allocate_instance_buffer();
@@ -596,6 +592,7 @@ public:
virtual void set_shadow_texture_size(int p_size) override;
void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) override;
+ uint32_t get_pipeline_compilations(RS::PipelineSource p_source) override;
void set_time(double p_time);
void update() override;
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h
index 0222a99577..dcd3e90e1b 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.h
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h
@@ -129,6 +129,7 @@ public:
_ALWAYS_INLINE_ uint64_t get_frame_number() const { return frame; }
_ALWAYS_INLINE_ double get_frame_delta_time() const { return delta; }
_ALWAYS_INLINE_ double get_total_time() const { return time; }
+ _ALWAYS_INLINE_ bool can_create_resources_async() const { return true; }
static Error is_viable() {
return OK;
diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp
index 39c3e9b168..6234cddee3 100644
--- a/servers/rendering/renderer_rd/shader_rd.cpp
+++ b/servers/rendering/renderer_rd/shader_rd.cpp
@@ -39,6 +39,8 @@
#include "servers/rendering/rendering_device.h"
#include "thirdparty/misc/smolv.h"
+#define ENABLE_SHADER_CACHE 1
+
void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) {
Vector<String> lines = String(p_code).split("\n");
@@ -144,7 +146,8 @@ RID ShaderRD::version_create() {
version.dirty = true;
version.valid = false;
version.initialize_needed = true;
- version.variants = nullptr;
+ version.variants.clear();
+ version.variant_data.clear();
return version_owner.make_rid(version);
}
@@ -154,23 +157,25 @@ void ShaderRD::_initialize_version(Version *p_version) {
p_version->valid = false;
p_version->dirty = false;
- p_version->variants = memnew_arr(RID, variant_defines.size());
+ p_version->variants.resize_zeroed(variant_defines.size());
+ p_version->variant_data.resize(variant_defines.size());
+ p_version->group_compilation_tasks.resize(group_enabled.size());
+ p_version->group_compilation_tasks.fill(0);
}
void ShaderRD::_clear_version(Version *p_version) {
+ _compile_ensure_finished(p_version);
+
// Clear versions if they exist.
- if (p_version->variants) {
+ if (!p_version->variants.is_empty()) {
for (int i = 0; i < variant_defines.size(); i++) {
if (p_version->variants[i].is_valid()) {
RD::get_singleton()->free(p_version->variants[i]);
}
}
- memdelete_arr(p_version->variants);
- if (p_version->variant_data) {
- memdelete_arr(p_version->variant_data);
- }
- p_version->variants = nullptr;
+ p_version->variants.clear();
+ p_version->variant_data.clear();
}
}
@@ -227,8 +232,8 @@ void ShaderRD::_build_variant_code(StringBuilder &builder, uint32_t p_variant, c
}
}
-void ShaderRD::_compile_variant(uint32_t p_variant, const CompileData *p_data) {
- uint32_t variant = group_to_variant_map[p_data->group][p_variant];
+void ShaderRD::_compile_variant(uint32_t p_variant, CompileData p_data) {
+ uint32_t variant = group_to_variant_map[p_data.group][p_variant];
if (!variants_enabled[variant]) {
return; // Variant is disabled, return.
@@ -245,7 +250,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, const CompileData *p_data) {
//vertex stage
StringBuilder builder;
- _build_variant_code(builder, variant, p_data->version, stage_templates[STAGE_TYPE_VERTEX]);
+ _build_variant_code(builder, variant, p_data.version, stage_templates[STAGE_TYPE_VERTEX]);
current_source = builder.as_string();
RD::ShaderStageSPIRVData stage;
@@ -263,7 +268,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, const CompileData *p_data) {
current_stage = RD::SHADER_STAGE_FRAGMENT;
StringBuilder builder;
- _build_variant_code(builder, variant, p_data->version, stage_templates[STAGE_TYPE_FRAGMENT]);
+ _build_variant_code(builder, variant, p_data.version, stage_templates[STAGE_TYPE_FRAGMENT]);
current_source = builder.as_string();
RD::ShaderStageSPIRVData stage;
@@ -281,7 +286,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, const CompileData *p_data) {
current_stage = RD::SHADER_STAGE_COMPUTE;
StringBuilder builder;
- _build_variant_code(builder, variant, p_data->version, stage_templates[STAGE_TYPE_COMPUTE]);
+ _build_variant_code(builder, variant, p_data.version, stage_templates[STAGE_TYPE_COMPUTE]);
current_source = builder.as_string();
@@ -313,8 +318,8 @@ void ShaderRD::_compile_variant(uint32_t p_variant, const CompileData *p_data) {
{
MutexLock lock(variant_set_mutex);
- p_data->version->variants[variant] = RD::get_singleton()->shader_create_from_bytecode(shader_data, p_data->version->variants[variant]);
- p_data->version->variant_data[variant] = shader_data;
+ p_data.version->variants.write[variant] = RD::get_singleton()->shader_create_from_bytecode(shader_data, p_data.version->variants[variant]);
+ p_data.version->variant_data.write[variant] = shader_data;
}
}
@@ -443,14 +448,14 @@ bool ShaderRD::_load_from_cache(Version *p_version, int p_group) {
ERR_FAIL_COND_V(br != variant_size, false);
- p_version->variant_data[variant_id] = variant_bytes;
+ p_version->variant_data.write[variant_id] = variant_bytes;
}
for (uint32_t i = 0; i < variant_count; i++) {
int variant_id = group_to_variant_map[p_group][i];
if (!variants_enabled[variant_id]) {
MutexLock lock(variant_set_mutex);
- p_version->variants[variant_id] = RID();
+ p_version->variants.write[variant_id] = RID();
continue;
}
{
@@ -464,12 +469,10 @@ bool ShaderRD::_load_from_cache(Version *p_version, int p_group) {
ERR_FAIL_COND_V(shader.is_null(), false);
}
- p_version->variants[variant_id] = shader;
+ p_version->variants.write[variant_id] = shader;
}
}
- memdelete_arr(p_version->variant_data); //clear stages
- p_version->variant_data = nullptr;
p_version->valid = true;
return true;
}
@@ -491,48 +494,51 @@ void ShaderRD::_save_to_cache(Version *p_version, int p_group) {
}
void ShaderRD::_allocate_placeholders(Version *p_version, int p_group) {
- ERR_FAIL_NULL(p_version->variants);
+ ERR_FAIL_COND(p_version->variants.is_empty());
+
for (uint32_t i = 0; i < group_to_variant_map[p_group].size(); i++) {
int variant_id = group_to_variant_map[p_group][i];
RID shader = RD::get_singleton()->shader_create_placeholder();
{
MutexLock lock(variant_set_mutex);
- p_version->variants[variant_id] = shader;
+ p_version->variants.write[variant_id] = shader;
}
}
}
// Try to compile all variants for a given group.
// Will skip variants that are disabled.
-void ShaderRD::_compile_version(Version *p_version, int p_group) {
+void ShaderRD::_compile_version_start(Version *p_version, int p_group) {
if (!group_enabled[p_group]) {
return;
}
- typedef Vector<uint8_t> ShaderStageData;
- p_version->variant_data = memnew_arr(ShaderStageData, variant_defines.size());
-
p_version->dirty = false;
+#if ENABLE_SHADER_CACHE
if (shader_cache_dir_valid) {
if (_load_from_cache(p_version, p_group)) {
return;
}
}
+#endif
CompileData compile_data;
compile_data.version = p_version;
compile_data.group = p_group;
-#if 1
- WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &ShaderRD::_compile_variant, &compile_data, group_to_variant_map[p_group].size(), -1, true, SNAME("ShaderCompilation"));
- WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &ShaderRD::_compile_variant, compile_data, group_to_variant_map[p_group].size(), -1, true, SNAME("ShaderCompilation"));
+ p_version->group_compilation_tasks.write[p_group] = group_task;
+}
-#else
- for (uint32_t i = 0; i < group_to_variant_map[p_group].size(); i++) {
- _compile_variant(i, &compile_data);
+void ShaderRD::_compile_version_end(Version *p_version, int p_group) {
+ if (p_version->group_compilation_tasks.size() <= p_group || p_version->group_compilation_tasks[p_group] == 0) {
+ return;
}
-#endif
+
+ WorkerThreadPool::GroupID group_task = p_version->group_compilation_tasks[p_group];
+ WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
+ p_version->group_compilation_tasks.write[p_group] = 0;
bool all_valid = true;
@@ -557,29 +563,35 @@ void ShaderRD::_compile_version(Version *p_version, int p_group) {
RD::get_singleton()->free(p_version->variants[i]);
}
}
- memdelete_arr(p_version->variants);
- if (p_version->variant_data) {
- memdelete_arr(p_version->variant_data);
- }
- p_version->variants = nullptr;
- p_version->variant_data = nullptr;
+
+ p_version->variants.clear();
+ p_version->variant_data.clear();
return;
- } else if (shader_cache_dir_valid) {
- // Save shader cache.
+ }
+#if ENABLE_SHADER_CACHE
+ else if (shader_cache_dir_valid) {
_save_to_cache(p_version, p_group);
}
-
- memdelete_arr(p_version->variant_data); //clear stages
- p_version->variant_data = nullptr;
+#endif
p_version->valid = true;
}
+void ShaderRD::_compile_ensure_finished(Version *p_version) {
+ // Wait for compilation of existing groups if necessary.
+ for (int i = 0; i < group_enabled.size(); i++) {
+ _compile_version_end(p_version, i);
+ }
+}
+
void ShaderRD::version_set_code(RID p_version, const HashMap<String, String> &p_code, const String &p_uniforms, const String &p_vertex_globals, const String &p_fragment_globals, const Vector<String> &p_custom_defines) {
ERR_FAIL_COND(is_compute);
Version *version = version_owner.get_or_null(p_version);
ERR_FAIL_NULL(version);
+
+ _compile_ensure_finished(version);
+
version->vertex_globals = p_vertex_globals.utf8();
version->fragment_globals = p_fragment_globals.utf8();
version->uniforms = p_uniforms.utf8();
@@ -601,7 +613,7 @@ void ShaderRD::version_set_code(RID p_version, const HashMap<String, String> &p_
_allocate_placeholders(version, i);
continue;
}
- _compile_version(version, i);
+ _compile_version_start(version, i);
}
version->initialize_needed = false;
}
@@ -613,6 +625,8 @@ void ShaderRD::version_set_compute_code(RID p_version, const HashMap<String, Str
Version *version = version_owner.get_or_null(p_version);
ERR_FAIL_NULL(version);
+ _compile_ensure_finished(version);
+
version->compute_globals = p_compute_globals.utf8();
version->uniforms = p_uniforms.utf8();
@@ -634,7 +648,7 @@ void ShaderRD::version_set_compute_code(RID p_version, const HashMap<String, Str
_allocate_placeholders(version, i);
continue;
}
- _compile_version(version, i);
+ _compile_version_start(version, i);
}
version->initialize_needed = false;
}
@@ -651,10 +665,12 @@ bool ShaderRD::version_is_valid(RID p_version) {
_allocate_placeholders(version, i);
continue;
}
- _compile_version(version, i);
+ _compile_version_start(version, i);
}
}
+ _compile_ensure_finished(version);
+
return version->valid;
}
@@ -696,7 +712,7 @@ void ShaderRD::enable_group(int p_group) {
version_owner.get_owned_list(&all_versions);
for (const RID &E : all_versions) {
Version *version = version_owner.get_or_null(E);
- _compile_version(version, p_group);
+ _compile_version_start(version, p_group);
}
}
@@ -735,6 +751,7 @@ void ShaderRD::initialize(const Vector<String> &p_variant_defines, const String
for (int i = 0; i < p_variant_defines.size(); i++) {
variant_defines.push_back(VariantDefine(0, p_variant_defines[i], true));
variants_enabled.push_back(true);
+ variant_to_group.push_back(0);
group_to_variant_map[0].push_back(i);
}
@@ -796,6 +813,7 @@ void ShaderRD::initialize(const Vector<VariantDefine> &p_variant_defines, const
// Fill variant array.
variant_defines.push_back(p_variant_defines[i]);
variants_enabled.push_back(true);
+ variant_to_group.push_back(p_variant_defines[i].group);
// Map variant array index to group id, so we can iterate over groups later.
if (!group_to_variant_map.has(p_variant_defines[i].group)) {
diff --git a/servers/rendering/renderer_rd/shader_rd.h b/servers/rendering/renderer_rd/shader_rd.h
index 688092d604..90e41947b9 100644
--- a/servers/rendering/renderer_rd/shader_rd.h
+++ b/servers/rendering/renderer_rd/shader_rd.h
@@ -59,6 +59,7 @@ private:
CharString general_defines;
Vector<VariantDefine> variant_defines;
Vector<bool> variants_enabled;
+ Vector<uint32_t> variant_to_group;
HashMap<int, LocalVector<int>> group_to_variant_map;
Vector<bool> group_enabled;
@@ -69,9 +70,10 @@ private:
CharString fragment_globals;
HashMap<StringName, CharString> code_sections;
Vector<CharString> custom_defines;
+ Vector<WorkerThreadPool::GroupID> group_compilation_tasks;
- Vector<uint8_t> *variant_data = nullptr;
- RID *variants = nullptr; // Same size as variant defines.
+ Vector<Vector<uint8_t>> variant_data;
+ Vector<RID> variants;
bool valid;
bool dirty;
@@ -85,11 +87,13 @@ private:
int group = 0;
};
- void _compile_variant(uint32_t p_variant, const CompileData *p_data);
+ void _compile_variant(uint32_t p_variant, CompileData p_data);
void _initialize_version(Version *p_version);
void _clear_version(Version *p_version);
- void _compile_version(Version *p_version, int p_group);
+ void _compile_version_start(Version *p_version, int p_group);
+ void _compile_version_end(Version *p_version, int p_group);
+ void _compile_ensure_finished(Version *p_version);
void _allocate_placeholders(Version *p_version, int p_group);
RID_Owner<Version> version_owner;
@@ -172,10 +176,15 @@ public:
_allocate_placeholders(version, i);
continue;
}
- _compile_version(version, i);
+ _compile_version_start(version, i);
}
}
+ uint32_t group = variant_to_group[p_variant];
+ if (version->group_compilation_tasks[group] != 0) {
+ _compile_version_end(version, group);
+ }
+
if (!version->valid) {
return RID();
}
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 704aafdfa5..dafcce37ad 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -193,9 +193,7 @@ void main() {
}
#endif // USE_ATTRIBUTES
-#ifdef USE_POINT_SIZE
float point_size = 1.0;
-#endif
#ifdef USE_WORLD_VERTEX_COORDS
vertex = (model_matrix * vec4(vertex, 0.0, 1.0)).xy;
@@ -368,8 +366,6 @@ float map_ninepatch_axis(float pixel, float draw_size, float tex_pixel_size, flo
#endif
-#ifdef USE_LIGHTING
-
vec3 light_normal_compute(vec3 light_vec, vec3 normal, vec3 base_color, vec3 light_color, vec4 specular_shininess, bool specular_shininess_used) {
float cNdotL = max(0.0, dot(normal, light_vec));
@@ -459,8 +455,6 @@ void light_blend_compute(uint light_base, vec4 light_color, inout vec3 color) {
}
}
-#endif
-
float msdf_median(float r, float g, float b, float a) {
return min(max(min(r, g), min(max(r, g), b)), a);
}
@@ -493,7 +487,8 @@ void main() {
#endif
if (bool(draw_data.flags & FLAGS_CLIP_RECT_UV)) {
- uv = clamp(uv, draw_data.src_rect.xy, draw_data.src_rect.xy + abs(draw_data.src_rect.zw));
+ vec2 half_texpixel = draw_data.color_texture_pixel_size * 0.5;
+ uv = clamp(uv, draw_data.src_rect.xy + half_texpixel, draw_data.src_rect.xy + abs(draw_data.src_rect.zw) - half_texpixel);
}
#endif
@@ -533,7 +528,7 @@ void main() {
color *= texture(sampler2D(color_texture, texture_sampler), uv);
}
- uint light_count = bitfieldExtract(draw_data.flags, FLAGS_LIGHT_COUNT_SHIFT, 4); //max 16 lights
+ uint light_count = bitfieldExtract(draw_data.flags, FLAGS_LIGHT_COUNT_SHIFT, 4); //max 15 lights
bool using_light = (light_count + canvas_data.directional_light_count) > 0;
vec3 normal;
@@ -617,134 +612,135 @@ void main() {
color *= canvas_data.canvas_modulation;
#endif
-#if defined(USE_LIGHTING) && !defined(MODE_UNSHADED)
-
- // Directional Lights
+#if !defined(MODE_UNSHADED)
+ if (sc_use_lighting()) {
+ // Directional Lights
- for (uint i = 0; i < canvas_data.directional_light_count; i++) {
- uint light_base = i;
+ for (uint i = 0; i < canvas_data.directional_light_count; i++) {
+ uint light_base = i;
- vec2 direction = light_array.data[light_base].position;
- vec4 light_color = light_array.data[light_base].color;
+ vec2 direction = light_array.data[light_base].position;
+ vec4 light_color = light_array.data[light_base].color;
#ifdef LIGHT_CODE_USED
- vec4 shadow_modulate = vec4(1.0);
- light_color = light_compute(light_vertex, vec3(direction, light_array.data[light_base].height), normal, light_color, light_color.a, specular_shininess, shadow_modulate, screen_uv, uv, base_color, true);
+ vec4 shadow_modulate = vec4(1.0);
+ light_color = light_compute(light_vertex, vec3(direction, light_array.data[light_base].height), normal, light_color, light_color.a, specular_shininess, shadow_modulate, screen_uv, uv, base_color, true);
#else
- if (normal_used) {
- vec3 light_vec = normalize(mix(vec3(direction, 0.0), vec3(0, 0, 1), light_array.data[light_base].height));
- light_color.rgb = light_normal_compute(light_vec, normal, base_color.rgb, light_color.rgb, specular_shininess, specular_shininess_used);
- } else {
- light_color.rgb *= base_color.rgb;
- }
+ if (normal_used) {
+ vec3 light_vec = normalize(mix(vec3(direction, 0.0), vec3(0, 0, 1), light_array.data[light_base].height));
+ light_color.rgb = light_normal_compute(light_vec, normal, base_color.rgb, light_color.rgb, specular_shininess, specular_shininess_used);
+ } else {
+ light_color.rgb *= base_color.rgb;
+ }
#endif
- if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
- vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
+ if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
+ vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
- vec4 shadow_uv = vec4(shadow_pos.x, light_array.data[light_base].shadow_y_ofs, shadow_pos.y * light_array.data[light_base].shadow_zfar_inv, 1.0);
+ vec4 shadow_uv = vec4(shadow_pos.x, light_array.data[light_base].shadow_y_ofs, shadow_pos.y * light_array.data[light_base].shadow_zfar_inv, 1.0);
- light_color = light_shadow_compute(light_base, light_color, shadow_uv
+ light_color = light_shadow_compute(light_base, light_color, shadow_uv
#ifdef LIGHT_CODE_USED
- ,
- shadow_modulate.rgb
+ ,
+ shadow_modulate.rgb
#endif
- );
- }
+ );
+ }
- light_blend_compute(light_base, light_color, color.rgb);
+ light_blend_compute(light_base, light_color, color.rgb);
#ifdef MODE_LIGHT_ONLY
- light_only_alpha += light_color.a;
+ light_only_alpha += light_color.a;
#endif
- }
+ }
- // Positional Lights
+ // Positional Lights
- for (uint i = 0; i < MAX_LIGHTS_PER_ITEM; i++) {
- if (i >= light_count) {
- break;
- }
- uint light_base = bitfieldExtract(draw_data.lights[i >> 2], (int(i) & 0x3) * 8, 8);
+ for (uint i = 0; i < MAX_LIGHTS_PER_ITEM; i++) {
+ if (i >= light_count) {
+ break;
+ }
+ uint light_base = bitfieldExtract(draw_data.lights[i >> 2], (int(i) & 0x3) * 8, 8);
- vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array.data[light_base].texture_matrix[0], light_array.data[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
- vec2 tex_uv_atlas = tex_uv * light_array.data[light_base].atlas_rect.zw + light_array.data[light_base].atlas_rect.xy;
+ vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array.data[light_base].texture_matrix[0], light_array.data[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
+ vec2 tex_uv_atlas = tex_uv * light_array.data[light_base].atlas_rect.zw + light_array.data[light_base].atlas_rect.xy;
- if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
- //if outside the light texture, light color is zero
- continue;
- }
+ if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
+ //if outside the light texture, light color is zero
+ continue;
+ }
- vec4 light_color = textureLod(sampler2D(atlas_texture, texture_sampler), tex_uv_atlas, 0.0);
- vec4 light_base_color = light_array.data[light_base].color;
+ vec4 light_color = textureLod(sampler2D(atlas_texture, texture_sampler), tex_uv_atlas, 0.0);
+ vec4 light_base_color = light_array.data[light_base].color;
#ifdef LIGHT_CODE_USED
- vec4 shadow_modulate = vec4(1.0);
- vec3 light_position = vec3(light_array.data[light_base].position, light_array.data[light_base].height);
+ vec4 shadow_modulate = vec4(1.0);
+ vec3 light_position = vec3(light_array.data[light_base].position, light_array.data[light_base].height);
- light_color.rgb *= light_base_color.rgb;
- light_color = light_compute(light_vertex, light_position, normal, light_color, light_base_color.a, specular_shininess, shadow_modulate, screen_uv, uv, base_color, false);
+ light_color.rgb *= light_base_color.rgb;
+ light_color = light_compute(light_vertex, light_position, normal, light_color, light_base_color.a, specular_shininess, shadow_modulate, screen_uv, uv, base_color, false);
#else
- light_color.rgb *= light_base_color.rgb * light_base_color.a;
+ light_color.rgb *= light_base_color.rgb * light_base_color.a;
- if (normal_used) {
- vec3 light_pos = vec3(light_array.data[light_base].position, light_array.data[light_base].height);
- vec3 pos = light_vertex;
- vec3 light_vec = normalize(light_pos - pos);
+ if (normal_used) {
+ vec3 light_pos = vec3(light_array.data[light_base].position, light_array.data[light_base].height);
+ vec3 pos = light_vertex;
+ vec3 light_vec = normalize(light_pos - pos);
- light_color.rgb = light_normal_compute(light_vec, normal, base_color.rgb, light_color.rgb, specular_shininess, specular_shininess_used);
- } else {
- light_color.rgb *= base_color.rgb;
- }
+ light_color.rgb = light_normal_compute(light_vec, normal, base_color.rgb, light_color.rgb, specular_shininess, specular_shininess_used);
+ } else {
+ light_color.rgb *= base_color.rgb;
+ }
#endif
- if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
- vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
-
- vec2 pos_norm = normalize(shadow_pos);
- vec2 pos_abs = abs(pos_norm);
- vec2 pos_box = pos_norm / max(pos_abs.x, pos_abs.y);
- vec2 pos_rot = pos_norm * mat2(vec2(0.7071067811865476, -0.7071067811865476), vec2(0.7071067811865476, 0.7071067811865476)); //is there a faster way to 45 degrees rot?
- float tex_ofs;
- float distance;
- if (pos_rot.y > 0) {
- if (pos_rot.x > 0) {
- tex_ofs = pos_box.y * 0.125 + 0.125;
- distance = shadow_pos.x;
+ if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
+ vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
+
+ vec2 pos_norm = normalize(shadow_pos);
+ vec2 pos_abs = abs(pos_norm);
+ vec2 pos_box = pos_norm / max(pos_abs.x, pos_abs.y);
+ vec2 pos_rot = pos_norm * mat2(vec2(0.7071067811865476, -0.7071067811865476), vec2(0.7071067811865476, 0.7071067811865476)); //is there a faster way to 45 degrees rot?
+ float tex_ofs;
+ float distance;
+ if (pos_rot.y > 0) {
+ if (pos_rot.x > 0) {
+ tex_ofs = pos_box.y * 0.125 + 0.125;
+ distance = shadow_pos.x;
+ } else {
+ tex_ofs = pos_box.x * -0.125 + (0.25 + 0.125);
+ distance = shadow_pos.y;
+ }
} else {
- tex_ofs = pos_box.x * -0.125 + (0.25 + 0.125);
- distance = shadow_pos.y;
+ if (pos_rot.x < 0) {
+ tex_ofs = pos_box.y * -0.125 + (0.5 + 0.125);
+ distance = -shadow_pos.x;
+ } else {
+ tex_ofs = pos_box.x * 0.125 + (0.75 + 0.125);
+ distance = -shadow_pos.y;
+ }
}
- } else {
- if (pos_rot.x < 0) {
- tex_ofs = pos_box.y * -0.125 + (0.5 + 0.125);
- distance = -shadow_pos.x;
- } else {
- tex_ofs = pos_box.x * 0.125 + (0.75 + 0.125);
- distance = -shadow_pos.y;
- }
- }
- distance *= light_array.data[light_base].shadow_zfar_inv;
+ distance *= light_array.data[light_base].shadow_zfar_inv;
- //float distance = length(shadow_pos);
- vec4 shadow_uv = vec4(tex_ofs, light_array.data[light_base].shadow_y_ofs, distance, 1.0);
+ //float distance = length(shadow_pos);
+ vec4 shadow_uv = vec4(tex_ofs, light_array.data[light_base].shadow_y_ofs, distance, 1.0);
- light_color = light_shadow_compute(light_base, light_color, shadow_uv
+ light_color = light_shadow_compute(light_base, light_color, shadow_uv
#ifdef LIGHT_CODE_USED
- ,
- shadow_modulate.rgb
+ ,
+ shadow_modulate.rgb
#endif
- );
- }
+ );
+ }
- light_blend_compute(light_base, light_color, color.rgb);
+ light_blend_compute(light_base, light_color, color.rgb);
#ifdef MODE_LIGHT_ONLY
- light_only_alpha += light_color.a;
+ light_only_alpha += light_color.a;
#endif
+ }
}
#endif
diff --git a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
index 7cf5b4576e..ead8c459a4 100644
--- a/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas_uniforms_inc.glsl
@@ -23,14 +23,14 @@
#define FLAGS_LIGHT_COUNT_SHIFT 20
-#define FLAGS_DEFAULT_NORMAL_MAP_USED (1 << 26)
-#define FLAGS_DEFAULT_SPECULAR_MAP_USED (1 << 27)
+#define FLAGS_DEFAULT_NORMAL_MAP_USED (1 << 24)
+#define FLAGS_DEFAULT_SPECULAR_MAP_USED (1 << 25)
-#define FLAGS_USE_MSDF (1 << 28)
-#define FLAGS_USE_LCD (1 << 29)
+#define FLAGS_USE_MSDF (1 << 26)
+#define FLAGS_USE_LCD (1 << 27)
-#define FLAGS_FLIP_H (1 << 30)
-#define FLAGS_FLIP_V (1 << 31)
+#define FLAGS_FLIP_H (1 << 28)
+#define FLAGS_FLIP_V (1 << 29)
struct InstanceData {
vec2 world_x;
@@ -54,19 +54,38 @@ struct InstanceData {
uint lights[4];
};
-layout(set = 4, binding = 0, std430) restrict readonly buffer DrawData {
- InstanceData data[];
-}
-instances;
-
layout(push_constant, std430) uniform Params {
uint base_instance_index; // base index to instance data
- uint pad1;
+ uint sc_packed_0;
uint pad2;
uint pad3;
}
params;
+// Specialization constants.
+
+#ifdef UBERSHADER
+
+// Pull the constants from the draw call's push constants.
+uint sc_packed_0() {
+ return draw_call.sc_packed_0;
+}
+
+#else
+
+// Pull the constants from the pipeline's specialization constants.
+layout(constant_id = 0) const uint pso_sc_packed_0 = 0;
+
+uint sc_packed_0() {
+ return pso_sc_packed_0;
+}
+
+#endif
+
+bool sc_use_lighting() {
+ return ((sc_packed_0() >> 0) & 1U) != 0;
+}
+
// In vulkan, sets should always be ordered using the following logic:
// Lower Sets: Sets that change format and layout less often
// Higher sets: Sets that change format and layout very often
@@ -163,3 +182,8 @@ layout(set = 3, binding = 0) uniform texture2D color_texture;
layout(set = 3, binding = 1) uniform texture2D normal_texture;
layout(set = 3, binding = 2) uniform texture2D specular_texture;
layout(set = 3, binding = 3) uniform sampler texture_sampler;
+
+layout(set = 3, binding = 4, std430) restrict readonly buffer DrawData {
+ InstanceData data[];
+}
+instances;
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index 400451ec36..72236dcd9f 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -156,8 +156,30 @@ vec2 multiview_uv(vec2 uv) {
ivec2 multiview_uv(ivec2 uv) {
return uv;
}
+
#endif //USE_MULTIVIEW
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+layout(location = 12) highp out vec4 diffuse_light_interp;
+layout(location = 13) highp out vec4 specular_light_interp;
+
+#include "../scene_forward_vertex_lights_inc.glsl"
+
+void cluster_get_item_range(uint p_offset, out uint item_min, out uint item_max, out uint item_from, out uint item_to) {
+ uint item_min_max = cluster_buffer.data[p_offset];
+ item_min = item_min_max & 0xFFFFu;
+ item_max = item_min_max >> 16;
+
+ item_from = item_min >> 5;
+ item_to = (item_max == 0) ? 0 : ((item_max - 1) >> 5) + 1; //side effect of how it is stored, as item_max 0 means no elements
+}
+
+uint cluster_get_range_clip_mask(uint i, uint z_min, uint z_max) {
+ int local_min = clamp(int(z_min) - int(i) * 32, 0, 31);
+ int mask_width = min(int(z_max) - int(z_min), 32 - local_min);
+ return bitfieldInsert(uint(0), uint(0xFFFFFFFF), local_min, mask_width);
+}
+#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
invariant gl_Position;
#GLOBALS
@@ -488,6 +510,145 @@ void vertex_shader(vec3 vertex_input,
screen_pos = gl_Position;
#endif
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+ diffuse_light_interp = vec4(0.0);
+ specular_light_interp = vec4(0.0);
+
+#ifdef USE_MULTIVIEW
+ vec3 view = -normalize(vertex_interp - eye_offset);
+ vec2 clip_pos = clamp((combined_projected.xy / combined_projected.w) * 0.5 + 0.5, 0.0, 1.0);
+#else
+ vec3 view = -normalize(vertex_interp);
+ vec2 clip_pos = clamp((gl_Position.xy / gl_Position.w) * 0.5 + 0.5, 0.0, 1.0);
+#endif
+
+ uvec2 cluster_pos = uvec2(clip_pos / scene_data.screen_pixel_size) >> implementation_data.cluster_shift;
+ uint cluster_offset = (implementation_data.cluster_width * cluster_pos.y + cluster_pos.x) * (implementation_data.max_cluster_element_count_div_32 + 32);
+ uint cluster_z = uint(clamp((-vertex_interp.z / scene_data.z_far) * 32.0, 0.0, 31.0));
+
+ { //omni lights
+
+ uint cluster_omni_offset = cluster_offset;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_omni_offset + implementation_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_omni_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+ uint merged_mask = mask;
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1u << bit);
+ uint light_index = 32 * i + bit;
+
+ if (!bool(omni_lights.data[light_index].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ if (omni_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip
+ }
+
+ light_process_omni_vertex(light_index, vertex, view, normal, roughness,
+ diffuse_light_interp.rgb, specular_light_interp.rgb);
+ }
+ }
+ }
+
+ { //spot lights
+ uint cluster_spot_offset = cluster_offset + implementation_data.cluster_type_size;
+
+ uint item_min;
+ uint item_max;
+ uint item_from;
+ uint item_to;
+
+ cluster_get_item_range(cluster_spot_offset + implementation_data.max_cluster_element_count_div_32 + cluster_z, item_min, item_max, item_from, item_to);
+
+ for (uint i = item_from; i < item_to; i++) {
+ uint mask = cluster_buffer.data[cluster_spot_offset + i];
+ mask &= cluster_get_range_clip_mask(i, item_min, item_max);
+ uint merged_mask = mask;
+
+ while (merged_mask != 0) {
+ uint bit = findMSB(merged_mask);
+ merged_mask &= ~(1u << bit);
+
+ uint light_index = 32 * i + bit;
+
+ if (!bool(spot_lights.data[light_index].mask & instances.data[instance_index].layer_mask)) {
+ continue; //not masked
+ }
+
+ if (spot_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip
+ }
+
+ light_process_spot_vertex(light_index, vertex, view, normal, roughness,
+ diffuse_light_interp.rgb, specular_light_interp.rgb);
+ }
+ }
+ }
+
+ { // Directional light.
+
+ // We process the first directional light separately as it may have shadows.
+ vec3 directional_diffuse = vec3(0.0);
+ vec3 directional_specular = vec3(0.0);
+
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
+ continue; // Not masked, skip.
+ }
+
+ if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip.
+ }
+ if (i == 0) {
+ light_compute_vertex(normal, directional_lights.data[0].direction, view,
+ directional_lights.data[0].color * directional_lights.data[0].energy,
+ true, roughness,
+ directional_diffuse,
+ directional_specular);
+ } else {
+ light_compute_vertex(normal, directional_lights.data[i].direction, view,
+ directional_lights.data[i].color * directional_lights.data[i].energy,
+ true, roughness,
+ diffuse_light_interp.rgb,
+ specular_light_interp.rgb);
+ }
+ }
+
+ // Calculate the contribution from the shadowed light so we can scale the shadows accordingly.
+ float diff_avg = dot(diffuse_light_interp.rgb, vec3(0.33333));
+ float diff_dir_avg = dot(directional_diffuse, vec3(0.33333));
+ if (diff_avg > 0.0) {
+ diffuse_light_interp.a = diff_dir_avg / (diff_avg + diff_dir_avg);
+ } else {
+ diffuse_light_interp.a = 1.0;
+ }
+
+ diffuse_light_interp.rgb += directional_diffuse;
+
+ float spec_avg = dot(specular_light_interp.rgb, vec3(0.33333));
+ float spec_dir_avg = dot(directional_specular, vec3(0.33333));
+ if (spec_avg > 0.0) {
+ specular_light_interp.a = spec_dir_avg / (spec_avg + spec_dir_avg);
+ } else {
+ specular_light_interp.a = 1.0;
+ }
+
+ specular_light_interp.rgb += directional_specular;
+ }
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+
#ifdef MODE_RENDER_DEPTH
if (scene_data.pancake_shadows) {
if (gl_Position.z >= 0.9999) {
@@ -643,29 +804,6 @@ void main() {
#define SHADER_IS_SRGB false
#define SHADER_SPACE_FAR 0.0
-/* Specialization Constants (Toggles) */
-
-layout(constant_id = 0) const bool sc_use_forward_gi = false;
-layout(constant_id = 1) const bool sc_use_light_projector = false;
-layout(constant_id = 2) const bool sc_use_light_soft_shadows = false;
-layout(constant_id = 3) const bool sc_use_directional_soft_shadows = false;
-
-/* Specialization Constants (Values) */
-
-layout(constant_id = 6) const uint sc_soft_shadow_samples = 4;
-layout(constant_id = 7) const uint sc_penumbra_shadow_samples = 4;
-
-layout(constant_id = 8) const uint sc_directional_soft_shadow_samples = 4;
-layout(constant_id = 9) const uint sc_directional_penumbra_shadow_samples = 4;
-
-layout(constant_id = 10) const bool sc_decal_use_mipmaps = true;
-layout(constant_id = 11) const bool sc_projector_use_mipmaps = true;
-layout(constant_id = 12) const bool sc_use_depth_fog = false;
-layout(constant_id = 13) const bool sc_use_lightmap_bicubic_filter = false;
-
-// not used in clustered renderer but we share some code with the mobile renderer that requires this.
-const float sc_luminance_multiplier = 1.0;
-
#include "scene_forward_clustered_inc.glsl"
/* Varyings */
@@ -791,7 +929,10 @@ ivec2 multiview_uv(ivec2 uv) {
return uv;
}
#endif //USE_MULTIVIEW
-
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+layout(location = 12) highp in vec4 diffuse_light_interp;
+layout(location = 13) highp in vec4 specular_light_interp;
+#endif
//defines to keep compatibility with vertex
#ifdef USE_MULTIVIEW
@@ -917,7 +1058,7 @@ vec4 fog_process(vec3 vertex) {
float fog_amount = 0.0;
- if (sc_use_depth_fog) {
+ if (sc_use_depth_fog()) {
float fog_z = smoothstep(scene_data_block.data.fog_depth_begin, scene_data_block.data.fog_depth_end, length(vertex));
float fog_quad_amount = pow(fog_z, scene_data_block.data.fog_depth_curve) * scene_data_block.data.fog_density;
fog_amount = fog_quad_amount;
@@ -1305,7 +1446,7 @@ void fragment_shader(in SceneData scene_data) {
if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
//has albedo
vec4 decal_albedo;
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
} else {
decal_albedo = textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, 0.0);
@@ -1316,7 +1457,7 @@ void fragment_shader(in SceneData scene_data) {
if (decals.data[decal_index].normal_rect != vec4(0.0)) {
vec3 decal_normal;
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
decal_normal = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
} else {
decal_normal = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, 0.0).xyz;
@@ -1331,7 +1472,7 @@ void fragment_shader(in SceneData scene_data) {
if (decals.data[decal_index].orm_rect != vec4(0.0)) {
vec3 decal_orm;
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
decal_orm = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
} else {
decal_orm = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, 0.0).xyz;
@@ -1344,7 +1485,7 @@ void fragment_shader(in SceneData scene_data) {
if (decals.data[decal_index].emission_rect != vec4(0.0)) {
//emission is additive, so its independent from albedo
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
emission += textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].modulate.rgb * decals.data[decal_index].emission_energy * fade;
} else {
emission += textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, 0.0).xyz * decals.data[decal_index].modulate.rgb * decals.data[decal_index].emission_energy * fade;
@@ -1375,7 +1516,6 @@ void fragment_shader(in SceneData scene_data) {
vec3 specular_light = vec3(0.0, 0.0, 0.0);
vec3 diffuse_light = vec3(0.0, 0.0, 0.0);
vec3 ambient_light = vec3(0.0, 0.0, 0.0);
-
#ifndef MODE_UNSHADED
// Used in regular draw pass and when drawing SDFs for SDFGI and materials for VoxelGI.
emission *= scene_data.emissive_exposure_normalization;
@@ -1520,7 +1660,7 @@ void fragment_shader(in SceneData scene_data) {
vec3 lm_light_l1_0;
vec3 lm_light_l1p1;
- if (sc_use_lightmap_bicubic_filter) {
+ if (sc_use_lightmap_bicubic_filter()) {
lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
@@ -1541,7 +1681,7 @@ void fragment_shader(in SceneData scene_data) {
ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * en * 4.0);
} else {
- if (sc_use_lightmap_bicubic_filter) {
+ if (sc_use_lightmap_bicubic_filter()) {
ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
} else {
ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
@@ -1550,7 +1690,7 @@ void fragment_shader(in SceneData scene_data) {
}
#else
- if (sc_use_forward_gi && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_SDFGI)) { //has lightmap capture
+ if (sc_use_forward_gi() && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_SDFGI)) { //has lightmap capture
//make vertex orientation the world one, but still align to camera
vec3 cam_pos = mat3(scene_data.inv_view_matrix) * vertex;
@@ -1622,7 +1762,7 @@ void fragment_shader(in SceneData scene_data) {
}
}
- if (sc_use_forward_gi && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_VOXEL_GI)) { // process voxel_gi_instances
+ if (sc_use_forward_gi() && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_VOXEL_GI)) { // process voxel_gi_instances
uint index1 = instances.data[instance_index].gi_offset & 0xFFFF;
// Make vertex orientation the world one, but still align to camera.
vec3 cam_pos = mat3(scene_data.inv_view_matrix) * vertex;
@@ -1657,7 +1797,7 @@ void fragment_shader(in SceneData scene_data) {
ambient_light = amb_accum.rgb;
}
- if (!sc_use_forward_gi && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_GI_BUFFERS)) { //use GI buffers
+ if (!sc_use_forward_gi() && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_GI_BUFFERS)) { //use GI buffers
vec2 coord;
@@ -1836,6 +1976,11 @@ void fragment_shader(in SceneData scene_data) {
// LIGHTING
#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#ifdef USE_VERTEX_LIGHTING
+ diffuse_light += diffuse_light_interp.rgb;
+ specular_light += specular_light_interp.rgb * f0;
+#endif
+
{ // Directional light.
// Do shadow and lighting in two passes to reduce register pressure.
@@ -1843,10 +1988,15 @@ void fragment_shader(in SceneData scene_data) {
uint shadow0 = 0;
uint shadow1 = 0;
+#ifdef USE_VERTEX_LIGHTING
+ // Only process the first light's shadow for vertex lighting.
+ for (uint i = 0; i < 1; i++) {
+#else
for (uint i = 0; i < 8; i++) {
if (i >= scene_data.directional_light_count) {
break;
}
+#endif
if (!bool(directional_lights.data[i].mask & instances.data[instance_index].layer_mask)) {
continue; //not masked
@@ -1870,7 +2020,7 @@ void fragment_shader(in SceneData scene_data) {
m_var.xyz += normal_bias;
//version with soft shadows, more expensive
- if (sc_use_directional_soft_shadows && directional_lights.data[i].softshadow_angle > 0) {
+ if (sc_use_directional_soft_shadows() && directional_lights.data[i].softshadow_angle > 0) {
uint blend_count = 0;
const uint blend_max = directional_lights.data[i].blend_splits ? 2 : 1;
@@ -2044,6 +2194,11 @@ void fragment_shader(in SceneData scene_data) {
shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+#ifdef USE_VERTEX_LIGHTING
+ diffuse_light *= mix(1.0, shadow, diffuse_light_interp.a);
+ specular_light *= mix(1.0, shadow, specular_light_interp.a);
+#endif
+
#undef BIAS_FUNC
} // shadows
@@ -2055,6 +2210,8 @@ void fragment_shader(in SceneData scene_data) {
}
#endif // SHADOWS_DISABLED
+#ifndef USE_VERTEX_LIGHTING
+
for (uint i = 0; i < 8; i++) {
if (i >= scene_data.directional_light_count) {
break;
@@ -2144,7 +2301,7 @@ void fragment_shader(in SceneData scene_data) {
shadow = 1.0;
#endif
- float size_A = sc_use_directional_soft_shadows ? directional_lights.data[i].size : 0.0;
+ float size_A = sc_use_directional_soft_shadows() ? directional_lights.data[i].size : 0.0;
light_compute(normal, directional_lights.data[i].direction, normalize(view), size_A,
#ifndef DEBUG_DRAW_PSSM_SPLITS
@@ -2175,8 +2332,10 @@ void fragment_shader(in SceneData scene_data) {
diffuse_light,
specular_light);
}
+#endif // USE_VERTEX_LIGHTING
}
+#ifndef USE_VERTEX_LIGHTING
{ //omni lights
uint cluster_omni_offset = cluster_offset;
@@ -2320,6 +2479,8 @@ void fragment_shader(in SceneData scene_data) {
}
}
}
+#endif // !USE_VERTEX_LIGHTING
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
#ifdef USE_SHADOW_TO_OPACITY
#ifndef MODE_RENDER_DEPTH
@@ -2334,8 +2495,6 @@ void fragment_shader(in SceneData scene_data) {
#endif // !MODE_RENDER_DEPTH
#endif // USE_SHADOW_TO_OPACITY
-#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
-
#ifdef MODE_RENDER_DEPTH
#ifdef MODE_RENDER_SDF
@@ -2552,6 +2711,14 @@ void fragment_shader(in SceneData scene_data) {
}
void main() {
+#ifdef UBERSHADER
+ bool front_facing = gl_FrontFacing;
+ if (uc_cull_mode() == POLYGON_CULL_BACK && !front_facing) {
+ discard;
+ } else if (uc_cull_mode() == POLYGON_CULL_FRONT && front_facing) {
+ discard;
+ }
+#endif
#ifdef MODE_DUAL_PARABOLOID
if (dp_clip > 0.0)
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
index 03511aa3a8..9f68d59be2 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
@@ -37,9 +37,96 @@ layout(push_constant, std430) uniform DrawCall {
uint uv_offset;
uint multimesh_motion_vectors_current_offset;
uint multimesh_motion_vectors_previous_offset;
+#ifdef UBERSHADER
+ uint sc_packed_0;
+ uint sc_packed_1;
+ uint sc_packed_2;
+ uint uc_packed_0;
+#endif
}
draw_call;
+/* Specialization Constants */
+
+#ifdef UBERSHADER
+
+#define POLYGON_CULL_DISABLED 0
+#define POLYGON_CULL_FRONT 1
+#define POLYGON_CULL_BACK 2
+
+// Pull the constants from the draw call's push constants.
+uint sc_packed_0() {
+ return draw_call.sc_packed_0;
+}
+
+uint uc_cull_mode() {
+ return (draw_call.uc_packed_0 >> 0) & 3U;
+}
+
+#else
+
+// Pull the constants from the pipeline's specialization constants.
+layout(constant_id = 0) const uint pso_sc_packed_0 = 0;
+
+uint sc_packed_0() {
+ return pso_sc_packed_0;
+}
+
+#endif
+
+bool sc_use_forward_gi() {
+ return ((sc_packed_0() >> 0) & 1U) != 0;
+}
+
+bool sc_use_light_projector() {
+ return ((sc_packed_0() >> 1) & 1U) != 0;
+}
+
+bool sc_use_light_soft_shadows() {
+ return ((sc_packed_0() >> 2) & 1U) != 0;
+}
+
+bool sc_use_directional_soft_shadows() {
+ return ((sc_packed_0() >> 3) & 1U) != 0;
+}
+
+bool sc_decal_use_mipmaps() {
+ return ((sc_packed_0() >> 4) & 1U) != 0;
+}
+
+bool sc_projector_use_mipmaps() {
+ return ((sc_packed_0() >> 5) & 1U) != 0;
+}
+
+bool sc_use_depth_fog() {
+ return ((sc_packed_0() >> 6) & 1U) != 0;
+}
+
+bool sc_use_lightmap_bicubic_filter() {
+ return ((sc_packed_0() >> 7) & 1U) != 0;
+}
+
+uint sc_soft_shadow_samples() {
+ return (sc_packed_0() >> 8) & 15U;
+}
+
+uint sc_penumbra_shadow_samples() {
+ return (sc_packed_0() >> 12) & 15U;
+}
+
+uint sc_directional_soft_shadow_samples() {
+ return (sc_packed_0() >> 16) & 15U;
+}
+
+uint sc_directional_penumbra_shadow_samples() {
+ return (sc_packed_0() >> 20) & 15U;
+}
+
+float sc_luminance_multiplier() {
+ // Not used in clustered renderer but we share some code with the mobile renderer that requires this.
+ return 1.0;
+}
+
#define SDFGI_MAX_CASCADES 8
/* Set 0: Base Pass (never changes) */
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index 17c7b756c3..9d47711599 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -77,10 +77,6 @@ void axis_angle_to_tbn(vec3 axis, float angle, out vec3 tangent, out vec3 binorm
normal = omc_axis.zzz * axis + vec3(-s_axis.y, s_axis.x, c);
}
-/* Spec Constants */
-
-layout(constant_id = 17) const bool sc_is_multimesh = false;
-
/* Varyings */
layout(location = 0) highp out vec3 vertex_interp;
@@ -105,7 +101,16 @@ layout(location = 4) mediump out vec2 uv2_interp;
layout(location = 5) mediump out vec3 tangent_interp;
layout(location = 6) mediump out vec3 binormal_interp;
#endif
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+layout(location = 7) highp out vec4 diffuse_light_interp;
+layout(location = 8) highp out vec4 specular_light_interp;
+
+layout(constant_id = 9) const bool sc_disable_omni_lights = false;
+layout(constant_id = 10) const bool sc_disable_spot_lights = false;
+layout(constant_id = 12) const bool sc_disable_directional_lights = false;
+#include "../scene_forward_vertex_lights_inc.glsl"
+#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
#ifdef MATERIAL_UNIFORMS_USED
/* clang-format off */
layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms {
@@ -185,6 +190,7 @@ void main() {
mat4 model_matrix = instances.data[draw_call.instance_index].transform;
mat4 inv_view_matrix = scene_data.inv_view_matrix;
+
#ifdef USE_DOUBLE_PRECISION
vec3 model_precision = vec3(model_matrix[0][3], model_matrix[1][3], model_matrix[2][3]);
model_matrix[0][3] = 0.0;
@@ -206,7 +212,7 @@ void main() {
mat4 matrix;
mat4 read_model_matrix = model_matrix;
- if (sc_is_multimesh) {
+ if (sc_is_multimesh()) {
//multimesh, instances are for it
#ifdef USE_PARTICLE_TRAILS
@@ -402,7 +408,7 @@ void main() {
// Then we combine the translations from the model matrix and the view matrix using emulated doubles.
// We add the result to the vertex and ignore the final lost precision.
vec3 model_origin = model_matrix[3].xyz;
- if (sc_is_multimesh) {
+ if (sc_is_multimesh()) {
vertex = mat3(matrix) * vertex;
model_origin = double_add_vec3(model_origin, model_precision, matrix[3].xyz, vec3(0.0), model_precision);
}
@@ -448,6 +454,107 @@ void main() {
binormal_interp = binormal;
#endif
+// VERTEX LIGHTING
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+#ifdef USE_MULTIVIEW
+ vec3 view = -normalize(vertex_interp - eye_offset);
+#else
+ vec3 view = -normalize(vertex_interp);
+#endif
+
+ diffuse_light_interp = vec4(0.0);
+ specular_light_interp = vec4(0.0);
+
+ if (!sc_disable_omni_lights()) {
+ uint light_indices = instances.data[draw_call.instance_index].omni_lights.x;
+ for (uint i = 0; i < 8; i++) {
+ uint light_index = light_indices & 0xFF;
+ if (i == 3) {
+ light_indices = instances.data[draw_call.instance_index].omni_lights.y;
+ } else {
+ light_indices = light_indices >> 8;
+ }
+
+ if (light_index == 0xFF) {
+ break;
+ }
+
+ light_process_omni_vertex(light_index, vertex, view, normal, roughness,
+ diffuse_light_interp.rgb, specular_light_interp.rgb);
+ }
+ }
+
+ if (!sc_disable_spot_lights()) {
+ uint light_indices = instances.data[draw_call.instance_index].spot_lights.x;
+ for (uint i = 0; i < 8; i++) {
+ uint light_index = light_indices & 0xFF;
+ if (i == 3) {
+ light_indices = instances.data[draw_call.instance_index].spot_lights.y;
+ } else {
+ light_indices = light_indices >> 8;
+ }
+
+ if (light_index == 0xFF) {
+ break;
+ }
+
+ light_process_spot_vertex(light_index, vertex, view, normal, roughness,
+ diffuse_light_interp.rgb, specular_light_interp.rgb);
+ }
+ }
+
+ if (!sc_disable_directional_lights()) {
+ // We process the first directional light separately as it may have shadows.
+ vec3 directional_diffuse = vec3(0.0);
+ vec3 directional_specular = vec3(0.0);
+
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
+ if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
+ continue; // Not masked, skip.
+ }
+
+ if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+ continue; // Statically baked light and object uses lightmap, skip.
+ }
+ if (i == 0) {
+ light_compute_vertex(normal, directional_lights.data[0].direction, view,
+ directional_lights.data[0].color * directional_lights.data[0].energy,
+ true, roughness,
+ directional_diffuse,
+ directional_specular);
+ } else {
+ light_compute_vertex(normal, directional_lights.data[i].direction, view,
+ directional_lights.data[i].color * directional_lights.data[i].energy,
+ true, roughness,
+ diffuse_light_interp.rgb,
+ specular_light_interp.rgb);
+ }
+ }
+
+ // Calculate the contribution from the shadowed light so we can scale the shadows accordingly.
+ float diff_avg = dot(diffuse_light_interp.rgb, vec3(0.33333));
+ float diff_dir_avg = dot(directional_diffuse, vec3(0.33333));
+ if (diff_avg > 0.0) {
+ diffuse_light_interp.a = diff_dir_avg / (diff_avg + diff_dir_avg);
+ } else {
+ diffuse_light_interp.a = 1.0;
+ }
+
+ diffuse_light_interp.rgb += directional_diffuse;
+
+ float spec_avg = dot(specular_light_interp.rgb, vec3(0.33333));
+ float spec_dir_avg = dot(directional_specular, vec3(0.33333));
+ if (spec_avg > 0.0) {
+ specular_light_interp.a = spec_dir_avg / (spec_avg + spec_dir_avg);
+ } else {
+ specular_light_interp.a = 1.0;
+ }
+
+ specular_light_interp.rgb += directional_specular;
+ }
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+
#ifdef MODE_RENDER_DEPTH
#ifdef MODE_DUAL_PARABOLOID
@@ -501,41 +608,6 @@ void main() {
#define SHADER_IS_SRGB false
#define SHADER_SPACE_FAR 0.0
-/* Specialization Constants */
-
-#if !defined(MODE_RENDER_DEPTH)
-
-#if !defined(MODE_UNSHADED)
-
-layout(constant_id = 0) const bool sc_use_light_projector = false;
-layout(constant_id = 1) const bool sc_use_light_soft_shadows = false;
-layout(constant_id = 2) const bool sc_use_directional_soft_shadows = false;
-
-layout(constant_id = 3) const uint sc_soft_shadow_samples = 4;
-layout(constant_id = 4) const uint sc_penumbra_shadow_samples = 4;
-
-layout(constant_id = 5) const uint sc_directional_soft_shadow_samples = 4;
-layout(constant_id = 6) const uint sc_directional_penumbra_shadow_samples = 4;
-
-layout(constant_id = 8) const bool sc_projector_use_mipmaps = true;
-
-layout(constant_id = 9) const bool sc_disable_omni_lights = false;
-layout(constant_id = 10) const bool sc_disable_spot_lights = false;
-layout(constant_id = 11) const bool sc_disable_reflection_probes = false;
-layout(constant_id = 12) const bool sc_disable_directional_lights = false;
-layout(constant_id = 18) const bool sc_use_lightmap_bicubic_filter = false;
-
-#endif //!MODE_UNSHADED
-
-layout(constant_id = 7) const bool sc_decal_use_mipmaps = true;
-layout(constant_id = 13) const bool sc_disable_decals = false;
-layout(constant_id = 14) const bool sc_disable_fog = false;
-layout(constant_id = 16) const bool sc_use_depth_fog = false;
-
-#endif //!MODE_RENDER_DEPTH
-
-layout(constant_id = 15) const float sc_luminance_multiplier = 2.0;
-
/* Include our forward mobile UBOs definitions etc. */
#include "scene_forward_mobile_inc.glsl"
@@ -564,6 +636,11 @@ layout(location = 5) mediump in vec3 tangent_interp;
layout(location = 6) mediump in vec3 binormal_interp;
#endif
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
+layout(location = 7) highp in vec4 diffuse_light_interp;
+layout(location = 8) highp in vec4 specular_light_interp;
+#endif
+
#ifdef MODE_DUAL_PARABOLOID
layout(location = 9) highp in float dp_clip;
@@ -709,7 +786,7 @@ layout(location = 0) out mediump vec4 frag_color;
#include "../scene_forward_aa_inc.glsl"
-#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) // && !defined(USE_VERTEX_LIGHTING)
// Default to SPECULAR_SCHLICK_GGX.
#if !defined(SPECULAR_DISABLED) && !defined(SPECULAR_SCHLICK_GGX) && !defined(SPECULAR_TOON)
@@ -718,7 +795,7 @@ layout(location = 0) out mediump vec4 frag_color;
#include "../scene_forward_lights_inc.glsl"
-#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && !defined(USE_VERTEX_LIGHTING)
#ifndef MODE_RENDER_DEPTH
@@ -759,7 +836,7 @@ vec4 fog_process(vec3 vertex) {
float fog_amount = 0.0;
- if (sc_use_depth_fog) {
+ if (sc_use_depth_fog()) {
float fog_z = smoothstep(scene_data_block.data.fog_depth_begin, scene_data_block.data.fog_depth_end, length(vertex));
float fog_quad_amount = pow(fog_z, scene_data_block.data.fog_depth_curve) * scene_data_block.data.fog_density;
fog_amount = fog_quad_amount;
@@ -785,6 +862,14 @@ vec4 fog_process(vec3 vertex) {
#define scene_data scene_data_block.data
void main() {
+#ifdef UBERSHADER
+ bool front_facing = gl_FrontFacing;
+ if (uc_cull_mode() == POLYGON_CULL_BACK && !front_facing) {
+ discard;
+ } else if (uc_cull_mode() == POLYGON_CULL_FRONT && front_facing) {
+ discard;
+ }
+#endif
#ifdef MODE_DUAL_PARABOLOID
if (dp_clip > 0.0)
@@ -1010,7 +1095,7 @@ void main() {
// to maximize VGPR usage
// Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
- if (!sc_disable_fog && scene_data.fog_enabled) {
+ if (!sc_disable_fog() && scene_data.fog_enabled) {
fog = fog_process(vertex);
}
@@ -1029,7 +1114,7 @@ void main() {
vec3 vertex_ddx = dFdx(vertex);
vec3 vertex_ddy = dFdy(vertex);
- if (!sc_disable_decals) { //Decals
+ if (!sc_disable_decals()) { //Decals
// must implement
uint decal_indices = instances.data[draw_call.instance_index].decals.x;
@@ -1067,7 +1152,7 @@ void main() {
if (decals.data[decal_index].albedo_rect != vec4(0.0)) {
//has albedo
vec4 decal_albedo;
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
decal_albedo = textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, ddx * decals.data[decal_index].albedo_rect.zw, ddy * decals.data[decal_index].albedo_rect.zw);
} else {
decal_albedo = textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].albedo_rect.zw + decals.data[decal_index].albedo_rect.xy, 0.0);
@@ -1078,7 +1163,7 @@ void main() {
if (decals.data[decal_index].normal_rect != vec4(0.0)) {
vec3 decal_normal;
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
decal_normal = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, ddx * decals.data[decal_index].normal_rect.zw, ddy * decals.data[decal_index].normal_rect.zw).xyz;
} else {
decal_normal = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].normal_rect.zw + decals.data[decal_index].normal_rect.xy, 0.0).xyz;
@@ -1093,7 +1178,7 @@ void main() {
if (decals.data[decal_index].orm_rect != vec4(0.0)) {
vec3 decal_orm;
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
decal_orm = textureGrad(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, ddx * decals.data[decal_index].orm_rect.zw, ddy * decals.data[decal_index].orm_rect.zw).xyz;
} else {
decal_orm = textureLod(sampler2D(decal_atlas, decal_sampler), uv_local.xz * decals.data[decal_index].orm_rect.zw + decals.data[decal_index].orm_rect.xy, 0.0).xyz;
@@ -1106,7 +1191,7 @@ void main() {
if (decals.data[decal_index].emission_rect != vec4(0.0)) {
//emission is additive, so its independent from albedo
- if (sc_decal_use_mipmaps) {
+ if (sc_decal_use_mipmaps()) {
emission += textureGrad(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, ddx * decals.data[decal_index].emission_rect.zw, ddy * decals.data[decal_index].emission_rect.zw).xyz * decals.data[decal_index].emission_energy * fade;
} else {
emission += textureLod(sampler2D(decal_atlas_srgb, decal_sampler), uv_local.xz * decals.data[decal_index].emission_rect.zw + decals.data[decal_index].emission_rect.xy, 0.0).xyz * decals.data[decal_index].emission_energy * fade;
@@ -1170,7 +1255,7 @@ void main() {
specular_light = textureLod(samplerCube(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), ref_vec, sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb;
#endif //USE_RADIANCE_CUBEMAP_ARRAY
- specular_light *= sc_luminance_multiplier;
+ specular_light *= sc_luminance_multiplier();
specular_light *= scene_data.IBL_exposure_normalization;
specular_light *= horizon * horizon;
specular_light *= scene_data.ambient_light_color_energy.a;
@@ -1192,7 +1277,7 @@ void main() {
#else
vec3 cubemap_ambient = textureLod(samplerCube(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), ambient_dir, MAX_ROUGHNESS_LOD).rgb;
#endif //USE_RADIANCE_CUBEMAP_ARRAY
- cubemap_ambient *= sc_luminance_multiplier;
+ cubemap_ambient *= sc_luminance_multiplier();
cubemap_ambient *= scene_data.IBL_exposure_normalization;
ambient_light = mix(ambient_light, cubemap_ambient * scene_data.ambient_light_color_energy.a, scene_data.ambient_color_sky_mix);
}
@@ -1278,7 +1363,7 @@ void main() {
vec3 lm_light_l1_0;
vec3 lm_light_l1p1;
- if (sc_use_lightmap_bicubic_filter) {
+ if (sc_use_lightmap_bicubic_filter()) {
lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0;
@@ -1298,7 +1383,7 @@ void main() {
ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * exposure_normalization * 4.0);
ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * exposure_normalization * 4.0);
} else {
- if (sc_use_lightmap_bicubic_filter) {
+ if (sc_use_lightmap_bicubic_filter()) {
ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
} else {
ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
@@ -1312,7 +1397,7 @@ void main() {
// skipping ssao, do we remove ssao totally?
- if (!sc_disable_reflection_probes) { //Reflection probes
+ if (!sc_disable_reflection_probes()) { //Reflection probes
vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0);
@@ -1401,17 +1486,23 @@ void main() {
// LIGHTING
#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#ifdef USE_VERTEX_LIGHTING
+ diffuse_light += diffuse_light_interp.rgb;
+ specular_light += specular_light_interp.rgb * f0;
+#endif
- if (!sc_disable_directional_lights) { //directional light
+ if (!sc_disable_directional_lights()) { //directional light
#ifndef SHADOWS_DISABLED
// Do shadow and lighting in two passes to reduce register pressure
uint shadow0 = 0;
uint shadow1 = 0;
- for (uint i = 0; i < 8; i++) {
- if (i >= scene_data.directional_light_count) {
- break;
- }
+#ifdef USE_VERTEX_LIGHTING
+ // Only process the first light's shadow for vertex lighting.
+ for (uint i = 0; i < 1; i++) {
+#else
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
+#endif
if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
continue; //not masked
@@ -1419,164 +1510,6 @@ void main() {
float shadow = 1.0;
- // Directional light shadow code is basically the same as forward clustered at this point in time minus `LIGHT_TRANSMITTANCE_USED` support.
- // Not sure if there is a reason to change this seeing directional lights are part of our global data
- // Should think about whether we may want to move this code into an include file or function??
-
-#ifdef USE_SOFT_SHADOWS
- //version with soft shadows, more expensive
- if (directional_lights.data[i].shadow_opacity > 0.001) {
- float depth_z = -vertex.z;
-
- vec4 pssm_coord;
- vec3 light_dir = directional_lights.data[i].direction;
-
-#define BIAS_FUNC(m_var, m_idx) \
- m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx]; \
- vec3 normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp)))) * directional_lights.data[i].shadow_normal_bias[m_idx]; \
- normal_bias -= light_dir * dot(light_dir, normal_bias); \
- m_var.xyz += normal_bias;
-
- if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
- vec4 v = vec4(vertex, 1.0);
-
- BIAS_FUNC(v, 0)
-
- pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
- pssm_coord /= pssm_coord.w;
-
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.x;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale1 * test_radius;
- shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
- } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
- vec4 v = vec4(vertex, 1.0);
-
- BIAS_FUNC(v, 1)
-
- pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
- pssm_coord /= pssm_coord.w;
-
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.y;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
- shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
- } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
- vec4 v = vec4(vertex, 1.0);
-
- BIAS_FUNC(v, 2)
-
- pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
- pssm_coord /= pssm_coord.w;
-
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.z;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
- shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
- } else {
- vec4 v = vec4(vertex, 1.0);
-
- BIAS_FUNC(v, 3)
-
- pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
- pssm_coord /= pssm_coord.w;
-
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.w;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
- shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
- }
-
- if (directional_lights.data[i].blend_splits) {
- float pssm_blend;
- float shadow2;
-
- if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
- vec4 v = vec4(vertex, 1.0);
- BIAS_FUNC(v, 1)
- pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
- pssm_coord /= pssm_coord.w;
-
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.y;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
- shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
-
- pssm_blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
- } else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
- vec4 v = vec4(vertex, 1.0);
- BIAS_FUNC(v, 2)
- pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
- pssm_coord /= pssm_coord.w;
-
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.z;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
- shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
-
- pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
- } else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
- vec4 v = vec4(vertex, 1.0);
- BIAS_FUNC(v, 3)
- pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
- pssm_coord /= pssm_coord.w;
- if (directional_lights.data[i].softshadow_angle > 0) {
- float range_pos = dot(directional_lights.data[i].direction, v.xyz);
- float range_begin = directional_lights.data[i].shadow_range_begin.w;
- float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
- vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
- shadow2 = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
- } else {
- shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale, pssm_coord, scene_data.taa_frame_count);
- }
-
- pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
- } else {
- pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
- }
-
- pssm_blend = sqrt(pssm_blend);
-
- shadow = mix(shadow, shadow2, pssm_blend);
- }
-
- shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
-
-#undef BIAS_FUNC
- }
-#else
- // Soft shadow disabled version
-
if (directional_lights.data[i].shadow_opacity > 0.001) {
float depth_z = -vertex.z;
@@ -1667,6 +1600,10 @@ void main() {
shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance
+#ifdef USE_VERTEX_LIGHTING
+ diffuse_light *= mix(1.0, shadow, diffuse_light_interp.a);
+ specular_light *= mix(1.0, shadow, specular_light_interp.a);
+#endif
#undef BIAS_FUNC
}
#endif
@@ -1678,13 +1615,8 @@ void main() {
}
}
-#endif // SHADOWS_DISABLED
-
- for (uint i = 0; i < 8; i++) {
- if (i >= scene_data.directional_light_count) {
- break;
- }
-
+#ifndef USE_VERTEX_LIGHTING
+ for (uint i = 0; i < scene_data.directional_light_count; i++) {
if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
continue; //not masked
}
@@ -1703,8 +1635,8 @@ void main() {
#endif
blur_shadow(shadow);
-#ifdef DEBUG_DRAW_PSSM_SPLITS
vec3 tint = vec3(1.0);
+#ifdef DEBUG_DRAW_PSSM_SPLITS
if (-vertex.z < directional_lights.data[i].shadow_split_offsets.x) {
tint = vec3(1.0, 0.0, 0.0);
} else if (-vertex.z < directional_lights.data[i].shadow_split_offsets.y) {
@@ -1718,12 +1650,10 @@ void main() {
shadow = 1.0;
#endif
- light_compute(normal, directional_lights.data[i].direction, normalize(view), 0.0,
-#ifndef DEBUG_DRAW_PSSM_SPLITS
- directional_lights.data[i].color * directional_lights.data[i].energy,
-#else
+ float size_A = sc_use_light_soft_shadows() ? directional_lights.data[i].size : 0.0;
+
+ light_compute(normal, directional_lights.data[i].direction, view, size_A,
directional_lights.data[i].color * directional_lights.data[i].energy * tint,
-#endif
true, shadow, f0, orms, 1.0, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
backlight,
@@ -1745,15 +1675,14 @@ void main() {
#ifdef LIGHT_ANISOTROPY_USED
binormal, tangent, anisotropy,
#endif
-#ifdef USE_SOFT_SHADOW
- directional_lights.data[i].size,
-#endif
diffuse_light,
specular_light);
}
+#endif // USE_VERTEX_LIGHTING
} //directional light
- if (!sc_disable_omni_lights) { //omni lights
+#ifndef USE_VERTEX_LIGHTING
+ if (!sc_disable_omni_lights()) { //omni lights
uint light_indices = instances.data[draw_call.instance_index].omni_lights.x;
for (uint i = 0; i < 8; i++) {
uint light_index = light_indices & 0xFF;
@@ -1771,6 +1700,7 @@ void main() {
shadow = blur_shadow(shadow);
+ // Fragment lighting
light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha,
#ifdef LIGHT_BACKLIGHT_USED
backlight,
@@ -1797,7 +1727,7 @@ void main() {
}
} //omni lights
- if (!sc_disable_spot_lights) { //spot lights
+ if (!sc_disable_spot_lights()) { //spot lights
uint light_indices = instances.data[draw_call.instance_index].spot_lights.x;
for (uint i = 0; i < 8; i++) {
@@ -1841,6 +1771,9 @@ void main() {
diffuse_light, specular_light);
}
} //spot lights
+#endif // !VERTEX_LIGHTING
+
+#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
#ifdef USE_SHADOW_TO_OPACITY
#ifndef MODE_RENDER_DEPTH
@@ -1855,8 +1788,6 @@ void main() {
#endif // !MODE_RENDER_DEPTH
#endif // USE_SHADOW_TO_OPACITY
-#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
-
#ifdef MODE_RENDER_DEPTH
#ifdef MODE_RENDER_MATERIAL
@@ -1932,7 +1863,7 @@ void main() {
// On mobile we use a UNORM buffer with 10bpp which results in a range from 0.0 - 1.0 resulting in HDR breaking
// We divide by sc_luminance_multiplier to support a range from 0.0 - 2.0 both increasing precision on bright and darker images
- frag_color.rgb = frag_color.rgb / sc_luminance_multiplier;
+ frag_color.rgb = frag_color.rgb / sc_luminance_multiplier();
#ifdef PREMUL_ALPHA_USED
frag_color.rgb *= premul_alpha;
#endif
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
index d971ff04c5..495e52a29e 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
@@ -20,9 +20,128 @@ layout(push_constant, std430) uniform DrawCall {
vec2 uv_offset;
uint instance_index;
uint pad;
+#ifdef UBERSHADER
+ uint sc_packed_0;
+ float sc_packed_1;
+ uint sc_packed_2;
+ uint uc_packed_0;
+#endif
}
draw_call;
+/* Specialization Constants */
+
+#ifdef UBERSHADER
+
+#define POLYGON_CULL_DISABLED 0
+#define POLYGON_CULL_FRONT 1
+#define POLYGON_CULL_BACK 2
+
+// Pull the constants from the draw call's push constants.
+uint sc_packed_0() {
+ return draw_call.sc_packed_0;
+}
+
+float sc_packed_1() {
+ return draw_call.sc_packed_1;
+}
+
+uint uc_cull_mode() {
+ return (draw_call.uc_packed_0 >> 0) & 3U;
+}
+
+#else
+
+// Pull the constants from the pipeline's specialization constants.
+layout(constant_id = 0) const uint pso_sc_packed_0 = 0;
+layout(constant_id = 1) const float pso_sc_packed_1 = 2.0;
+
+uint sc_packed_0() {
+ return pso_sc_packed_0;
+}
+
+float sc_packed_1() {
+ return pso_sc_packed_1;
+}
+
+#endif
+
+bool sc_use_light_projector() {
+ return ((sc_packed_0() >> 0) & 1U) != 0;
+}
+
+bool sc_use_light_soft_shadows() {
+ return ((sc_packed_0() >> 1) & 1U) != 0;
+}
+
+bool sc_use_directional_soft_shadows() {
+ return ((sc_packed_0() >> 2) & 1U) != 0;
+}
+
+bool sc_decal_use_mipmaps() {
+ return ((sc_packed_0() >> 3) & 1U) != 0;
+}
+
+bool sc_projector_use_mipmaps() {
+ return ((sc_packed_0() >> 4) & 1U) != 0;
+}
+
+bool sc_disable_omni_lights() {
+ return ((sc_packed_0() >> 5) & 1U) != 0;
+}
+
+bool sc_disable_spot_lights() {
+ return ((sc_packed_0() >> 6) & 1U) != 0;
+}
+
+bool sc_disable_reflection_probes() {
+ return ((sc_packed_0() >> 7) & 1U) != 0;
+}
+
+bool sc_disable_directional_lights() {
+ return ((sc_packed_0() >> 8) & 1U) != 0;
+}
+
+bool sc_disable_decals() {
+ return ((sc_packed_0() >> 9) & 1U) != 0;
+}
+
+bool sc_disable_fog() {
+ return ((sc_packed_0() >> 10) & 1U) != 0;
+}
+
+bool sc_use_depth_fog() {
+ return ((sc_packed_0() >> 11) & 1U) != 0;
+}
+
+bool sc_is_multimesh() {
+ return ((sc_packed_0() >> 12) & 1U) != 0;
+}
+
+bool sc_use_lightmap_bicubic_filter() {
+ return ((sc_packed_0() >> 13) & 1U) != 0;
+}
+
+uint sc_soft_shadow_samples() {
+ return (sc_packed_0() >> 16) & 15U;
+}
+
+uint sc_penumbra_shadow_samples() {
+ return (sc_packed_0() >> 20) & 15U;
+}
+
+uint sc_directional_soft_shadow_samples() {
+ return (sc_packed_0() >> 24) & 15U;
+}
+
+uint sc_directional_penumbra_shadow_samples() {
+ return (sc_packed_0() >> 28) & 15U;
+}
+
+float sc_luminance_multiplier() {
+ return sc_packed_1();
+}
+
/* Set 0: Base Pass (never changes) */
#include "../light_data_inc.glsl"
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
index 14a4dc7089..a1a185d0fd 100644
--- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
@@ -269,7 +269,7 @@ float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, ve
float depth = coord.z;
//if only one sample is taken, take it from the center
- if (sc_directional_soft_shadow_samples == 0) {
+ if (sc_directional_soft_shadow_samples() == 0) {
return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
}
@@ -283,11 +283,11 @@ float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, ve
float avg = 0.0;
- for (uint i = 0; i < sc_directional_soft_shadow_samples; i++) {
+ for (uint i = 0; i < sc_directional_soft_shadow_samples(); i++) {
avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos + shadow_pixel_size * (disk_rotation * scene_data_block.data.directional_soft_shadow_kernel[i].xy), depth, 1.0));
}
- return avg * (1.0 / float(sc_directional_soft_shadow_samples));
+ return avg * (1.0 / float(sc_directional_soft_shadow_samples()));
}
float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord, float taa_frame_count) {
@@ -295,7 +295,7 @@ float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord, fl
float depth = coord.z;
//if only one sample is taken, take it from the center
- if (sc_soft_shadow_samples == 0) {
+ if (sc_soft_shadow_samples() == 0) {
return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
}
@@ -309,16 +309,16 @@ float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord, fl
float avg = 0.0;
- for (uint i = 0; i < sc_soft_shadow_samples; i++) {
+ for (uint i = 0; i < sc_soft_shadow_samples(); i++) {
avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos + shadow_pixel_size * (disk_rotation * scene_data_block.data.soft_shadow_kernel[i].xy), depth, 1.0));
}
- return avg * (1.0 / float(sc_soft_shadow_samples));
+ return avg * (1.0 / float(sc_soft_shadow_samples()));
}
float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec4 uv_rect, vec2 flip_offset, float depth, float taa_frame_count) {
//if only one sample is taken, take it from the center
- if (sc_soft_shadow_samples == 0) {
+ if (sc_soft_shadow_samples() == 0) {
vec2 pos = coord * 0.5 + 0.5;
pos = uv_rect.xy + pos * uv_rect.zw;
return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0));
@@ -335,7 +335,7 @@ float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec
float avg = 0.0;
vec2 offset_scale = blur_scale * 2.0 * scene_data_block.data.shadow_atlas_pixel_size / uv_rect.zw;
- for (uint i = 0; i < sc_soft_shadow_samples; i++) {
+ for (uint i = 0; i < sc_soft_shadow_samples(); i++) {
vec2 offset = offset_scale * (disk_rotation * scene_data_block.data.soft_shadow_kernel[i].xy);
vec2 sample_coord = coord + offset;
@@ -356,7 +356,7 @@ float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec
avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(sample_coord, depth, 1.0));
}
- return avg * (1.0 / float(sc_soft_shadow_samples));
+ return avg * (1.0 / float(sc_soft_shadow_samples()));
}
float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex_scale, float taa_frame_count) {
@@ -372,7 +372,7 @@ float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex
disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr));
}
- for (uint i = 0; i < sc_directional_penumbra_shadow_samples; i++) {
+ for (uint i = 0; i < sc_directional_penumbra_shadow_samples(); i++) {
vec2 suv = pssm_coord.xy + (disk_rotation * scene_data_block.data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
float d = textureLod(sampler2D(shadow, SAMPLER_LINEAR_CLAMP), suv, 0.0).r;
if (d > pssm_coord.z) {
@@ -388,12 +388,12 @@ float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex
tex_scale *= penumbra;
float s = 0.0;
- for (uint i = 0; i < sc_directional_penumbra_shadow_samples; i++) {
+ for (uint i = 0; i < sc_directional_penumbra_shadow_samples(); i++) {
vec2 suv = pssm_coord.xy + (disk_rotation * scene_data_block.data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
s += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(suv, pssm_coord.z, 1.0));
}
- return s / float(sc_directional_penumbra_shadow_samples);
+ return s / float(sc_directional_penumbra_shadow_samples());
} else {
//no blockers found, so no shadow
@@ -434,7 +434,7 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
float shadow;
- if (sc_use_light_soft_shadows && omni_lights.data[idx].soft_shadow_size > 0.0) {
+ if (sc_use_light_soft_shadows() && omni_lights.data[idx].soft_shadow_size > 0.0) {
//soft shadow
//find blocker
@@ -459,7 +459,7 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
tangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
bitangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale;
- for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ for (uint i = 0; i < sc_penumbra_shadow_samples(); i++) {
vec2 disk = disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy;
vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y;
@@ -495,7 +495,7 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
z_norm += omni_lights.data[idx].inv_radius * omni_lights.data[idx].shadow_bias;
shadow = 0.0;
- for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ for (uint i = 0; i < sc_penumbra_shadow_samples(); i++) {
vec2 disk = disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy;
vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y;
@@ -516,7 +516,7 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(pos.xy, z_norm, 1.0));
}
- shadow /= float(sc_penumbra_shadow_samples);
+ shadow /= float(sc_penumbra_shadow_samples());
shadow = mix(1.0, shadow, omni_lights.data[idx].shadow_opacity);
} else {
@@ -574,7 +574,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
float size_A = 0.0;
- if (sc_use_light_soft_shadows && omni_lights.data[idx].size > 0.0) {
+ if (sc_use_light_soft_shadows() && omni_lights.data[idx].size > 0.0) {
float t = omni_lights.data[idx].size / max(0.001, light_length);
size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t));
}
@@ -616,7 +616,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
#endif // !SHADOWS_DISABLED
#endif // LIGHT_TRANSMITTANCE_USED
- if (sc_use_light_projector && omni_lights.data[idx].projector_rect != vec4(0.0)) {
+ if (sc_use_light_projector() && omni_lights.data[idx].projector_rect != vec4(0.0)) {
vec3 local_v = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
local_v = normalize(local_v);
@@ -632,7 +632,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
local_v.xy = local_v.xy * 0.5 + 0.5;
vec2 proj_uv = local_v.xy * atlas_rect.zw;
- if (sc_projector_use_mipmaps) {
+ if (sc_projector_use_mipmaps()) {
vec2 proj_uv_ddx;
vec2 proj_uv_ddy;
{
@@ -716,7 +716,7 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
splane /= splane.w;
float shadow;
- if (sc_use_light_soft_shadows && spot_lights.data[idx].soft_shadow_size > 0.0) {
+ if (sc_use_light_soft_shadows() && spot_lights.data[idx].soft_shadow_size > 0.0) {
//soft shadow
//find blocker
@@ -737,7 +737,7 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
float uv_size = spot_lights.data[idx].soft_shadow_size * z_norm * spot_lights.data[idx].soft_shadow_scale;
vec2 clamp_max = spot_lights.data[idx].atlas_rect.xy + spot_lights.data[idx].atlas_rect.zw;
- for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ for (uint i = 0; i < sc_penumbra_shadow_samples(); i++) {
vec2 suv = shadow_uv + (disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy) * uv_size;
suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
float d = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), suv, 0.0).r;
@@ -754,13 +754,13 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr
uv_size *= penumbra;
shadow = 0.0;
- for (uint i = 0; i < sc_penumbra_shadow_samples; i++) {
+ for (uint i = 0; i < sc_penumbra_shadow_samples(); i++) {
vec2 suv = shadow_uv + (disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy) * uv_size;
suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
shadow += textureProj(sampler2DShadow(shadow_atlas, shadow_sampler), vec4(suv, splane.z, 1.0));
}
- shadow /= float(sc_penumbra_shadow_samples);
+ shadow /= float(sc_penumbra_shadow_samples());
shadow = mix(1.0, shadow, spot_lights.data[idx].shadow_opacity);
} else {
@@ -831,7 +831,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
float size_A = 0.0;
- if (sc_use_light_soft_shadows && spot_lights.data[idx].size > 0.0) {
+ if (sc_use_light_soft_shadows() && spot_lights.data[idx].size > 0.0) {
float t = spot_lights.data[idx].size / max(0.001, light_length);
size_A = max(0.0, 1.0 - 1 / sqrt(1 + t * t));
}
@@ -859,13 +859,13 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
#endif // !SHADOWS_DISABLED
#endif // LIGHT_TRANSMITTANCE_USED
- if (sc_use_light_projector && spot_lights.data[idx].projector_rect != vec4(0.0)) {
+ if (sc_use_light_projector() && spot_lights.data[idx].projector_rect != vec4(0.0)) {
vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex, 1.0));
splane /= splane.w;
vec2 proj_uv = splane.xy * spot_lights.data[idx].projector_rect.zw;
- if (sc_projector_use_mipmaps) {
+ if (sc_projector_use_mipmaps()) {
//ensure we have proper mipmaps
vec4 splane_ddx = (spot_lights.data[idx].shadow_matrix * vec4(vertex + vertex_ddx, 1.0));
splane_ddx /= splane_ddx.w;
@@ -940,7 +940,7 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 ref_vec, vec3 normal,
vec4 reflection;
- reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier;
+ reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier();
reflection.rgb *= reflections.data[ref_index].exposure_normalization;
if (reflections.data[ref_index].exterior) {
reflection.rgb = mix(specular_light, reflection.rgb, blend);
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_vertex_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_vertex_lights_inc.glsl
new file mode 100644
index 0000000000..e962c8f7f3
--- /dev/null
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_vertex_lights_inc.glsl
@@ -0,0 +1,82 @@
+// Simplified versions of light functions intended for the vertex shader.
+
+// Eyeballed approximation of `exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25`.
+// Uses slightly more FMA instructions (2x rate) to avoid special instructions (0.25x rate).
+// Range is reduced to [0.64,4977] from [068,2,221,528] which makes mediump feasible for the rest of the shader.
+mediump float roughness_to_shininess(mediump float roughness) {
+ mediump float r = 1.2 - roughness;
+ mediump float r2 = r * r;
+ return r * r2 * r2 * 2000.0;
+}
+
+void light_compute_vertex(vec3 N, vec3 L, vec3 V, vec3 light_color, bool is_directional, float roughness,
+ inout vec3 diffuse_light, inout vec3 specular_light) {
+ float NdotL = min(dot(N, L), 1.0);
+ float cNdotL = max(NdotL, 0.0); // clamped NdotL
+
+#if defined(DIFFUSE_LAMBERT_WRAP)
+ // Energy conserving lambert wrap shader.
+ // https://web.archive.org/web/20210228210901/http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
+ float diffuse_brdf_NL = max(0.0, (cNdotL + roughness) / ((1.0 + roughness) * (1.0 + roughness))) * (1.0 / M_PI);
+#else
+ // lambert
+ float diffuse_brdf_NL = cNdotL * (1.0 / M_PI);
+#endif
+
+ diffuse_light += light_color * diffuse_brdf_NL;
+
+#if !defined(SPECULAR_DISABLED)
+ float specular_brdf_NL = 0.0;
+ // Normalized blinn always unless disabled.
+ vec3 H = normalize(V + L);
+ float cNdotH = clamp(dot(N, H), 0.0, 1.0);
+ float shininess = roughness_to_shininess(roughness);
+ float blinn = pow(cNdotH, shininess);
+ blinn *= (shininess + 2.0) * (1.0 / (8.0 * M_PI)) * cNdotL;
+ specular_brdf_NL = blinn;
+ specular_light += specular_brdf_NL * light_color;
+#endif
+}
+
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
+void light_process_omni_vertex(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, float roughness,
+ inout vec3 diffuse_light, inout vec3 specular_light) {
+ vec3 light_rel_vec = omni_lights.data[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ float omni_attenuation = get_omni_attenuation(light_length, omni_lights.data[idx].inv_radius, omni_lights.data[idx].attenuation);
+ vec3 color = omni_lights.data[idx].color * omni_attenuation;
+
+ light_compute_vertex(normal, normalize(light_rel_vec), eye_vec, color, false, roughness,
+ diffuse_light,
+ specular_light);
+}
+
+void light_process_spot_vertex(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, float roughness,
+ inout vec3 diffuse_light,
+ inout vec3 specular_light) {
+ vec3 light_rel_vec = spot_lights.data[idx].position - vertex;
+ float light_length = length(light_rel_vec);
+ float spot_attenuation = get_omni_attenuation(light_length, spot_lights.data[idx].inv_radius, spot_lights.data[idx].attenuation);
+ vec3 spot_dir = spot_lights.data[idx].direction;
+
+ // This conversion to a highp float is crucial to prevent light leaking
+ // due to precision errors in the following calculations (cone angle is mediump).
+ highp float cone_angle = spot_lights.data[idx].cone_angle;
+ float scos = max(dot(-normalize(light_rel_vec), spot_dir), cone_angle);
+ float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cone_angle));
+
+ spot_attenuation *= 1.0 - pow(spot_rim, spot_lights.data[idx].cone_attenuation);
+ vec3 color = spot_lights.data[idx].color * spot_attenuation;
+ float specular_amount = spot_lights.data[idx].specular_amount;
+
+ light_compute_vertex(normal, normalize(light_rel_vec), eye_vec, color, false, roughness,
+ diffuse_light, specular_light);
+}
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index b07063cfda..8f71909154 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -313,6 +313,12 @@ void LightStorage::light_omni_set_shadow_mode(RID p_light, RS::LightOmniShadowMo
light->version++;
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
+
+ if (p_mode == RS::LIGHT_OMNI_SHADOW_DUAL_PARABOLOID) {
+ shadow_dual_paraboloid_used = true;
+ } else if (p_mode == RS::LIGHT_OMNI_SHADOW_CUBE) {
+ shadow_cubemaps_used = true;
+ }
}
RS::LightOmniShadowMode LightStorage::light_omni_get_shadow_mode(RID p_light) {
@@ -1478,21 +1484,20 @@ bool LightStorage::reflection_probe_instance_begin_render(RID p_instance, RID p_
//reflection atlas was unused, create:
RD::TextureFormat tf;
tf.array_layers = 6 * atlas->count;
- tf.format = RendererSceneRenderRD::get_singleton()->_render_buffers_get_color_format();
+ tf.format = get_reflection_probe_color_format();
tf.texture_type = RD::TEXTURE_TYPE_CUBE_ARRAY;
tf.mipmaps = mipmaps;
tf.width = atlas->size;
tf.height = atlas->size;
- tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | (RendererSceneRenderRD::get_singleton()->_render_buffers_can_be_storage() ? RD::TEXTURE_USAGE_STORAGE_BIT : 0);
-
+ tf.usage_bits = get_reflection_probe_color_usage_bits();
atlas->reflection = RD::get_singleton()->texture_create(tf, RD::TextureView());
}
{
RD::TextureFormat tf;
- tf.format = RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_D32_SFLOAT, RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ? RD::DATA_FORMAT_D32_SFLOAT : RD::DATA_FORMAT_X8_D24_UNORM_PACK32;
+ tf.format = get_reflection_probe_depth_format();
tf.width = atlas->size;
tf.height = atlas->size;
- tf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+ tf.usage_bits = get_reflection_probe_depth_usage_bits();
atlas->depth_buffer = RD::get_singleton()->texture_create(tf, RD::TextureView());
}
atlas->reflections.resize(atlas->count);
@@ -1763,6 +1768,22 @@ void LightStorage::update_reflection_probe_buffer(RenderDataRD *p_render_data, c
}
}
+RD::DataFormat LightStorage::get_reflection_probe_color_format() {
+ return RendererSceneRenderRD::get_singleton()->_render_buffers_get_color_format();
+}
+
+uint32_t LightStorage::get_reflection_probe_color_usage_bits() {
+ return RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | (RendererSceneRenderRD::get_singleton()->_render_buffers_can_be_storage() ? RD::TEXTURE_USAGE_STORAGE_BIT : 0);
+}
+
+RD::DataFormat LightStorage::get_reflection_probe_depth_format() {
+ return RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_D32_SFLOAT, RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ? RD::DATA_FORMAT_D32_SFLOAT : RD::DATA_FORMAT_X8_D24_UNORM_PACK32;
+}
+
+uint32_t LightStorage::get_reflection_probe_depth_usage_bits() {
+ return RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+}
+
/* LIGHTMAP API */
RID LightStorage::lightmap_allocate() {
@@ -1996,10 +2017,10 @@ void LightStorage::shadow_atlas_free(RID p_atlas) {
void LightStorage::_update_shadow_atlas(ShadowAtlas *shadow_atlas) {
if (shadow_atlas->size > 0 && shadow_atlas->depth.is_null()) {
RD::TextureFormat tf;
- tf.format = shadow_atlas->use_16_bits ? RD::DATA_FORMAT_D16_UNORM : RD::DATA_FORMAT_D32_SFLOAT;
+ tf.format = get_shadow_atlas_depth_format(shadow_atlas->use_16_bits);
tf.width = shadow_atlas->size;
tf.height = shadow_atlas->size;
- tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ tf.usage_bits = get_shadow_atlas_depth_usage_bits();
shadow_atlas->depth = RD::get_singleton()->texture_create(tf, RD::TextureView());
Vector<RID> fb_tex;
@@ -2384,15 +2405,23 @@ void LightStorage::shadow_atlas_update(RID p_atlas) {
_update_shadow_atlas(shadow_atlas);
}
+RD::DataFormat LightStorage::get_shadow_atlas_depth_format(bool p_16_bits) {
+ return p_16_bits ? RD::DATA_FORMAT_D16_UNORM : RD::DATA_FORMAT_D32_SFLOAT;
+}
+
+uint32_t LightStorage::get_shadow_atlas_depth_usage_bits() {
+ return RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+}
+
/* DIRECTIONAL SHADOW */
void LightStorage::update_directional_shadow_atlas() {
if (directional_shadow.depth.is_null() && directional_shadow.size > 0) {
RD::TextureFormat tf;
- tf.format = directional_shadow.use_16_bits ? RD::DATA_FORMAT_D16_UNORM : RD::DATA_FORMAT_D32_SFLOAT;
+ tf.format = get_shadow_atlas_depth_format(directional_shadow.use_16_bits);
tf.width = directional_shadow.size;
tf.height = directional_shadow.size;
- tf.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ tf.usage_bits = get_shadow_atlas_depth_usage_bits();
directional_shadow.depth = RD::get_singleton()->texture_create(tf, RD::TextureView());
Vector<RID> fb_tex;
@@ -2477,12 +2506,12 @@ LightStorage::ShadowCubemap *LightStorage::_get_shadow_cubemap(int p_size) {
ShadowCubemap sc;
{
RD::TextureFormat tf;
- tf.format = RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_D32_SFLOAT, RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ? RD::DATA_FORMAT_D32_SFLOAT : RD::DATA_FORMAT_X8_D24_UNORM_PACK32;
+ tf.format = get_cubemap_depth_format();
tf.width = p_size;
tf.height = p_size;
tf.texture_type = RD::TEXTURE_TYPE_CUBE;
tf.array_layers = 6;
- tf.usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+ tf.usage_bits = get_cubemap_depth_usage_bits();
sc.cubemap = RD::get_singleton()->texture_create(tf, RD::TextureView());
}
@@ -2510,3 +2539,19 @@ RID LightStorage::get_cubemap_fb(int p_size, int p_pass) {
return cubemap->side_fb[p_pass];
}
+
+RD::DataFormat LightStorage::get_cubemap_depth_format() {
+ return RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_D32_SFLOAT, RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) ? RD::DATA_FORMAT_D32_SFLOAT : RD::DATA_FORMAT_X8_D24_UNORM_PACK32;
+}
+
+uint32_t LightStorage::get_cubemap_depth_usage_bits() {
+ return RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
+}
+
+bool LightStorage::get_shadow_cubemaps_used() const {
+ return shadow_cubemaps_used;
+}
+
+bool LightStorage::get_shadow_dual_paraboloid_used() const {
+ return shadow_dual_paraboloid_used;
+}
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index 1db58d72f9..59303e8a73 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -434,6 +434,11 @@ private:
HashMap<int, ShadowCubemap> shadow_cubemaps;
ShadowCubemap *_get_shadow_cubemap(int p_size);
+ /* PIPELINE HINTS */
+
+ bool shadow_cubemaps_used = false;
+ bool shadow_dual_paraboloid_used = false;
+
public:
static LightStorage *get_singleton();
@@ -938,6 +943,10 @@ public:
void set_max_reflection_probes(const uint32_t p_max_reflection_probes);
RID get_reflection_probe_buffer() { return reflection_buffer; }
void update_reflection_probe_buffer(RenderDataRD *p_render_data, const PagedArray<RID> &p_reflections, const Transform3D &p_camera_inverse_transform, RID p_environment);
+ static RD::DataFormat get_reflection_probe_color_format();
+ static uint32_t get_reflection_probe_color_usage_bits();
+ static RD::DataFormat get_reflection_probe_depth_format();
+ static uint32_t get_reflection_probe_depth_usage_bits();
/* LIGHTMAP */
@@ -1079,6 +1088,8 @@ public:
}
virtual void shadow_atlas_update(RID p_atlas) override;
+ static RD::DataFormat get_shadow_atlas_depth_format(bool p_16_bits);
+ static uint32_t get_shadow_atlas_depth_usage_bits();
/* DIRECTIONAL SHADOW */
@@ -1109,6 +1120,13 @@ public:
RID get_cubemap(int p_size);
RID get_cubemap_fb(int p_size, int p_pass);
+ static RD::DataFormat get_cubemap_depth_format();
+ static uint32_t get_cubemap_depth_usage_bits();
+
+ /* PIPELINE HINTS */
+
+ bool get_shadow_cubemaps_used() const;
+ bool get_shadow_dual_paraboloid_used() const;
};
} // namespace RendererRD
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index 3bfc1bd15c..17a18b2766 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -633,6 +633,93 @@ bool MaterialStorage::ShaderData::is_parameter_texture(const StringName &p_param
return uniforms[p_param].is_texture();
}
+RD::PipelineColorBlendState::Attachment MaterialStorage::ShaderData::blend_mode_to_blend_attachment(BlendMode p_mode) {
+ RD::PipelineColorBlendState::Attachment attachment;
+
+ switch (p_mode) {
+ case BLEND_MODE_MIX: {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+ attachment.color_blend_op = RD::BLEND_OP_ADD;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ } break;
+ case BLEND_MODE_ADD: {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+ attachment.color_blend_op = RD::BLEND_OP_ADD;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+ } break;
+ case BLEND_MODE_SUB: {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
+ attachment.color_blend_op = RD::BLEND_OP_REVERSE_SUBTRACT;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+ } break;
+ case BLEND_MODE_MUL: {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+ attachment.color_blend_op = RD::BLEND_OP_ADD;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_DST_COLOR;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ZERO;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_DST_ALPHA;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
+ } break;
+ case BLEND_MODE_ALPHA_TO_COVERAGE: {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+ attachment.color_blend_op = RD::BLEND_OP_ADD;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_SRC_ALPHA;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ZERO;
+ } break;
+ case BLEND_MODE_PREMULTIPLIED_ALPHA: {
+ attachment.enable_blend = true;
+ attachment.alpha_blend_op = RD::BLEND_OP_ADD;
+ attachment.color_blend_op = RD::BLEND_OP_ADD;
+ attachment.src_color_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.dst_color_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ attachment.src_alpha_blend_factor = RD::BLEND_FACTOR_ONE;
+ attachment.dst_alpha_blend_factor = RD::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ } break;
+ case BLEND_MODE_DISABLED:
+ default: {
+ // Use default attachment values.
+ } break;
+ }
+
+ return attachment;
+}
+
+bool MaterialStorage::ShaderData::blend_mode_uses_blend_alpha(BlendMode p_mode) {
+ switch (p_mode) {
+ case BLEND_MODE_MIX:
+ return false;
+ case BLEND_MODE_ADD:
+ return true;
+ case BLEND_MODE_SUB:
+ return true;
+ case BLEND_MODE_MUL:
+ return true;
+ case BLEND_MODE_ALPHA_TO_COVERAGE:
+ return false;
+ case BLEND_MODE_PREMULTIPLIED_ALPHA:
+ return true;
+ case BLEND_MODE_DISABLED:
+ default:
+ return false;
+ }
+}
+
///////////////////////////////////////////////////////////////////////////
// MaterialStorage::MaterialData
@@ -2021,6 +2108,7 @@ void MaterialStorage::_material_uniform_set_erased(void *p_material) {
}
void MaterialStorage::_material_queue_update(Material *material, bool p_uniform, bool p_texture) {
+ MutexLock lock(material_update_list_mutex);
material->uniform_dirty = material->uniform_dirty || p_uniform;
material->texture_dirty = material->texture_dirty || p_texture;
@@ -2032,6 +2120,7 @@ void MaterialStorage::_material_queue_update(Material *material, bool p_uniform,
}
void MaterialStorage::_update_queued_materials() {
+ MutexLock lock(material_update_list_mutex);
while (material_update_list.first()) {
Material *material = material_update_list.first()->self();
bool uniforms_changed = false;
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.h b/servers/rendering/renderer_rd/storage_rd/material_storage.h
index 9c53450462..08c1064dcb 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.h
@@ -56,6 +56,16 @@ public:
};
struct ShaderData {
+ enum BlendMode {
+ BLEND_MODE_MIX,
+ BLEND_MODE_ADD,
+ BLEND_MODE_SUB,
+ BLEND_MODE_MUL,
+ BLEND_MODE_ALPHA_TO_COVERAGE,
+ BLEND_MODE_PREMULTIPLIED_ALPHA,
+ BLEND_MODE_DISABLED
+ };
+
String path;
HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;
HashMap<StringName, HashMap<int, RID>> default_texture_params;
@@ -73,6 +83,9 @@ public:
virtual RS::ShaderNativeSourceCode get_native_source_code() const { return RS::ShaderNativeSourceCode(); }
virtual ~ShaderData() {}
+
+ static RD::PipelineColorBlendState::Attachment blend_mode_to_blend_attachment(BlendMode p_mode);
+ static bool blend_mode_uses_blend_alpha(BlendMode p_mode);
};
struct MaterialData {
@@ -244,6 +257,7 @@ private:
Material *get_material(RID p_rid) { return material_owner.get_or_null(p_rid); };
SelfList<Material>::List material_update_list;
+ Mutex material_update_list_mutex;
static void _material_uniform_set_erased(void *p_material);
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 9ae39691dc..0d468ad1e3 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -1142,97 +1142,71 @@ void MeshStorage::update_mesh_instances() {
RD::get_singleton()->compute_list_end();
}
-void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint64_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis, uint32_t p_current_buffer, uint32_t p_previous_buffer) {
+RD::VertexFormatID MeshStorage::_mesh_surface_generate_vertex_format(uint64_t p_surface_format, uint64_t p_input_mask, bool p_instanced_surface, bool p_input_motion_vectors, uint32_t &r_position_stride) {
Vector<RD::VertexAttribute> attributes;
- Vector<RID> buffers;
- Vector<uint64_t> offsets;
-
- uint32_t position_stride = 0;
uint32_t normal_tangent_stride = 0;
uint32_t attribute_stride = 0;
uint32_t skin_stride = 0;
+ r_position_stride = 0;
+
for (int i = 0; i < RS::ARRAY_INDEX; i++) {
RD::VertexAttribute vd;
- RID buffer;
vd.location = i;
- uint64_t offset = 0;
- if (!(s->format & (1ULL << i))) {
- // Not supplied by surface, use default value
- buffer = mesh_default_rd_buffers[i];
+ if (!(p_surface_format & (1ULL << i))) {
vd.stride = 0;
switch (i) {
- case RS::ARRAY_VERTEX: {
- vd.format = RD::DATA_FORMAT_R32G32B32_SFLOAT;
-
- } break;
- case RS::ARRAY_NORMAL: {
+ case RS::ARRAY_VERTEX:
+ case RS::ARRAY_NORMAL:
vd.format = RD::DATA_FORMAT_R32G32B32_SFLOAT;
- } break;
- case RS::ARRAY_TANGENT: {
- vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
- } break;
- case RS::ARRAY_COLOR: {
- vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
-
- } break;
- case RS::ARRAY_TEX_UV: {
+ break;
+ case RS::ARRAY_TEX_UV:
+ case RS::ARRAY_TEX_UV2:
vd.format = RD::DATA_FORMAT_R32G32_SFLOAT;
-
- } break;
- case RS::ARRAY_TEX_UV2: {
- vd.format = RD::DATA_FORMAT_R32G32_SFLOAT;
- } break;
+ break;
+ case RS::ARRAY_BONES:
+ vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT;
+ break;
+ case RS::ARRAY_TANGENT:
+ case RS::ARRAY_COLOR:
case RS::ARRAY_CUSTOM0:
case RS::ARRAY_CUSTOM1:
case RS::ARRAY_CUSTOM2:
- case RS::ARRAY_CUSTOM3: {
- //assumed weights too
- vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
- } break;
- case RS::ARRAY_BONES: {
- //assumed weights too
- vd.format = RD::DATA_FORMAT_R32G32B32A32_UINT;
- } break;
- case RS::ARRAY_WEIGHTS: {
- //assumed weights too
+ case RS::ARRAY_CUSTOM3:
+ case RS::ARRAY_WEIGHTS:
vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
- } break;
+ break;
+ default:
+ DEV_ASSERT(false && "Unknown vertex format element.");
+ break;
}
} else {
- //Supplied, use it
-
- vd.stride = 1; //mark that it needs a stride set (default uses 0)
+ // Mark that it needs a stride set (default uses 0).
+ vd.stride = 1;
switch (i) {
case RS::ARRAY_VERTEX: {
- vd.offset = position_stride;
+ vd.offset = r_position_stride;
- if (s->format & RS::ARRAY_FLAG_USE_2D_VERTICES) {
+ if (p_surface_format & RS::ARRAY_FLAG_USE_2D_VERTICES) {
vd.format = RD::DATA_FORMAT_R32G32_SFLOAT;
- position_stride = sizeof(float) * 2;
+ r_position_stride = sizeof(float) * 2;
} else {
- if (!mis && (s->format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES)) {
+ if (!p_instanced_surface && (p_surface_format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES)) {
vd.format = RD::DATA_FORMAT_R16G16B16A16_UNORM;
- position_stride = sizeof(uint16_t) * 4;
+ r_position_stride = sizeof(uint16_t) * 4;
} else {
vd.format = RD::DATA_FORMAT_R32G32B32_SFLOAT;
- position_stride = sizeof(float) * 3;
+ r_position_stride = sizeof(float) * 3;
}
}
- if (mis) {
- buffer = mis->vertex_buffer[p_current_buffer];
- } else {
- buffer = s->vertex_buffer;
- }
-
} break;
case RS::ARRAY_NORMAL: {
vd.offset = 0;
- offset = position_stride * s->vertex_count;
- if (!mis && (s->format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES)) {
+
+ if (!p_instanced_surface && (p_surface_format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES)) {
vd.format = RD::DATA_FORMAT_R16G16_UNORM;
normal_tangent_stride += sizeof(uint16_t) * 2;
} else {
@@ -1240,20 +1214,14 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
// A small trick here: if we are uncompressed and we have normals, but no tangents. We need
// the shader to think there are 4 components to "axis_tangent_attrib". So we give a size of 4,
// but a stride based on only having 2 elements.
- if (!(s->format & RS::ARRAY_FORMAT_TANGENT)) {
+ if (!(p_surface_format & RS::ARRAY_FORMAT_TANGENT)) {
normal_tangent_stride += sizeof(uint16_t) * 2;
} else {
normal_tangent_stride += sizeof(uint16_t) * 4;
}
}
- if (mis) {
- buffer = mis->vertex_buffer[p_current_buffer];
- } else {
- buffer = s->vertex_buffer;
- }
} break;
case RS::ARRAY_TANGENT: {
- buffer = mesh_default_rd_buffers[i];
vd.stride = 0;
vd.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
} break;
@@ -1262,30 +1230,27 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
vd.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
attribute_stride += sizeof(int8_t) * 4;
- buffer = s->attribute_buffer;
} break;
case RS::ARRAY_TEX_UV: {
vd.offset = attribute_stride;
- if (s->format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES) {
+ if (p_surface_format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES) {
vd.format = RD::DATA_FORMAT_R16G16_UNORM;
attribute_stride += sizeof(uint16_t) * 2;
} else {
vd.format = RD::DATA_FORMAT_R32G32_SFLOAT;
attribute_stride += sizeof(float) * 2;
}
- buffer = s->attribute_buffer;
} break;
case RS::ARRAY_TEX_UV2: {
vd.offset = attribute_stride;
- if (s->format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES) {
+ if (p_surface_format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES) {
vd.format = RD::DATA_FORMAT_R16G16_UNORM;
attribute_stride += sizeof(uint16_t) * 2;
} else {
vd.format = RD::DATA_FORMAT_R32G32_SFLOAT;
attribute_stride += sizeof(float) * 2;
}
- buffer = s->attribute_buffer;
} break;
case RS::ARRAY_CUSTOM0:
case RS::ARRAY_CUSTOM1:
@@ -1295,26 +1260,23 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
int idx = i - RS::ARRAY_CUSTOM0;
const uint32_t fmt_shift[RS::ARRAY_CUSTOM_COUNT] = { RS::ARRAY_FORMAT_CUSTOM0_SHIFT, RS::ARRAY_FORMAT_CUSTOM1_SHIFT, RS::ARRAY_FORMAT_CUSTOM2_SHIFT, RS::ARRAY_FORMAT_CUSTOM3_SHIFT };
- uint32_t fmt = (s->format >> fmt_shift[idx]) & RS::ARRAY_FORMAT_CUSTOM_MASK;
+ uint32_t fmt = (p_surface_format >> fmt_shift[idx]) & RS::ARRAY_FORMAT_CUSTOM_MASK;
const uint32_t fmtsize[RS::ARRAY_CUSTOM_MAX] = { 4, 4, 4, 8, 4, 8, 12, 16 };
const RD::DataFormat fmtrd[RS::ARRAY_CUSTOM_MAX] = { RD::DATA_FORMAT_R8G8B8A8_UNORM, RD::DATA_FORMAT_R8G8B8A8_SNORM, RD::DATA_FORMAT_R16G16_SFLOAT, RD::DATA_FORMAT_R16G16B16A16_SFLOAT, RD::DATA_FORMAT_R32_SFLOAT, RD::DATA_FORMAT_R32G32_SFLOAT, RD::DATA_FORMAT_R32G32B32_SFLOAT, RD::DATA_FORMAT_R32G32B32A32_SFLOAT };
vd.format = fmtrd[fmt];
attribute_stride += fmtsize[fmt];
- buffer = s->attribute_buffer;
} break;
case RS::ARRAY_BONES: {
vd.offset = skin_stride;
vd.format = RD::DATA_FORMAT_R16G16B16A16_UINT;
skin_stride += sizeof(int16_t) * 4;
- buffer = s->skin_buffer;
} break;
case RS::ARRAY_WEIGHTS: {
vd.offset = skin_stride;
vd.format = RD::DATA_FORMAT_R16G16B16A16_UNORM;
skin_stride += sizeof(int16_t) * 4;
- buffer = s->skin_buffer;
} break;
}
}
@@ -1324,13 +1286,10 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
attributes.push_back(vd);
- buffers.push_back(buffer);
- offsets.push_back(offset);
if (p_input_motion_vectors) {
- // Since the previous vertex, normal and tangent can't be part of the vertex format but they are required when motion
- // vectors are enabled, we opt to push a copy of the vertex attribute with a different location and buffer (if it's
- // part of an instance that has one).
+ // Since the previous vertex, normal and tangent can't be part of the vertex format but they are required when
+ // motion vectors are enabled, we opt to push a copy of the vertex attribute with a different location.
switch (i) {
case RS::ARRAY_VERTEX: {
vd.location = ATTRIBUTE_LOCATION_PREV_VERTEX;
@@ -1344,25 +1303,21 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
if (int(vd.location) != i) {
- if (mis && buffer != mesh_default_rd_buffers[i]) {
- buffer = mis->vertex_buffer[p_previous_buffer];
- }
-
attributes.push_back(vd);
- buffers.push_back(buffer);
- offsets.push_back(offset);
}
}
}
- //update final stride
+ // Update final stride.
for (int i = 0; i < attributes.size(); i++) {
if (attributes[i].stride == 0) {
- continue; //default location
+ // Default location.
+ continue;
}
+
int loc = attributes[i].location;
if (loc == RS::ARRAY_VERTEX || loc == ATTRIBUTE_LOCATION_PREV_VERTEX) {
- attributes.write[i].stride = position_stride;
+ attributes.write[i].stride = r_position_stride;
} else if ((loc < RS::ARRAY_COLOR) || ((loc >= ATTRIBUTE_LOCATION_PREV_NORMAL) && (loc <= ATTRIBUTE_LOCATION_PREV_TANGENT))) {
attributes.write[i].stride = normal_tangent_stride;
} else if (loc < RS::ARRAY_BONES) {
@@ -1372,11 +1327,75 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
}
}
+ return RD::get_singleton()->vertex_format_create(attributes);
+}
+
+void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint64_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis, uint32_t p_current_buffer, uint32_t p_previous_buffer) {
+ uint32_t position_stride = 0;
+ v.vertex_format = _mesh_surface_generate_vertex_format(s->format, p_input_mask, mis != nullptr, p_input_motion_vectors, position_stride);
+
+ Vector<RID> buffers;
+ Vector<uint64_t> offsets;
+ RID buffer;
+ uint64_t offset = 0;
+ for (int i = 0; i < RS::ARRAY_INDEX; i++) {
+ offset = 0;
+
+ if (!(s->format & (1ULL << i))) {
+ // Not supplied by surface, use default buffers.
+ buffer = mesh_default_rd_buffers[i];
+ } else {
+ // Supplied by surface, use buffer.
+ switch (i) {
+ case RS::ARRAY_VERTEX:
+ case RS::ARRAY_NORMAL:
+ offset = i == RS::ARRAY_NORMAL ? position_stride * s->vertex_count : 0;
+ buffer = mis != nullptr ? mis->vertex_buffer[p_current_buffer] : s->vertex_buffer;
+ break;
+ case RS::ARRAY_TANGENT:
+ buffer = mesh_default_rd_buffers[i];
+ break;
+ case RS::ARRAY_COLOR:
+ case RS::ARRAY_TEX_UV:
+ case RS::ARRAY_TEX_UV2:
+ case RS::ARRAY_CUSTOM0:
+ case RS::ARRAY_CUSTOM1:
+ case RS::ARRAY_CUSTOM2:
+ case RS::ARRAY_CUSTOM3:
+ buffer = s->attribute_buffer;
+ break;
+ case RS::ARRAY_BONES:
+ case RS::ARRAY_WEIGHTS:
+ buffer = s->skin_buffer;
+ break;
+ }
+ }
+
+ if (!(p_input_mask & (1ULL << i))) {
+ continue; // Shader does not need this, skip it (but computing stride was important anyway)
+ }
+
+ buffers.push_back(buffer);
+ offsets.push_back(offset);
+
+ if (p_input_motion_vectors) {
+ // Push the buffer for motion vector inputs.
+ if (i == RS::ARRAY_VERTEX || i == RS::ARRAY_NORMAL || i == RS::ARRAY_TANGENT) {
+ if (mis && buffer != mesh_default_rd_buffers[i]) {
+ buffers.push_back(mis->vertex_buffer[p_previous_buffer]);
+ } else {
+ buffers.push_back(buffer);
+ }
+
+ offsets.push_back(offset);
+ }
+ }
+ }
+
v.input_mask = p_input_mask;
v.current_buffer = p_current_buffer;
v.previous_buffer = p_previous_buffer;
v.input_motion_vectors = p_input_motion_vectors;
- v.vertex_format = RD::get_singleton()->vertex_format_create(attributes);
v.vertex_array = RD::get_singleton()->vertex_array_create(s->vertex_count, v.vertex_format, buffers, offsets);
}
@@ -2166,7 +2185,7 @@ void MeshStorage::skeleton_allocate_data(RID p_skeleton, int p_bones, bool p_2d_
if (skeleton->size) {
skeleton->data.resize(skeleton->size * (skeleton->use_2d ? 8 : 12));
skeleton->buffer = RD::get_singleton()->storage_buffer_create(skeleton->data.size() * sizeof(float));
- memset(skeleton->data.ptrw(), 0, skeleton->data.size() * sizeof(float));
+ memset(skeleton->data.ptr(), 0, skeleton->data.size() * sizeof(float));
_skeleton_make_dirty(skeleton);
@@ -2200,7 +2219,7 @@ void MeshStorage::skeleton_bone_set_transform(RID p_skeleton, int p_bone, const
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(skeleton->use_2d);
- float *dataptr = skeleton->data.ptrw() + p_bone * 12;
+ float *dataptr = skeleton->data.ptr() + p_bone * 12;
dataptr[0] = p_transform.basis.rows[0][0];
dataptr[1] = p_transform.basis.rows[0][1];
@@ -2251,8 +2270,7 @@ void MeshStorage::skeleton_bone_set_transform_2d(RID p_skeleton, int p_bone, con
ERR_FAIL_NULL(skeleton);
ERR_FAIL_INDEX(p_bone, skeleton->size);
ERR_FAIL_COND(!skeleton->use_2d);
-
- float *dataptr = skeleton->data.ptrw() + p_bone * 8;
+ float *dataptr = skeleton->data.ptr() + p_bone * 8;
dataptr[0] = p_transform.columns[0][0];
dataptr[1] = p_transform.columns[1][0];
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index f811314fb6..322f3cc6f4 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -199,6 +199,7 @@ private:
weight_update_list(this), array_update_list(this) {}
};
+ RD::VertexFormatID _mesh_surface_generate_vertex_format(uint64_t p_surface_format, uint64_t p_input_mask, bool p_instanced_surface, bool p_input_motion_vectors, uint32_t &r_position_stride);
void _mesh_surface_generate_version_for_input_mask(Mesh::Surface::Version &v, Mesh::Surface *s, uint64_t p_input_mask, bool p_input_motion_vectors, MeshInstance::Surface *mis = nullptr, uint32_t p_current_buffer = 0, uint32_t p_previous_buffer = 0);
void _mesh_instance_clear(MeshInstance *mi);
@@ -311,7 +312,7 @@ private:
struct Skeleton {
bool use_2d = false;
int size = 0;
- Vector<float> data;
+ LocalVector<float> data;
RID buffer;
bool dirty = false;
@@ -605,6 +606,12 @@ public:
return s->particles_render_index;
}
+ _FORCE_INLINE_ RD::VertexFormatID mesh_surface_get_vertex_format(void *p_surface, uint64_t p_input_mask, bool p_instanced_surface, bool p_input_motion_vectors) {
+ Mesh::Surface *s = reinterpret_cast<Mesh::Surface *>(p_surface);
+ uint32_t position_stride = 0;
+ return _mesh_surface_generate_vertex_format(s->format, p_input_mask, p_instanced_surface, p_input_motion_vectors, position_stride);
+ }
+
Dependency *mesh_get_dependency(RID p_mesh) const;
/* MESH INSTANCE API */
diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp
index 2f44096dc8..ca44f4dd7e 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp
@@ -167,76 +167,27 @@ void RenderSceneBuffersRD::configure(const RenderSceneBuffersConfiguration *p_co
// cleanout any old buffers we had.
cleanup();
- // At least one of these is required to be supported.
- RenderingDeviceCommons::DataFormat preferred_format[2] = { RD::DATA_FORMAT_D24_UNORM_S8_UINT, RD::DATA_FORMAT_D32_SFLOAT_S8_UINT };
- if (can_be_storage) {
- // Prefer higher precision on desktop.
- preferred_format[0] = RD::DATA_FORMAT_D32_SFLOAT_S8_UINT;
- preferred_format[1] = RD::DATA_FORMAT_D24_UNORM_S8_UINT;
- }
-
- // create our 3D render buffers
- {
- // Create our color buffer(s)
- uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | (can_be_storage ? RD::TEXTURE_USAGE_STORAGE_BIT : 0) | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
- usage_bits |= RD::TEXTURE_USAGE_INPUT_ATTACHMENT_BIT; // only needed when using subpasses in the mobile renderer
-
- // our internal texture should have MSAA support if applicable
- if (msaa_3d != RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
- }
-
- create_texture(RB_SCOPE_BUFFERS, RB_TEX_COLOR, base_data_format, usage_bits);
- }
-
- // Create our depth buffer
- {
- // TODO Lazy create this in case we've got an external depth buffer
-
- RD::DataFormat format;
- uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT;
-
- if (msaa_3d == RS::VIEWPORT_MSAA_DISABLED) {
- usage_bits |= RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
- format = RD::get_singleton()->texture_is_format_supported_for_usage(preferred_format[0], usage_bits) ? preferred_format[0] : preferred_format[1];
- } else {
- format = RD::DATA_FORMAT_R32_SFLOAT;
- usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | (can_be_storage ? RD::TEXTURE_USAGE_STORAGE_BIT : 0);
- }
+ // Create our color buffer.
+ const bool resolve_target = msaa_3d != RS::VIEWPORT_MSAA_DISABLED;
+ create_texture(RB_SCOPE_BUFFERS, RB_TEX_COLOR, base_data_format, get_color_usage_bits(resolve_target, false, can_be_storage));
- create_texture(RB_SCOPE_BUFFERS, RB_TEX_DEPTH, format, usage_bits);
- }
+ // Create our depth buffer.
+ create_texture(RB_SCOPE_BUFFERS, RB_TEX_DEPTH, get_depth_format(resolve_target, false, can_be_storage), get_depth_usage_bits(resolve_target, false, can_be_storage));
- // Create our MSAA buffers
+ // Create our MSAA buffers.
if (msaa_3d == RS::VIEWPORT_MSAA_DISABLED) {
texture_samples = RD::TEXTURE_SAMPLES_1;
} else {
- RD::DataFormat format = base_data_format;
- uint32_t usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
-
- const RD::TextureSamples ts[RS::VIEWPORT_MSAA_MAX] = {
- RD::TEXTURE_SAMPLES_1,
- RD::TEXTURE_SAMPLES_2,
- RD::TEXTURE_SAMPLES_4,
- RD::TEXTURE_SAMPLES_8,
- };
-
- texture_samples = ts[msaa_3d];
-
- create_texture(RB_SCOPE_BUFFERS, RB_TEX_COLOR_MSAA, format, usage_bits, texture_samples);
-
- usage_bits = RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
- format = RD::get_singleton()->texture_is_format_supported_for_usage(preferred_format[0], usage_bits) ? preferred_format[0] : preferred_format[1];
-
- create_texture(RB_SCOPE_BUFFERS, RB_TEX_DEPTH_MSAA, format, usage_bits, texture_samples);
+ texture_samples = msaa_to_samples(msaa_3d);
+ create_texture(RB_SCOPE_BUFFERS, RB_TEX_COLOR_MSAA, base_data_format, get_color_usage_bits(false, true, can_be_storage), texture_samples);
+ create_texture(RB_SCOPE_BUFFERS, RB_TEX_DEPTH_MSAA, get_depth_format(false, true, can_be_storage), get_depth_usage_bits(false, true, can_be_storage), texture_samples);
}
// VRS (note, our vrs object will only be set if VRS is supported)
RID vrs_texture;
RS::ViewportVRSMode vrs_mode = texture_storage->render_target_get_vrs_mode(render_target);
if (vrs && vrs_mode != RS::VIEWPORT_VRS_DISABLED) {
- uint32_t usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_VRS_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
- vrs_texture = create_texture(RB_SCOPE_VRS, RB_TEXTURE, RD::DATA_FORMAT_R8_UINT, usage_bits, RD::TEXTURE_SAMPLES_1, vrs->get_vrs_texture_size(internal_size));
+ vrs_texture = create_texture(RB_SCOPE_VRS, RB_TEXTURE, get_vrs_format(), get_vrs_usage_bits(), RD::TEXTURE_SAMPLES_1, vrs->get_vrs_texture_size(internal_size));
}
// (re-)configure any named buffers
@@ -664,16 +615,12 @@ void RenderSceneBuffersRD::ensure_upscaled() {
void RenderSceneBuffersRD::ensure_velocity() {
if (!has_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY)) {
- uint32_t usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
-
- if (msaa_3d != RS::VIEWPORT_MSAA_DISABLED) {
- uint32_t msaa_usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT;
- usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
+ const bool msaa = msaa_3d != RS::VIEWPORT_MSAA_DISABLED;
+ create_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY, get_velocity_format(), get_velocity_usage_bits(msaa, false, can_be_storage));
- create_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY_MSAA, RD::DATA_FORMAT_R16G16_SFLOAT, msaa_usage_bits, texture_samples);
+ if (msaa) {
+ create_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY_MSAA, get_velocity_format(), get_velocity_usage_bits(false, msaa, can_be_storage), texture_samples);
}
-
- create_texture(RB_SCOPE_BUFFERS, RB_TEX_VELOCITY, RD::DATA_FORMAT_R16G16_SFLOAT, usage_bits);
}
}
@@ -724,3 +671,62 @@ RID RenderSceneBuffersRD::get_velocity_buffer(bool p_get_msaa, uint32_t p_layer)
}
}
}
+
+uint32_t RenderSceneBuffersRD::get_color_usage_bits(bool p_resolve, bool p_msaa, bool p_storage) {
+ DEV_ASSERT((!p_resolve && !p_msaa) || (p_resolve != p_msaa));
+
+ uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_INPUT_ATTACHMENT_BIT;
+ if (p_msaa) {
+ usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT;
+ } else if (p_resolve) {
+ usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | (p_storage ? RD::TEXTURE_USAGE_STORAGE_BIT : 0);
+ } else {
+ usage_bits |= (p_storage ? RD::TEXTURE_USAGE_STORAGE_BIT : 0);
+ }
+
+ return usage_bits;
+}
+
+RD::DataFormat RenderSceneBuffersRD::get_depth_format(bool p_resolve, bool p_msaa, bool p_storage) {
+ if (p_resolve) {
+ return RD::DATA_FORMAT_R32_SFLOAT;
+ } else {
+ const RenderingDeviceCommons::DataFormat preferred_formats[2] = {
+ p_storage ? RD::DATA_FORMAT_D32_SFLOAT_S8_UINT : RD::DATA_FORMAT_D24_UNORM_S8_UINT,
+ p_storage ? RD::DATA_FORMAT_D24_UNORM_S8_UINT : RD::DATA_FORMAT_D32_SFLOAT_S8_UINT
+ };
+
+ return RD::get_singleton()->texture_is_format_supported_for_usage(preferred_formats[0], get_depth_usage_bits(p_resolve, p_msaa, p_storage)) ? preferred_formats[0] : preferred_formats[1];
+ }
+}
+
+uint32_t RenderSceneBuffersRD::get_depth_usage_bits(bool p_resolve, bool p_msaa, bool p_storage) {
+ DEV_ASSERT((!p_resolve && !p_msaa) || (p_resolve != p_msaa));
+
+ uint32_t usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT;
+ if (p_msaa) {
+ usage_bits |= RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT;
+ } else if (p_resolve) {
+ usage_bits |= RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | (p_storage ? RD::TEXTURE_USAGE_STORAGE_BIT : 0);
+ } else {
+ usage_bits |= RD::TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ }
+
+ return usage_bits;
+}
+
+RD::DataFormat RenderSceneBuffersRD::get_velocity_format() {
+ return RD::DATA_FORMAT_R16G16_SFLOAT;
+}
+
+uint32_t RenderSceneBuffersRD::get_velocity_usage_bits(bool p_resolve, bool p_msaa, bool p_storage) {
+ return get_color_usage_bits(p_resolve, p_msaa, p_storage);
+}
+
+RD::DataFormat RenderSceneBuffersRD::get_vrs_format() {
+ return RD::DATA_FORMAT_R8_UINT;
+}
+
+uint32_t RenderSceneBuffersRD::get_vrs_usage_bits() {
+ return RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_VRS_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
+}
diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h
index 0025fc5ab7..187dbab445 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h
+++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h
@@ -178,6 +178,7 @@ public:
// info from our renderer
void set_can_be_storage(const bool p_can_be_storage) { can_be_storage = p_can_be_storage; }
+ bool get_can_be_storage() const { return can_be_storage; }
void set_max_cluster_elements(const uint32_t p_max_elements) { max_cluster_elements = p_max_elements; }
uint32_t get_max_cluster_elements() { return max_cluster_elements; }
void set_base_data_format(const RD::DataFormat p_base_data_format) { base_data_format = p_base_data_format; }
@@ -305,6 +306,30 @@ public:
return samplers;
}
+ _FORCE_INLINE_ static RD::TextureSamples msaa_to_samples(RS::ViewportMSAA p_msaa) {
+ switch (p_msaa) {
+ case RS::VIEWPORT_MSAA_DISABLED:
+ return RD::TEXTURE_SAMPLES_1;
+ case RS::VIEWPORT_MSAA_2X:
+ return RD::TEXTURE_SAMPLES_2;
+ case RS::VIEWPORT_MSAA_4X:
+ return RD::TEXTURE_SAMPLES_4;
+ case RS::VIEWPORT_MSAA_8X:
+ return RD::TEXTURE_SAMPLES_8;
+ default:
+ DEV_ASSERT(false && "Unknown MSAA option.");
+ return RD::TEXTURE_SAMPLES_1;
+ }
+ }
+
+ static uint32_t get_color_usage_bits(bool p_resolve, bool p_msaa, bool p_storage);
+ static RD::DataFormat get_depth_format(bool p_resolve, bool p_msaa, bool p_storage);
+ static uint32_t get_depth_usage_bits(bool p_resolve, bool p_msaa, bool p_storage);
+ static RD::DataFormat get_velocity_format();
+ static uint32_t get_velocity_usage_bits(bool p_resolve, bool p_msaa, bool p_storage);
+ static RD::DataFormat get_vrs_format();
+ static uint32_t get_vrs_usage_bits();
+
private:
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Our classDB doesn't support calling our normal exposed functions
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index e5a8dbb9b2..9dc606620b 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -40,25 +40,12 @@ using namespace RendererRD;
///////////////////////////////////////////////////////////////////////////
// TextureStorage::CanvasTexture
-void TextureStorage::CanvasTexture::clear_sets() {
- if (cleared_cache) {
- return;
- }
- for (int i = 1; i < RS::CANVAS_ITEM_TEXTURE_FILTER_MAX; i++) {
- for (int j = 1; j < RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX; j++) {
- for (int k = 0; k < 2; k++) {
- if (RD::get_singleton()->uniform_set_is_valid(uniform_sets[i][j][k])) {
- RD::get_singleton()->free(uniform_sets[i][j][k]);
- uniform_sets[i][j][k] = RID();
- }
- }
- }
- }
- cleared_cache = true;
+void TextureStorage::CanvasTexture::clear_cache() {
+ info_cache[0] = CanvasTextureCache();
+ info_cache[1] = CanvasTextureCache();
}
TextureStorage::CanvasTexture::~CanvasTexture() {
- clear_sets();
}
///////////////////////////////////////////////////////////////////////////
@@ -185,9 +172,8 @@ TextureStorage::TextureStorage() {
ptr[i] = Math::make_half_float(1.0f);
}
- Vector<Vector<uint8_t>> vpv;
- vpv.push_back(sv);
- default_rd_textures[DEFAULT_RD_TEXTURE_DEPTH] = RD::get_singleton()->texture_create(tf, RD::TextureView(), vpv);
+ default_rd_textures[DEFAULT_RD_TEXTURE_DEPTH] = RD::get_singleton()->texture_create(tf, RD::TextureView());
+ RD::get_singleton()->texture_update(default_rd_textures[DEFAULT_RD_TEXTURE_DEPTH], 0, sv);
}
for (int i = 0; i < 16; i++) {
@@ -460,9 +446,8 @@ TextureStorage::TextureStorage() {
}
{
- Vector<Vector<uint8_t>> vsv;
- vsv.push_back(sv);
- default_rd_textures[DEFAULT_RD_TEXTURE_2D_ARRAY_DEPTH] = RD::get_singleton()->texture_create(tformat, RD::TextureView(), vsv);
+ default_rd_textures[DEFAULT_RD_TEXTURE_2D_ARRAY_DEPTH] = RD::get_singleton()->texture_create(tformat, RD::TextureView());
+ RD::get_singleton()->texture_update(default_rd_textures[DEFAULT_RD_TEXTURE_2D_ARRAY_DEPTH], 0, sv);
}
}
@@ -579,10 +564,6 @@ bool TextureStorage::free(RID p_rid) {
return false;
}
-bool TextureStorage::can_create_resources_async() const {
- return true;
-}
-
/* Canvas Texture API */
RID TextureStorage::canvas_texture_allocate() {
@@ -612,8 +593,7 @@ void TextureStorage::canvas_texture_set_channel(RID p_canvas_texture, RS::Canvas
ct->specular = p_texture;
} break;
}
-
- ct->clear_sets();
+ ct->clear_cache();
}
void TextureStorage::canvas_texture_set_shading_parameters(RID p_canvas_texture, const Color &p_specular_color, float p_shininess) {
@@ -624,7 +604,6 @@ void TextureStorage::canvas_texture_set_shading_parameters(RID p_canvas_texture,
ct->specular_color.g = p_specular_color.g;
ct->specular_color.b = p_specular_color.b;
ct->specular_color.a = p_shininess;
- ct->clear_sets();
}
void TextureStorage::canvas_texture_set_texture_filter(RID p_canvas_texture, RS::CanvasItemTextureFilter p_filter) {
@@ -632,7 +611,6 @@ void TextureStorage::canvas_texture_set_texture_filter(RID p_canvas_texture, RS:
ERR_FAIL_NULL(ct);
ct->texture_filter = p_filter;
- ct->clear_sets();
}
void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS::CanvasItemTextureRepeat p_repeat) {
@@ -640,17 +618,14 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS:
ERR_FAIL_NULL(ct);
ct->texture_repeat = p_repeat;
- ct->clear_sets();
}
-bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular, bool p_texture_is_data) {
+TextureStorage::CanvasTextureInfo TextureStorage::canvas_texture_get_info(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, bool p_use_srgb, bool p_texture_is_data) {
MaterialStorage *material_storage = MaterialStorage::get_singleton();
CanvasTexture *ct = nullptr;
Texture *t = get_texture(p_texture);
- // TODO once we have our texture storage split off we'll look into moving this code into canvas_texture
-
if (t) {
//regular texture
if (!t->canvas_texture) {
@@ -667,93 +642,71 @@ bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasIte
}
if (!ct) {
- return false; //invalid texture RID
+ return CanvasTextureInfo(); //invalid texture RID
}
RS::CanvasItemTextureFilter filter = ct->texture_filter != RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? ct->texture_filter : p_base_filter;
- ERR_FAIL_COND_V(filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT, false);
+ ERR_FAIL_COND_V(filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT, CanvasTextureInfo());
RS::CanvasItemTextureRepeat repeat = ct->texture_repeat != RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT ? ct->texture_repeat : p_base_repeat;
- ERR_FAIL_COND_V(repeat == RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT, false);
+ ERR_FAIL_COND_V(repeat == RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT, CanvasTextureInfo());
- RID uniform_set = ct->uniform_sets[filter][repeat][int(p_use_srgb)];
- if (!RD::get_singleton()->uniform_set_is_valid(uniform_set)) {
- //create and update
- Vector<RD::Uniform> uniforms;
+ CanvasTextureCache &ctc = ct->info_cache[int(p_use_srgb)];
+ if (!RD::get_singleton()->texture_is_valid(ctc.diffuse) ||
+ !RD::get_singleton()->texture_is_valid(ctc.normal) ||
+ !RD::get_singleton()->texture_is_valid(ctc.specular)) {
{ //diffuse
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- u.binding = 0;
-
t = get_texture(ct->diffuse);
if (!t) {
- u.append_id(texture_rd_get_default(DEFAULT_RD_TEXTURE_WHITE));
+ ctc.diffuse = texture_rd_get_default(DEFAULT_RD_TEXTURE_WHITE);
ct->size_cache = Size2i(1, 1);
} else {
- u.append_id(t->rd_texture_srgb.is_valid() && p_use_srgb && !p_texture_is_data ? t->rd_texture_srgb : t->rd_texture);
+ ctc.diffuse = t->rd_texture_srgb.is_valid() && p_use_srgb && !p_texture_is_data ? t->rd_texture_srgb : t->rd_texture;
ct->size_cache = Size2i(t->width_2d, t->height_2d);
if (t->render_target) {
t->render_target->was_used = true;
}
}
- uniforms.push_back(u);
}
{ //normal
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- u.binding = 1;
-
t = get_texture(ct->normal_map);
if (!t) {
- u.append_id(texture_rd_get_default(DEFAULT_RD_TEXTURE_NORMAL));
+ ctc.normal = texture_rd_get_default(DEFAULT_RD_TEXTURE_NORMAL);
ct->use_normal_cache = false;
} else {
- u.append_id(t->rd_texture);
+ ctc.normal = t->rd_texture;
ct->use_normal_cache = true;
if (t->render_target) {
t->render_target->was_used = true;
}
}
- uniforms.push_back(u);
}
{ //specular
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
- u.binding = 2;
-
t = get_texture(ct->specular);
if (!t) {
- u.append_id(texture_rd_get_default(DEFAULT_RD_TEXTURE_WHITE));
+ ctc.specular = texture_rd_get_default(DEFAULT_RD_TEXTURE_WHITE);
ct->use_specular_cache = false;
} else {
- u.append_id(t->rd_texture);
+ ctc.specular = t->rd_texture;
ct->use_specular_cache = true;
if (t->render_target) {
t->render_target->was_used = true;
}
}
- uniforms.push_back(u);
- }
- { //sampler
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_SAMPLER;
- u.binding = 3;
- u.append_id(material_storage->sampler_rd_get_default(filter, repeat));
- uniforms.push_back(u);
}
-
- uniform_set = RD::get_singleton()->uniform_set_create(uniforms, p_base_shader, p_base_set);
- ct->uniform_sets[filter][repeat][int(p_use_srgb)] = uniform_set;
- ct->cleared_cache = false;
}
- r_uniform_set = uniform_set;
- r_size = ct->size_cache;
- r_specular_shininess = ct->specular_color;
- r_use_normal = ct->use_normal_cache;
- r_use_specular = ct->use_specular_cache;
+ CanvasTextureInfo res;
+ res.diffuse = ctc.diffuse;
+ res.normal = ctc.normal;
+ res.specular = ctc.specular;
+ res.sampler = material_storage->sampler_rd_get_default(filter, repeat);
+ res.size = ct->size_cache;
+ res.specular_color = ct->specular_color;
+ res.use_normal = ct->use_normal_cache;
+ res.use_specular = ct->use_specular_cache;
- return true;
+ return res;
}
/* Texture API */
@@ -3278,13 +3231,13 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
if (rt->size.width == 0 || rt->size.height == 0) {
return;
}
+
+ rt->color_format = render_target_get_color_format(rt->use_hdr, false);
+ rt->color_format_srgb = render_target_get_color_format(rt->use_hdr, true);
+
if (rt->use_hdr) {
- rt->color_format = RendererSceneRenderRD::get_singleton()->_render_buffers_get_color_format();
- rt->color_format_srgb = rt->color_format;
rt->image_format = rt->is_transparent ? Image::FORMAT_RGBAH : Image::FORMAT_RGBH;
} else {
- rt->color_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
- rt->color_format_srgb = RD::DATA_FORMAT_R8G8B8A8_SRGB;
rt->image_format = rt->is_transparent ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8;
}
@@ -3303,8 +3256,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
rd_color_attachment_format.texture_type = RD::TEXTURE_TYPE_2D;
}
rd_color_attachment_format.samples = RD::TEXTURE_SAMPLES_1;
- rd_color_attachment_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT;
- rd_color_attachment_format.usage_bits |= RD::TEXTURE_USAGE_STORAGE_BIT; // FIXME we need this only when FSR is enabled
+ rd_color_attachment_format.usage_bits = render_target_get_color_usage_bits(false);
rd_color_attachment_format.shareable_formats.push_back(rt->color_format);
rd_color_attachment_format.shareable_formats.push_back(rt->color_format_srgb);
if (rt->msaa != RS::VIEWPORT_MSAA_DISABLED) {
@@ -3326,7 +3278,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
RD::TEXTURE_SAMPLES_8,
};
rd_color_multisample_format.samples = texture_samples[rt->msaa];
- rd_color_multisample_format.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
+ rd_color_multisample_format.usage_bits = render_target_get_color_usage_bits(true);
RD::TextureView rd_view_multisample;
rd_color_multisample_format.is_resolve_buffer = false;
rt->color_multisample = RD::get_singleton()->texture_create(rd_color_multisample_format, rd_view_multisample);
@@ -4216,3 +4168,20 @@ RID TextureStorage::render_target_get_vrs_texture(RID p_render_target) const {
return rt->vrs_texture;
}
+
+RD::DataFormat TextureStorage::render_target_get_color_format(bool p_use_hdr, bool p_srgb) {
+ if (p_use_hdr) {
+ return RendererSceneRenderRD::get_singleton()->_render_buffers_get_color_format();
+ } else {
+ return p_srgb ? RD::DATA_FORMAT_R8G8B8A8_SRGB : RD::DATA_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
+uint32_t TextureStorage::render_target_get_color_usage_bits(bool p_msaa) {
+ if (p_msaa) {
+ return RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT;
+ } else {
+ // FIXME: Storage bit should only be requested when FSR is required.
+ return RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_STORAGE_BIT;
+ }
+}
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
index 9de4ff7b6b..2135ee3e3b 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
@@ -76,6 +76,21 @@ public:
TYPE_3D
};
+ struct CanvasTextureInfo {
+ RID diffuse;
+ RID normal;
+ RID specular;
+ RID sampler;
+ Size2i size;
+ Color specular_color;
+
+ bool use_normal = false;
+ bool use_specular = false;
+
+ _FORCE_INLINE_ bool is_valid() const { return diffuse.is_valid(); }
+ _FORCE_INLINE_ bool is_null() const { return diffuse.is_null(); }
+ };
+
private:
friend class LightStorage;
friend class MaterialStorage;
@@ -86,6 +101,12 @@ private:
/* Canvas Texture API */
+ struct CanvasTextureCache {
+ RID diffuse = RID();
+ RID normal = RID();
+ RID specular = RID();
+ };
+
class CanvasTexture {
public:
RID diffuse;
@@ -96,14 +117,14 @@ private:
RS::CanvasItemTextureFilter texture_filter = RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT;
RS::CanvasItemTextureRepeat texture_repeat = RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT;
- RID uniform_sets[RS::CANVAS_ITEM_TEXTURE_FILTER_MAX][RS::CANVAS_ITEM_TEXTURE_REPEAT_MAX][2];
+ CanvasTextureCache info_cache[2];
Size2i size_cache = Size2i(1, 1);
bool use_normal_cache = false;
bool use_specular_cache = false;
bool cleared_cache = true;
- void clear_sets();
+ void clear_cache();
~CanvasTexture();
};
@@ -477,14 +498,12 @@ public:
virtual void canvas_texture_set_texture_filter(RID p_item, RS::CanvasItemTextureFilter p_filter) override;
virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override;
- bool canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular, bool p_texture_is_data);
+ CanvasTextureInfo canvas_texture_get_info(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, bool p_use_srgb, bool p_texture_is_data);
/* Texture API */
bool owns_texture(RID p_rid) const { return texture_owner.owns(p_rid); };
- virtual bool can_create_resources_async() const override;
-
virtual RID texture_allocate() override;
virtual void texture_free(RID p_rid) override;
@@ -777,6 +796,9 @@ public:
void render_target_set_framebuffer_uniform_set(RID p_render_target, RID p_uniform_set);
void render_target_set_backbuffer_uniform_set(RID p_render_target, RID p_uniform_set);
+
+ static RD::DataFormat render_target_get_color_format(bool p_use_hdr, bool p_srgb);
+ static uint32_t render_target_get_color_usage_bits(bool p_msaa);
};
} // namespace RendererRD
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 43abb22e3d..8b7ec08868 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -1690,6 +1690,14 @@ Variant RendererSceneCull::instance_geometry_get_shader_parameter_default_value(
return Variant();
}
+void RendererSceneCull::mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) {
+ scene_render->mesh_generate_pipelines(p_mesh, p_background_compilation);
+}
+
+uint32_t RendererSceneCull::get_pipeline_compilations(RS::PipelineSource p_source) {
+ return scene_render->get_pipeline_compilations(p_source);
+}
+
void RendererSceneCull::instance_geometry_get_shader_parameter_list(RID p_instance, List<PropertyInfo> *p_parameters) const {
const Instance *instance = const_cast<RendererSceneCull *>(this)->instance_owner.get_or_null(p_instance);
ERR_FAIL_NULL(instance);
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 972f66d325..5aae59eb51 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1102,6 +1102,9 @@ public:
virtual Variant instance_geometry_get_shader_parameter(RID p_instance, const StringName &p_parameter) const;
virtual Variant instance_geometry_get_shader_parameter_default_value(RID p_instance, const StringName &p_parameter) const;
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation);
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source);
+
_FORCE_INLINE_ void _update_instance(Instance *p_instance);
_FORCE_INLINE_ void _update_instance_aabb(Instance *p_instance);
_FORCE_INLINE_ void _update_dirty_instance(Instance *p_instance);
diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h
index 72ccbcdf11..4d81a9b6a3 100644
--- a/servers/rendering/renderer_scene_render.h
+++ b/servers/rendering/renderer_scene_render.h
@@ -58,6 +58,11 @@ public:
virtual void geometry_instance_free(RenderGeometryInstance *p_geometry_instance) = 0;
virtual uint32_t geometry_instance_get_pair_mask() = 0;
+ /* PIPELINES */
+
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) = 0;
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) = 0;
+
/* SDFGI UPDATE */
virtual void sdfgi_update(const Ref<RenderSceneBuffers> &p_render_buffers, RID p_environment, const Vector3 &p_world_position) = 0;
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index e322bba768..b287550986 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -36,7 +36,15 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
+// TODO: Thread safety
+// - Roll back thread safe attribute for RID_Owner members after the read-only/atomic update scheme is implemented.
+
#define FORCE_SEPARATE_PRESENT_QUEUE 0
+#define PRINT_FRAMEBUFFER_FORMAT 0
+
+#define ERR_RENDER_THREAD_MSG String("This function (") + String(__func__) + String(") can only be called from the render thread. ")
+#define ERR_RENDER_THREAD_GUARD() ERR_FAIL_COND_MSG(render_thread_id != Thread::get_caller_id(), ERR_RENDER_THREAD_MSG);
+#define ERR_RENDER_THREAD_GUARD_V(m_ret) ERR_FAIL_COND_V_MSG(render_thread_id != Thread::get_caller_id(), (m_ret), ERR_RENDER_THREAD_MSG);
/**************************/
/**** HELPER FUNCTIONS ****/
@@ -138,6 +146,8 @@ RenderingDevice::ShaderSPIRVGetCacheKeyFunction RenderingDevice::get_spirv_cache
/***************************/
void RenderingDevice::_add_dependency(RID p_id, RID p_depends_on) {
+ _THREAD_SAFE_METHOD_
+
HashSet<RID> *set = dependency_map.getptr(p_depends_on);
if (set == nullptr) {
set = &dependency_map.insert(p_depends_on, HashSet<RID>())->value;
@@ -152,6 +162,8 @@ void RenderingDevice::_add_dependency(RID p_id, RID p_depends_on) {
}
void RenderingDevice::_free_dependencies(RID p_id) {
+ _THREAD_SAFE_METHOD_
+
// Direct dependencies must be freed.
HashMap<RID, HashSet<RID>>::Iterator E = dependency_map.find(p_id);
@@ -236,6 +248,35 @@ RenderingDevice::Buffer *RenderingDevice::_get_buffer_from_owner(RID p_buffer) {
return buffer;
}
+Error RenderingDevice::_buffer_initialize(Buffer *p_buffer, const uint8_t *p_data, size_t p_data_size, uint32_t p_required_align) {
+ uint32_t transfer_worker_offset;
+ TransferWorker *transfer_worker = _acquire_transfer_worker(p_data_size, p_required_align, transfer_worker_offset);
+ p_buffer->transfer_worker_index = transfer_worker->index;
+
+ {
+ MutexLock lock(transfer_worker->operations_mutex);
+ p_buffer->transfer_worker_operation = ++transfer_worker->operations_counter;
+ }
+
+ // Copy to the worker's staging buffer.
+ uint8_t *data_ptr = driver->buffer_map(transfer_worker->staging_buffer);
+ ERR_FAIL_NULL_V(data_ptr, ERR_CANT_CREATE);
+
+ memcpy(data_ptr + transfer_worker_offset, p_data, p_data_size);
+ driver->buffer_unmap(transfer_worker->staging_buffer);
+
+ // Copy from the staging buffer to the real buffer.
+ RDD::BufferCopyRegion region;
+ region.src_offset = transfer_worker_offset;
+ region.dst_offset = 0;
+ region.size = p_data_size;
+ driver->command_copy_buffer(transfer_worker->command_buffer, transfer_worker->staging_buffer, p_buffer->driver_id, region);
+
+ _release_transfer_worker(transfer_worker);
+
+ return OK;
+}
+
Error RenderingDevice::_insert_staging_block() {
StagingBufferBlock block;
@@ -386,32 +427,89 @@ void RenderingDevice::_staging_buffer_execute_required_action(StagingRequiredAct
}
}
-Error RenderingDevice::_buffer_update(Buffer *p_buffer, RID p_buffer_id, size_t p_offset, const uint8_t *p_data, size_t p_data_size, bool p_use_draw_queue, uint32_t p_required_align) {
+Error RenderingDevice::buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size) {
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
+
+ ERR_FAIL_COND_V_MSG(draw_list, ERR_INVALID_PARAMETER,
+ "Copying buffers is forbidden during creation of a draw list");
+ ERR_FAIL_COND_V_MSG(compute_list, ERR_INVALID_PARAMETER,
+ "Copying buffers is forbidden during creation of a compute list");
+
+ Buffer *src_buffer = _get_buffer_from_owner(p_src_buffer);
+ if (!src_buffer) {
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Source buffer argument is not a valid buffer of any type.");
+ }
+
+ Buffer *dst_buffer = _get_buffer_from_owner(p_dst_buffer);
+ if (!dst_buffer) {
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Destination buffer argument is not a valid buffer of any type.");
+ }
+
+ // Validate the copy's dimensions for both buffers.
+ ERR_FAIL_COND_V_MSG((p_size + p_src_offset) > src_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the source buffer.");
+ ERR_FAIL_COND_V_MSG((p_size + p_dst_offset) > dst_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the destination buffer.");
+
+ _check_transfer_worker_buffer(src_buffer);
+ _check_transfer_worker_buffer(dst_buffer);
+
+ // Perform the copy.
+ RDD::BufferCopyRegion region;
+ region.src_offset = p_src_offset;
+ region.dst_offset = p_dst_offset;
+ region.size = p_size;
+
+ if (_buffer_make_mutable(dst_buffer, p_dst_buffer)) {
+ // The destination buffer must be mutable to be used as a copy destination.
+ draw_graph.add_synchronization();
+ }
+
+ draw_graph.add_buffer_copy(src_buffer->driver_id, src_buffer->draw_tracker, dst_buffer->driver_id, dst_buffer->draw_tracker, region);
+
+ return OK;
+}
+
+Error RenderingDevice::buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data) {
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
+
+ copy_bytes_count += p_size;
+
+ ERR_FAIL_COND_V_MSG(draw_list, ERR_INVALID_PARAMETER,
+ "Updating buffers is forbidden during creation of a draw list");
+ ERR_FAIL_COND_V_MSG(compute_list, ERR_INVALID_PARAMETER,
+ "Updating buffers is forbidden during creation of a compute list");
+
+ Buffer *buffer = _get_buffer_from_owner(p_buffer);
+ ERR_FAIL_NULL_V_MSG(buffer, ERR_INVALID_PARAMETER, "Buffer argument is not a valid buffer of any type.");
+ ERR_FAIL_COND_V_MSG(p_offset + p_size > buffer->size, ERR_INVALID_PARAMETER, "Attempted to write buffer (" + itos((p_offset + p_size) - buffer->size) + " bytes) past the end.");
+
+ _check_transfer_worker_buffer(buffer);
+
// Submitting may get chunked for various reasons, so convert this to a task.
- size_t to_submit = p_data_size;
+ size_t to_submit = p_size;
size_t submit_from = 0;
thread_local LocalVector<RDG::RecordedBufferCopy> command_buffer_copies_vector;
command_buffer_copies_vector.clear();
+ const uint8_t *src_data = reinterpret_cast<const uint8_t *>(p_data);
+ const uint32_t required_align = 32;
while (to_submit > 0) {
uint32_t block_write_offset;
uint32_t block_write_amount;
StagingRequiredAction required_action;
- Error err = _staging_buffer_allocate(MIN(to_submit, staging_buffer_block_size), p_required_align, block_write_offset, block_write_amount, required_action);
+ Error err = _staging_buffer_allocate(MIN(to_submit, staging_buffer_block_size), required_align, block_write_offset, block_write_amount, required_action);
if (err) {
return err;
}
- if (p_use_draw_queue && !command_buffer_copies_vector.is_empty() && required_action == STAGING_REQUIRED_ACTION_FLUSH_AND_STALL_ALL) {
- if (_buffer_make_mutable(p_buffer, p_buffer_id)) {
+ if (!command_buffer_copies_vector.is_empty() && required_action == STAGING_REQUIRED_ACTION_FLUSH_AND_STALL_ALL) {
+ if (_buffer_make_mutable(buffer, p_buffer)) {
// The buffer must be mutable to be used as a copy destination.
draw_graph.add_synchronization();
}
- // If we're using the draw queue and the staging buffer requires flushing everything, we submit the command early and clear the current vector.
- draw_graph.add_buffer_update(p_buffer->driver_id, p_buffer->draw_tracker, command_buffer_copies_vector);
+ draw_graph.add_buffer_update(buffer->driver_id, buffer->draw_tracker, command_buffer_copies_vector);
command_buffer_copies_vector.clear();
}
@@ -422,7 +520,7 @@ Error RenderingDevice::_buffer_update(Buffer *p_buffer, RID p_buffer_id, size_t
ERR_FAIL_NULL_V(data_ptr, ERR_CANT_CREATE);
// Copy to staging buffer.
- memcpy(data_ptr + block_write_offset, p_data + submit_from, block_write_amount);
+ memcpy(data_ptr + block_write_offset, src_data + submit_from, block_write_amount);
// Unmap.
driver->buffer_unmap(staging_buffer_blocks[staging_buffer_current].driver_id);
@@ -433,14 +531,10 @@ Error RenderingDevice::_buffer_update(Buffer *p_buffer, RID p_buffer_id, size_t
region.dst_offset = submit_from + p_offset;
region.size = block_write_amount;
- if (p_use_draw_queue) {
- RDG::RecordedBufferCopy buffer_copy;
- buffer_copy.source = staging_buffer_blocks[staging_buffer_current].driver_id;
- buffer_copy.region = region;
- command_buffer_copies_vector.push_back(buffer_copy);
- } else {
- driver->command_copy_buffer(frames[frame].setup_command_buffer, staging_buffer_blocks[staging_buffer_current].driver_id, p_buffer->driver_id, region);
- }
+ RDG::RecordedBufferCopy buffer_copy;
+ buffer_copy.source = staging_buffer_blocks[staging_buffer_current].driver_id;
+ buffer_copy.region = region;
+ command_buffer_copies_vector.push_back(buffer_copy);
staging_buffer_blocks.write[staging_buffer_current].fill_amount = block_write_offset + block_write_amount;
@@ -448,77 +542,18 @@ Error RenderingDevice::_buffer_update(Buffer *p_buffer, RID p_buffer_id, size_t
submit_from += block_write_amount;
}
- if (p_use_draw_queue && !command_buffer_copies_vector.is_empty()) {
- if (_buffer_make_mutable(p_buffer, p_buffer_id)) {
+ if (!command_buffer_copies_vector.is_empty()) {
+ if (_buffer_make_mutable(buffer, p_buffer)) {
// The buffer must be mutable to be used as a copy destination.
draw_graph.add_synchronization();
}
- draw_graph.add_buffer_update(p_buffer->driver_id, p_buffer->draw_tracker, command_buffer_copies_vector);
+ draw_graph.add_buffer_update(buffer->driver_id, buffer->draw_tracker, command_buffer_copies_vector);
}
- return OK;
-}
-
-Error RenderingDevice::buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size) {
- _THREAD_SAFE_METHOD_
-
- ERR_FAIL_COND_V_MSG(draw_list, ERR_INVALID_PARAMETER,
- "Copying buffers is forbidden during creation of a draw list");
- ERR_FAIL_COND_V_MSG(compute_list, ERR_INVALID_PARAMETER,
- "Copying buffers is forbidden during creation of a compute list");
-
- Buffer *src_buffer = _get_buffer_from_owner(p_src_buffer);
- if (!src_buffer) {
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Source buffer argument is not a valid buffer of any type.");
- }
-
- Buffer *dst_buffer = _get_buffer_from_owner(p_dst_buffer);
- if (!dst_buffer) {
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Destination buffer argument is not a valid buffer of any type.");
- }
-
- // Validate the copy's dimensions for both buffers.
- ERR_FAIL_COND_V_MSG((p_size + p_src_offset) > src_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the source buffer.");
- ERR_FAIL_COND_V_MSG((p_size + p_dst_offset) > dst_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the destination buffer.");
-
- // Perform the copy.
- RDD::BufferCopyRegion region;
- region.src_offset = p_src_offset;
- region.dst_offset = p_dst_offset;
- region.size = p_size;
-
- if (_buffer_make_mutable(dst_buffer, p_dst_buffer)) {
- // The destination buffer must be mutable to be used as a copy destination.
- draw_graph.add_synchronization();
- }
-
- draw_graph.add_buffer_copy(src_buffer->driver_id, src_buffer->draw_tracker, dst_buffer->driver_id, dst_buffer->draw_tracker, region);
-
- return OK;
-}
-
-Error RenderingDevice::buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data) {
- _THREAD_SAFE_METHOD_
-
- copy_bytes_count += p_size;
-
- ERR_FAIL_COND_V_MSG(draw_list, ERR_INVALID_PARAMETER,
- "Updating buffers is forbidden during creation of a draw list");
- ERR_FAIL_COND_V_MSG(compute_list, ERR_INVALID_PARAMETER,
- "Updating buffers is forbidden during creation of a compute list");
-
- Buffer *buffer = _get_buffer_from_owner(p_buffer);
- if (!buffer) {
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Buffer argument is not a valid buffer of any type.");
- }
-
- ERR_FAIL_COND_V_MSG(p_offset + p_size > buffer->size, ERR_INVALID_PARAMETER,
- "Attempted to write buffer (" + itos((p_offset + p_size) - buffer->size) + " bytes) past the end.");
-
gpu_copy_count++;
- return _buffer_update(buffer, p_buffer, p_offset, (uint8_t *)p_data, p_size, true);
+ return OK;
}
String RenderingDevice::get_perf_report() const {
@@ -534,7 +569,7 @@ void RenderingDevice::update_perf_report() {
}
Error RenderingDevice::buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_size) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
ERR_FAIL_COND_V_MSG((p_size % 4) != 0, ERR_INVALID_PARAMETER,
"Size must be a multiple of four");
@@ -551,6 +586,8 @@ Error RenderingDevice::buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_
ERR_FAIL_COND_V_MSG(p_offset + p_size > buffer->size, ERR_INVALID_PARAMETER,
"Attempted to write buffer (" + itos((p_offset + p_size) - buffer->size) + " bytes) past the end.");
+ _check_transfer_worker_buffer(buffer);
+
if (_buffer_make_mutable(buffer, p_buffer)) {
// The destination buffer must be mutable to be used as a clear destination.
draw_graph.add_synchronization();
@@ -562,7 +599,7 @@ Error RenderingDevice::buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_
}
Vector<uint8_t> RenderingDevice::buffer_get_data(RID p_buffer, uint32_t p_offset, uint32_t p_size) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(Vector<uint8_t>());
Buffer *buffer = _get_buffer_from_owner(p_buffer);
if (!buffer) {
@@ -577,6 +614,8 @@ Vector<uint8_t> RenderingDevice::buffer_get_data(RID p_buffer, uint32_t p_offset
"Size is larger than the buffer.");
}
+ _check_transfer_worker_buffer(buffer);
+
RDD::BufferID tmp_buffer = driver->buffer_create(buffer->size, RDD::BUFFER_USAGE_TRANSFER_TO_BIT, RDD::MEMORY_ALLOCATION_TYPE_CPU);
ERR_FAIL_COND_V(!tmp_buffer, Vector<uint8_t>());
@@ -607,8 +646,6 @@ Vector<uint8_t> RenderingDevice::buffer_get_data(RID p_buffer, uint32_t p_offset
}
RID RenderingDevice::storage_buffer_create(uint32_t p_size_bytes, const Vector<uint8_t> &p_data, BitField<StorageBufferUsage> p_usage) {
- _THREAD_SAFE_METHOD_
-
ERR_FAIL_COND_V(p_data.size() && (uint32_t)p_data.size() != p_size_bytes, RID());
Buffer buffer;
@@ -625,10 +662,12 @@ RID RenderingDevice::storage_buffer_create(uint32_t p_size_bytes, const Vector<u
buffer.draw_tracker->buffer_driver_id = buffer.driver_id;
if (p_data.size()) {
- _buffer_update(&buffer, RID(), 0, p_data.ptr(), p_data.size());
+ _buffer_initialize(&buffer, p_data.ptr(), p_data.size());
}
+ _THREAD_SAFE_LOCK_
buffer_memory += buffer.size;
+ _THREAD_SAFE_UNLOCK_
RID id = storage_buffer_owner.make_rid(buffer);
#ifdef DEV_ENABLED
@@ -638,8 +677,6 @@ RID RenderingDevice::storage_buffer_create(uint32_t p_size_bytes, const Vector<u
}
RID RenderingDevice::texture_buffer_create(uint32_t p_size_elements, DataFormat p_format, const Vector<uint8_t> &p_data) {
- _THREAD_SAFE_METHOD_
-
uint32_t element_size = get_format_vertex_size(p_format);
ERR_FAIL_COND_V_MSG(element_size == 0, RID(), "Format requested is not supported for texture buffers");
uint64_t size_bytes = uint64_t(element_size) * p_size_elements;
@@ -665,10 +702,12 @@ RID RenderingDevice::texture_buffer_create(uint32_t p_size_elements, DataFormat
}
if (p_data.size()) {
- _buffer_update(&texture_buffer, RID(), 0, p_data.ptr(), p_data.size());
+ _buffer_initialize(&texture_buffer, p_data.ptr(), p_data.size());
}
+ _THREAD_SAFE_LOCK_
buffer_memory += size_bytes;
+ _THREAD_SAFE_UNLOCK_
RID id = texture_buffer_owner.make_rid(texture_buffer);
#ifdef DEV_ENABLED
@@ -682,8 +721,6 @@ RID RenderingDevice::texture_buffer_create(uint32_t p_size_elements, DataFormat
/*****************/
RID RenderingDevice::texture_create(const TextureFormat &p_format, const TextureView &p_view, const Vector<Vector<uint8_t>> &p_data) {
- _THREAD_SAFE_METHOD_
-
// Some adjustments will happen.
TextureFormat format = p_format;
@@ -740,6 +777,9 @@ RID RenderingDevice::texture_create(const TextureFormat &p_format, const Texture
"Data for slice index " + itos(i) + " (mapped to layer " + itos(i) + ") differs in size (supplied: " + itos(p_data[i].size()) + ") than what is required by the format (" + itos(required_size) + ").");
}
+ ERR_FAIL_COND_V_MSG(format.usage_bits & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, RID(),
+ "Textures created as depth attachments can't be initialized with data directly. Use RenderingDevice::texture_update() instead.");
+
if (!(format.usage_bits & TEXTURE_USAGE_CAN_UPDATE_BIT)) {
forced_usage_bits = TEXTURE_USAGE_CAN_UPDATE_BIT;
}
@@ -839,7 +879,7 @@ RID RenderingDevice::texture_create(const TextureFormat &p_format, const Texture
if (p_data.size()) {
for (uint32_t i = 0; i < p_format.array_layers; i++) {
- _texture_update(id, i, p_data[i], true, false);
+ _texture_initialize(id, i, p_data[i]);
}
if (texture.draw_tracker != nullptr) {
@@ -852,8 +892,6 @@ RID RenderingDevice::texture_create(const TextureFormat &p_format, const Texture
}
RID RenderingDevice::texture_create_shared(const TextureView &p_view, RID p_with_texture) {
- _THREAD_SAFE_METHOD_
-
Texture *src_texture = texture_owner.get_or_null(p_with_texture);
ERR_FAIL_NULL_V(src_texture, RID());
@@ -939,7 +977,6 @@ RID RenderingDevice::texture_create_shared(const TextureView &p_view, RID p_with
}
RID RenderingDevice::texture_create_from_extension(TextureType p_type, DataFormat p_format, TextureSamples p_samples, BitField<RenderingDevice::TextureUsageBits> p_usage, uint64_t p_image, uint64_t p_width, uint64_t p_height, uint64_t p_depth, uint64_t p_layers) {
- _THREAD_SAFE_METHOD_
// This method creates a texture object using a VkImage created by an extension, module or other external source (OpenXR uses this).
Texture texture;
@@ -982,8 +1019,6 @@ RID RenderingDevice::texture_create_from_extension(TextureType p_type, DataForma
}
RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view, RID p_with_texture, uint32_t p_layer, uint32_t p_mipmap, uint32_t p_mipmaps, TextureSliceType p_slice_type, uint32_t p_layers) {
- _THREAD_SAFE_METHOD_
-
Texture *src_texture = texture_owner.get_or_null(p_with_texture);
ERR_FAIL_NULL_V(src_texture, RID());
@@ -1124,10 +1159,6 @@ RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view,
return id;
}
-Error RenderingDevice::texture_update(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data) {
- return _texture_update(p_texture, p_layer, p_data, false, true);
-}
-
static _ALWAYS_INLINE_ void _copy_region(uint8_t const *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_x, uint32_t p_src_y, uint32_t p_src_w, uint32_t p_src_h, uint32_t p_src_full_w, uint32_t p_dst_pitch, uint32_t p_unit_size) {
uint32_t src_offset = (p_src_y * p_src_full_w + p_src_x) * p_unit_size;
uint32_t dst_offset = 0;
@@ -1144,11 +1175,184 @@ static _ALWAYS_INLINE_ void _copy_region(uint8_t const *__restrict p_src, uint8_
}
}
-Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data, bool p_use_setup_queue, bool p_validate_can_update) {
- _THREAD_SAFE_METHOD_
+static _ALWAYS_INLINE_ void _copy_region_block_or_regular(const uint8_t *p_read_ptr, uint8_t *p_write_ptr, uint32_t p_x, uint32_t p_y, uint32_t p_width, uint32_t p_region_w, uint32_t p_region_h, uint32_t p_block_w, uint32_t p_block_h, uint32_t p_dst_pitch, uint32_t p_pixel_size, uint32_t p_block_size) {
+ if (p_block_w != 1 || p_block_h != 1) {
+ // Block format.
+ uint32_t xb = p_x / p_block_w;
+ uint32_t yb = p_y / p_block_h;
+ uint32_t wb = p_width / p_block_w;
+ uint32_t region_wb = p_region_w / p_block_w;
+ uint32_t region_hb = p_region_h / p_block_h;
+ _copy_region(p_read_ptr, p_write_ptr, xb, yb, region_wb, region_hb, wb, p_dst_pitch, p_block_size);
+ } else {
+ // Regular format.
+ _copy_region(p_read_ptr, p_write_ptr, p_x, p_y, p_region_w, p_region_h, p_width, p_dst_pitch, p_pixel_size);
+ }
+}
+
+uint32_t RenderingDevice::_texture_layer_count(Texture *p_texture) const {
+ switch (p_texture->type) {
+ case TEXTURE_TYPE_CUBE:
+ case TEXTURE_TYPE_CUBE_ARRAY:
+ return p_texture->layers * 6;
+ default:
+ return p_texture->layers;
+ }
+}
+
+uint32_t RenderingDevice::_texture_alignment(Texture *p_texture) const {
+ uint32_t alignment = get_compressed_image_format_block_byte_size(p_texture->format);
+ if (alignment == 1) {
+ alignment = get_image_format_pixel_size(p_texture->format);
+ }
+
+ return STEPIFY(alignment, driver->api_trait_get(RDD::API_TRAIT_TEXTURE_TRANSFER_ALIGNMENT));
+}
+
+Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data) {
+ Texture *texture = texture_owner.get_or_null(p_texture);
+ ERR_FAIL_NULL_V(texture, ERR_INVALID_PARAMETER);
+
+ if (texture->owner != RID()) {
+ p_texture = texture->owner;
+ texture = texture_owner.get_or_null(texture->owner);
+ ERR_FAIL_NULL_V(texture, ERR_BUG); // This is a bug.
+ }
+
+ uint32_t layer_count = _texture_layer_count(texture);
+ ERR_FAIL_COND_V(p_layer >= layer_count, ERR_INVALID_PARAMETER);
+
+ uint32_t width, height;
+ uint32_t tight_mip_size = get_image_format_required_size(texture->format, texture->width, texture->height, texture->depth, texture->mipmaps, &width, &height);
+ uint32_t required_size = tight_mip_size;
+ uint32_t required_align = _texture_alignment(texture);
+
+ ERR_FAIL_COND_V_MSG(required_size != (uint32_t)p_data.size(), ERR_INVALID_PARAMETER,
+ "Required size for texture update (" + itos(required_size) + ") does not match data supplied size (" + itos(p_data.size()) + ").");
+
+ uint32_t block_w, block_h;
+ get_compressed_image_format_block_dimensions(texture->format, block_w, block_h);
+
+ uint32_t pixel_size = get_image_format_pixel_size(texture->format);
+ uint32_t pixel_rshift = get_compressed_image_format_pixel_rshift(texture->format);
+ uint32_t block_size = get_compressed_image_format_block_byte_size(texture->format);
+
+ // The algorithm operates on two passes, one to figure out the total size the staging buffer will require to allocate and another one where the copy is actually performed.
+ uint32_t staging_worker_offset = 0;
+ uint32_t staging_local_offset = 0;
+ TransferWorker *transfer_worker = nullptr;
+ const uint8_t *read_ptr = p_data.ptr();
+ uint8_t *write_ptr = nullptr;
+ for (uint32_t pass = 0; pass < 2; pass++) {
+ const bool copy_pass = (pass == 1);
+ if (copy_pass) {
+ transfer_worker = _acquire_transfer_worker(staging_local_offset, required_align, staging_worker_offset);
+ texture->transfer_worker_index = transfer_worker->index;
+
+ {
+ MutexLock lock(transfer_worker->operations_mutex);
+ texture->transfer_worker_operation = ++transfer_worker->operations_counter;
+ }
+
+ staging_local_offset = 0;
+
+ write_ptr = driver->buffer_map(transfer_worker->staging_buffer);
+ ERR_FAIL_NULL_V(write_ptr, ERR_CANT_CREATE);
+
+ write_ptr += staging_worker_offset;
+
+ if (driver->api_trait_get(RDD::API_TRAIT_HONORS_PIPELINE_BARRIERS)) {
+ // Transition the texture to the optimal layout.
+ RDD::TextureBarrier tb;
+ tb.texture = texture->driver_id;
+ tb.dst_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
+ tb.prev_layout = RDD::TEXTURE_LAYOUT_UNDEFINED;
+ tb.next_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
+ tb.subresources.aspect = texture->barrier_aspect_flags;
+ tb.subresources.mipmap_count = texture->mipmaps;
+ tb.subresources.base_layer = p_layer;
+ tb.subresources.layer_count = 1;
+ driver->command_pipeline_barrier(transfer_worker->command_buffer, RDD::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, RDD::PIPELINE_STAGE_COPY_BIT, {}, {}, tb);
+ }
+ }
+
+ uint32_t mipmap_offset = 0;
+ uint32_t logic_width = texture->width;
+ uint32_t logic_height = texture->height;
+ for (uint32_t mm_i = 0; mm_i < texture->mipmaps; mm_i++) {
+ uint32_t depth = 0;
+ uint32_t image_total = get_image_format_required_size(texture->format, texture->width, texture->height, texture->depth, mm_i + 1, &width, &height, &depth);
+
+ const uint8_t *read_ptr_mipmap = read_ptr + mipmap_offset;
+ tight_mip_size = image_total - mipmap_offset;
+
+ for (uint32_t z = 0; z < depth; z++) {
+ if (required_align > 0) {
+ uint32_t align_offset = staging_local_offset % required_align;
+ if (align_offset != 0) {
+ staging_local_offset += required_align - align_offset;
+ }
+ }
+
+ uint32_t pitch = (width * pixel_size * block_w) >> pixel_rshift;
+ uint32_t pitch_step = driver->api_trait_get(RDD::API_TRAIT_TEXTURE_DATA_ROW_PITCH_STEP);
+ pitch = STEPIFY(pitch, pitch_step);
+ uint32_t to_allocate = pitch * height;
+ to_allocate >>= pixel_rshift;
+
+ if (copy_pass) {
+ const uint8_t *read_ptr_mipmap_layer = read_ptr_mipmap + (tight_mip_size / depth) * z;
+ _copy_region_block_or_regular(read_ptr_mipmap_layer, write_ptr, 0, 0, width, width, height, block_w, block_h, pitch, pixel_size, block_size);
+ write_ptr += to_allocate;
- ERR_FAIL_COND_V_MSG((draw_list || compute_list) && !p_use_setup_queue, ERR_INVALID_PARAMETER,
- "Updating textures is forbidden during creation of a draw or compute list");
+ RDD::BufferTextureCopyRegion copy_region;
+ copy_region.buffer_offset = staging_worker_offset + staging_local_offset;
+ copy_region.texture_subresources.aspect = texture->read_aspect_flags;
+ copy_region.texture_subresources.mipmap = mm_i;
+ copy_region.texture_subresources.base_layer = p_layer;
+ copy_region.texture_subresources.layer_count = 1;
+ copy_region.texture_offset = Vector3i(0, 0, z);
+ copy_region.texture_region_size = Vector3i(logic_width, logic_height, 1);
+ driver->command_copy_buffer_to_texture(transfer_worker->command_buffer, transfer_worker->staging_buffer, texture->driver_id, RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL, copy_region);
+ }
+
+ staging_local_offset += to_allocate;
+ }
+
+ mipmap_offset = image_total;
+ logic_width = MAX(1u, logic_width >> 1);
+ logic_height = MAX(1u, logic_height >> 1);
+ }
+
+ if (copy_pass) {
+ driver->buffer_unmap(transfer_worker->staging_buffer);
+
+ // If the texture does not have a tracker, it means it must be transitioned to the sampling state.
+ if (texture->draw_tracker == nullptr && driver->api_trait_get(RDD::API_TRAIT_HONORS_PIPELINE_BARRIERS)) {
+ RDD::TextureBarrier tb;
+ tb.texture = texture->driver_id;
+ tb.src_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
+ tb.prev_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
+ tb.next_layout = RDD::TEXTURE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ tb.subresources.aspect = texture->barrier_aspect_flags;
+ tb.subresources.mipmap_count = texture->mipmaps;
+ tb.subresources.base_layer = p_layer;
+ tb.subresources.layer_count = 1;
+
+ driver->command_pipeline_barrier(transfer_worker->command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, {}, {}, tb);
+ }
+
+ _release_transfer_worker(transfer_worker);
+ }
+ }
+
+ return OK;
+}
+
+Error RenderingDevice::texture_update(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data) {
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
+
+ ERR_FAIL_COND_V_MSG(draw_list || compute_list, ERR_INVALID_PARAMETER, "Updating textures is forbidden during creation of a draw or compute list");
Texture *texture = texture_owner.get_or_null(p_texture);
ERR_FAIL_NULL_V(texture, ERR_INVALID_PARAMETER);
@@ -1162,47 +1366,37 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
ERR_FAIL_COND_V_MSG(texture->bound, ERR_CANT_ACQUIRE_RESOURCE,
"Texture can't be updated while a draw list that uses it as part of a framebuffer is being created. Ensure the draw list is finalized (and that the color/depth texture using it is not set to `RenderingDevice.FINAL_ACTION_CONTINUE`) to update this texture.");
- ERR_FAIL_COND_V_MSG(p_validate_can_update && !(texture->usage_flags & TEXTURE_USAGE_CAN_UPDATE_BIT), ERR_INVALID_PARAMETER,
- "Texture requires the `RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT` to be set to be updatable.");
+ ERR_FAIL_COND_V_MSG(!(texture->usage_flags & TEXTURE_USAGE_CAN_UPDATE_BIT), ERR_INVALID_PARAMETER, "Texture requires the `RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT` to be set to be updatable.");
- ERR_FAIL_COND_V(p_layer >= texture->layers, ERR_INVALID_PARAMETER);
+ uint32_t layer_count = _texture_layer_count(texture);
+ ERR_FAIL_COND_V(p_layer >= layer_count, ERR_INVALID_PARAMETER);
uint32_t width, height;
uint32_t tight_mip_size = get_image_format_required_size(texture->format, texture->width, texture->height, texture->depth, texture->mipmaps, &width, &height);
uint32_t required_size = tight_mip_size;
- uint32_t required_align = get_compressed_image_format_block_byte_size(texture->format);
- if (required_align == 1) {
- required_align = get_image_format_pixel_size(texture->format);
- }
- required_align = STEPIFY(required_align, driver->api_trait_get(RDD::API_TRAIT_TEXTURE_TRANSFER_ALIGNMENT));
+ uint32_t required_align = _texture_alignment(texture);
ERR_FAIL_COND_V_MSG(required_size != (uint32_t)p_data.size(), ERR_INVALID_PARAMETER,
"Required size for texture update (" + itos(required_size) + ") does not match data supplied size (" + itos(p_data.size()) + ").");
+ _check_transfer_worker_texture(texture);
+
+ uint32_t block_w, block_h;
+ get_compressed_image_format_block_dimensions(texture->format, block_w, block_h);
+
+ uint32_t pixel_size = get_image_format_pixel_size(texture->format);
+ uint32_t pixel_rshift = get_compressed_image_format_pixel_rshift(texture->format);
+ uint32_t block_size = get_compressed_image_format_block_byte_size(texture->format);
+
uint32_t region_size = texture_upload_region_size_px;
- const uint8_t *r = p_data.ptr();
+ const uint8_t *read_ptr = p_data.ptr();
thread_local LocalVector<RDG::RecordedBufferToTextureCopy> command_buffer_to_texture_copies_vector;
command_buffer_to_texture_copies_vector.clear();
- if (p_use_setup_queue && driver->api_trait_get(RDD::API_TRAIT_HONORS_PIPELINE_BARRIERS)) {
- // When using the setup queue directly, we transition the texture to the optimal layout.
- RDD::TextureBarrier tb;
- tb.texture = texture->driver_id;
- tb.dst_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
- tb.prev_layout = RDD::TEXTURE_LAYOUT_UNDEFINED;
- tb.next_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
- tb.subresources.aspect = texture->barrier_aspect_flags;
- tb.subresources.mipmap_count = texture->mipmaps;
- tb.subresources.base_layer = p_layer;
- tb.subresources.layer_count = 1;
-
- driver->command_pipeline_barrier(frames[frame].setup_command_buffer, RDD::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, RDD::PIPELINE_STAGE_COPY_BIT, {}, {}, tb);
- } else if (!p_use_setup_queue) {
- // Indicate the texture will get modified for the shared texture fallback.
- _texture_update_shared_fallback(p_texture, texture, true);
- }
+ // Indicate the texture will get modified for the shared texture fallback.
+ _texture_update_shared_fallback(p_texture, texture, true);
uint32_t mipmap_offset = 0;
@@ -1213,13 +1407,11 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
uint32_t depth = 0;
uint32_t image_total = get_image_format_required_size(texture->format, texture->width, texture->height, texture->depth, mm_i + 1, &width, &height, &depth);
- const uint8_t *read_ptr_mipmap = r + mipmap_offset;
+ const uint8_t *read_ptr_mipmap = read_ptr + mipmap_offset;
tight_mip_size = image_total - mipmap_offset;
- for (uint32_t z = 0; z < depth; z++) { // For 3D textures, depth may be > 0.
-
- const uint8_t *read_ptr = read_ptr_mipmap + (tight_mip_size / depth) * z;
-
+ for (uint32_t z = 0; z < depth; z++) {
+ const uint8_t *read_ptr_mipmap_layer = read_ptr_mipmap + (tight_mip_size / depth) * z;
for (uint32_t y = 0; y < height; y += region_size) {
for (uint32_t x = 0; x < width; x += region_size) {
uint32_t region_w = MIN(region_size, width - x);
@@ -1228,11 +1420,7 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
uint32_t region_logic_w = MIN(region_size, logic_width - x);
uint32_t region_logic_h = MIN(region_size, logic_height - y);
- uint32_t pixel_size = get_image_format_pixel_size(texture->format);
- uint32_t block_w = 0, block_h = 0;
- get_compressed_image_format_block_dimensions(texture->format, block_w, block_h);
-
- uint32_t region_pitch = (region_w * pixel_size * block_w) >> get_compressed_image_format_pixel_rshift(texture->format);
+ uint32_t region_pitch = (region_w * pixel_size * block_w) >> pixel_rshift;
uint32_t pitch_step = driver->api_trait_get(RDD::API_TRAIT_TEXTURE_DATA_ROW_PITCH_STEP);
region_pitch = STEPIFY(region_pitch, pitch_step);
uint32_t to_allocate = region_pitch * region_h;
@@ -1241,13 +1429,13 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
Error err = _staging_buffer_allocate(to_allocate, required_align, alloc_offset, alloc_size, required_action, false);
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
- if (!p_use_setup_queue && !command_buffer_to_texture_copies_vector.is_empty() && required_action == STAGING_REQUIRED_ACTION_FLUSH_AND_STALL_ALL) {
+ if (!command_buffer_to_texture_copies_vector.is_empty() && required_action == STAGING_REQUIRED_ACTION_FLUSH_AND_STALL_ALL) {
if (_texture_make_mutable(texture, p_texture)) {
// The texture must be mutable to be used as a copy destination.
draw_graph.add_synchronization();
}
- // If we're using the draw queue and the staging buffer requires flushing everything, we submit the command early and clear the current vector.
+ // If the staging buffer requires flushing everything, we submit the command early and clear the current vector.
draw_graph.add_texture_update(texture->driver_id, texture->draw_tracker, command_buffer_to_texture_copies_vector);
command_buffer_to_texture_copies_vector.clear();
}
@@ -1266,24 +1454,7 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
ERR_FAIL_COND_V(region_w % block_w, ERR_BUG);
ERR_FAIL_COND_V(region_h % block_h, ERR_BUG);
- if (block_w != 1 || block_h != 1) {
- // Compressed image (blocks).
- // Must copy a block region.
-
- uint32_t block_size = get_compressed_image_format_block_byte_size(texture->format);
- // Re-create current variables in blocky format.
- uint32_t xb = x / block_w;
- uint32_t yb = y / block_h;
- uint32_t wb = width / block_w;
- //uint32_t hb = height / block_h;
- uint32_t region_wb = region_w / block_w;
- uint32_t region_hb = region_h / block_h;
- _copy_region(read_ptr, write_ptr, xb, yb, region_wb, region_hb, wb, region_pitch, block_size);
- } else {
- // Regular image (pixels).
- // Must copy a pixel region.
- _copy_region(read_ptr, write_ptr, x, y, region_w, region_h, width, region_pitch, pixel_size);
- }
+ _copy_region_block_or_regular(read_ptr_mipmap_layer, write_ptr, x, y, width, region_w, region_h, block_w, block_h, region_pitch, pixel_size, block_size);
{ // Unmap.
driver->buffer_unmap(staging_buffer_blocks[staging_buffer_current].driver_id);
@@ -1298,14 +1469,10 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
copy_region.texture_offset = Vector3i(x, y, z);
copy_region.texture_region_size = Vector3i(region_logic_w, region_logic_h, 1);
- if (p_use_setup_queue) {
- driver->command_copy_buffer_to_texture(frames[frame].setup_command_buffer, staging_buffer_blocks[staging_buffer_current].driver_id, texture->driver_id, RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL, copy_region);
- } else {
- RDG::RecordedBufferToTextureCopy buffer_to_texture_copy;
- buffer_to_texture_copy.from_buffer = staging_buffer_blocks[staging_buffer_current].driver_id;
- buffer_to_texture_copy.region = copy_region;
- command_buffer_to_texture_copies_vector.push_back(buffer_to_texture_copy);
- }
+ RDG::RecordedBufferToTextureCopy buffer_to_texture_copy;
+ buffer_to_texture_copy.from_buffer = staging_buffer_blocks[staging_buffer_current].driver_id;
+ buffer_to_texture_copy.region = copy_region;
+ command_buffer_to_texture_copies_vector.push_back(buffer_to_texture_copy);
staging_buffer_blocks.write[staging_buffer_current].fill_amount = alloc_offset + alloc_size;
}
@@ -1317,27 +1484,13 @@ Error RenderingDevice::_texture_update(RID p_texture, uint32_t p_layer, const Ve
logic_height = MAX(1u, logic_height >> 1);
}
- if (p_use_setup_queue && (texture->draw_tracker == nullptr) && driver->api_trait_get(RDD::API_TRAIT_HONORS_PIPELINE_BARRIERS)) {
- // If the texture does not have a tracker, it means it must be transitioned to the sampling state.
- RDD::TextureBarrier tb;
- tb.texture = texture->driver_id;
- tb.src_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
- tb.prev_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
- tb.next_layout = RDD::TEXTURE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
- tb.subresources.aspect = texture->barrier_aspect_flags;
- tb.subresources.mipmap_count = texture->mipmaps;
- tb.subresources.base_layer = p_layer;
- tb.subresources.layer_count = 1;
- driver->command_pipeline_barrier(frames[frame].setup_command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, {}, {}, tb);
- } else if (!p_use_setup_queue && !command_buffer_to_texture_copies_vector.is_empty()) {
- if (_texture_make_mutable(texture, p_texture)) {
- // The texture must be mutable to be used as a copy destination.
- draw_graph.add_synchronization();
- }
-
- draw_graph.add_texture_update(texture->driver_id, texture->draw_tracker, command_buffer_to_texture_copies_vector);
+ if (_texture_make_mutable(texture, p_texture)) {
+ // The texture must be mutable to be used as a copy destination.
+ draw_graph.add_synchronization();
}
+ draw_graph.add_texture_update(texture->driver_id, texture->draw_tracker, command_buffer_to_texture_copies_vector);
+
return OK;
}
@@ -1587,7 +1740,7 @@ Vector<uint8_t> RenderingDevice::_texture_get_data(Texture *tex, uint32_t p_laye
}
Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_layer) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(Vector<uint8_t>());
Texture *tex = texture_owner.get_or_null(p_texture);
ERR_FAIL_NULL_V(tex, Vector<uint8_t>());
@@ -1599,7 +1752,9 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye
ERR_FAIL_COND_V(p_layer >= tex->layers, Vector<uint8_t>());
- if ((tex->usage_flags & TEXTURE_USAGE_CPU_READ_BIT)) {
+ _check_transfer_worker_texture(tex);
+
+ if (tex->usage_flags & TEXTURE_USAGE_CPU_READ_BIT) {
// Does not need anything fancy, map and read.
return _texture_get_data(tex, p_layer);
} else {
@@ -1701,7 +1856,7 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye
}
bool RenderingDevice::texture_is_shared(RID p_texture) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(false);
Texture *tex = texture_owner.get_or_null(p_texture);
ERR_FAIL_NULL_V(tex, false);
@@ -1709,11 +1864,13 @@ bool RenderingDevice::texture_is_shared(RID p_texture) {
}
bool RenderingDevice::texture_is_valid(RID p_texture) {
+ ERR_RENDER_THREAD_GUARD_V(false);
+
return texture_owner.owns(p_texture);
}
RD::TextureFormat RenderingDevice::texture_get_format(RID p_texture) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(TextureFormat());
Texture *tex = texture_owner.get_or_null(p_texture);
ERR_FAIL_NULL_V(tex, TextureFormat());
@@ -1736,7 +1893,7 @@ RD::TextureFormat RenderingDevice::texture_get_format(RID p_texture) {
}
Size2i RenderingDevice::texture_size(RID p_texture) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(Size2i());
Texture *tex = texture_owner.get_or_null(p_texture);
ERR_FAIL_NULL_V(tex, Size2i());
@@ -1750,7 +1907,7 @@ uint64_t RenderingDevice::texture_get_native_handle(RID p_texture) {
#endif
Error RenderingDevice::texture_copy(RID p_from_texture, RID p_to_texture, const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_size, uint32_t p_src_mipmap, uint32_t p_dst_mipmap, uint32_t p_src_layer, uint32_t p_dst_layer) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
Texture *src_tex = texture_owner.get_or_null(p_from_texture);
ERR_FAIL_NULL_V(src_tex, ERR_INVALID_PARAMETER);
@@ -1789,6 +1946,9 @@ Error RenderingDevice::texture_copy(RID p_from_texture, RID p_to_texture, const
ERR_FAIL_COND_V_MSG(src_tex->read_aspect_flags != dst_tex->read_aspect_flags, ERR_INVALID_PARAMETER,
"Source and destination texture must be of the same type (color or depth).");
+ _check_transfer_worker_texture(src_tex);
+ _check_transfer_worker_texture(dst_tex);
+
RDD::TextureCopyRegion copy_region;
copy_region.src_subresources.aspect = src_tex->read_aspect_flags;
copy_region.src_subresources.mipmap = p_src_mipmap;
@@ -1820,7 +1980,7 @@ Error RenderingDevice::texture_copy(RID p_from_texture, RID p_to_texture, const
}
Error RenderingDevice::texture_resolve_multisample(RID p_from_texture, RID p_to_texture) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
Texture *src_tex = texture_owner.get_or_null(p_from_texture);
ERR_FAIL_NULL_V(src_tex, ERR_INVALID_PARAMETER);
@@ -1853,6 +2013,9 @@ Error RenderingDevice::texture_resolve_multisample(RID p_from_texture, RID p_to_
// Indicate the texture will get modified for the shared texture fallback.
_texture_update_shared_fallback(p_to_texture, dst_tex, true);
+ _check_transfer_worker_texture(src_tex);
+ _check_transfer_worker_texture(dst_tex);
+
// The textures must be mutable to be used in the resolve operation.
bool src_made_mutable = _texture_make_mutable(src_tex, p_from_texture);
bool dst_made_mutable = _texture_make_mutable(dst_tex, p_to_texture);
@@ -1866,7 +2029,7 @@ Error RenderingDevice::texture_resolve_multisample(RID p_from_texture, RID p_to_
}
Error RenderingDevice::texture_clear(RID p_texture, const Color &p_color, uint32_t p_base_mipmap, uint32_t p_mipmaps, uint32_t p_base_layer, uint32_t p_layers) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
Texture *src_tex = texture_owner.get_or_null(p_texture);
ERR_FAIL_NULL_V(src_tex, ERR_INVALID_PARAMETER);
@@ -1883,6 +2046,8 @@ Error RenderingDevice::texture_clear(RID p_texture, const Color &p_color, uint32
ERR_FAIL_COND_V(p_base_mipmap + p_mipmaps > src_tex->mipmaps, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_base_layer + p_layers > src_tex->layers, ERR_INVALID_PARAMETER);
+ _check_transfer_worker_texture(src_tex);
+
RDD::TextureSubresourceRange range;
range.aspect = src_tex->read_aspect_flags;
range.base_mipmap = src_tex->base_mipmap + p_base_mipmap;
@@ -1906,8 +2071,6 @@ Error RenderingDevice::texture_clear(RID p_texture, const Color &p_color, uint32
bool RenderingDevice::texture_is_format_supported_for_usage(DataFormat p_format, BitField<RenderingDevice::TextureUsageBits> p_usage) const {
ERR_FAIL_INDEX_V(p_format, DATA_FORMAT_MAX, false);
- _THREAD_SAFE_METHOD_
-
bool cpu_readable = (p_usage & RDD::TEXTURE_USAGE_CPU_READ_BIT);
BitField<TextureUsageBits> supported = driver->texture_get_usages_supported_by_format(p_format, cpu_readable);
bool any_unsupported = (((int64_t)supported) | ((int64_t)p_usage)) != ((int64_t)supported);
@@ -2081,6 +2244,8 @@ RDD::RenderPassID RenderingDevice::_render_pass_create(const Vector<AttachmentFo
}
for (int j = 0; j < pass->resolve_attachments.size(); j++) {
int32_t attachment = pass->resolve_attachments[j];
+ attachments[attachment].load_op = RDD::ATTACHMENT_LOAD_OP_DONT_CARE;
+
RDD::AttachmentReference reference;
if (attachment == ATTACHMENT_UNUSED) {
reference.attachment = RDD::AttachmentReference::UNUSED;
@@ -2212,10 +2377,20 @@ RenderingDevice::FramebufferFormatID RenderingDevice::framebuffer_format_create_
fb_format.pass_samples = samples;
fb_format.view_count = p_view_count;
framebuffer_formats[id] = fb_format;
+
+#if PRINT_FRAMEBUFFER_FORMAT
+ print_line("FRAMEBUFFER FORMAT:", id, "ATTACHMENTS:", p_attachments.size(), "PASSES:", p_passes.size());
+ for (RD::AttachmentFormat attachment : p_attachments) {
+ print_line("FORMAT:", attachment.format, "SAMPLES:", attachment.samples, "USAGE FLAGS:", attachment.usage_flags);
+ }
+#endif
+
return id;
}
RenderingDevice::FramebufferFormatID RenderingDevice::framebuffer_format_create_empty(TextureSamples p_samples) {
+ _THREAD_SAFE_METHOD_
+
FramebufferFormatKey key;
key.passes.push_back(FramebufferPass());
@@ -2240,10 +2415,17 @@ RenderingDevice::FramebufferFormatID RenderingDevice::framebuffer_format_create_
fb_format.render_pass = render_pass;
fb_format.pass_samples.push_back(p_samples);
framebuffer_formats[id] = fb_format;
+
+#if PRINT_FRAMEBUFFER_FORMAT
+ print_line("FRAMEBUFFER FORMAT:", id, "ATTACHMENTS: EMPTY");
+#endif
+
return id;
}
RenderingDevice::TextureSamples RenderingDevice::framebuffer_format_get_texture_samples(FramebufferFormatID p_format, uint32_t p_pass) {
+ _THREAD_SAFE_METHOD_
+
HashMap<FramebufferFormatID, FramebufferFormat>::Iterator E = framebuffer_formats.find(p_format);
ERR_FAIL_COND_V(!E, TEXTURE_SAMPLES_1);
ERR_FAIL_COND_V(p_pass >= uint32_t(E->value.pass_samples.size()), TEXTURE_SAMPLES_1);
@@ -2253,6 +2435,7 @@ RenderingDevice::TextureSamples RenderingDevice::framebuffer_format_get_texture_
RID RenderingDevice::framebuffer_create_empty(const Size2i &p_size, TextureSamples p_samples, FramebufferFormatID p_format_check) {
_THREAD_SAFE_METHOD_
+
Framebuffer framebuffer;
framebuffer.format_id = framebuffer_format_create_empty(p_samples);
ERR_FAIL_COND_V(p_format_check != INVALID_FORMAT_ID && framebuffer.format_id != p_format_check, RID());
@@ -2276,6 +2459,10 @@ RID RenderingDevice::framebuffer_create(const Vector<RID> &p_texture_attachments
ERR_FAIL_COND_V_MSG(texture && texture->layers != p_view_count, RID(), "Layers of our texture doesn't match view count for this framebuffer");
+ if (texture != nullptr) {
+ _check_transfer_worker_texture(texture);
+ }
+
if (texture && texture->usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
pass.depth_attachment = i;
} else if (texture && texture->usage_flags & TEXTURE_USAGE_VRS_ATTACHMENT_BIT) {
@@ -2310,6 +2497,8 @@ RID RenderingDevice::framebuffer_create_multipass(const Vector<RID> &p_texture_a
} else {
ERR_FAIL_COND_V_MSG(texture->layers != p_view_count, RID(), "Layers of our texture doesn't match view count for this framebuffer");
+ _check_transfer_worker_texture(texture);
+
if (!size_set) {
size.width = texture->width;
size.height = texture->height;
@@ -2409,10 +2598,10 @@ RID RenderingDevice::sampler_create(const SamplerState &p_state) {
}
bool RenderingDevice::sampler_is_format_supported_for_filter(DataFormat p_format, SamplerFilter p_sampler_filter) const {
- ERR_FAIL_INDEX_V(p_format, DATA_FORMAT_MAX, false);
-
_THREAD_SAFE_METHOD_
+ ERR_FAIL_INDEX_V(p_format, DATA_FORMAT_MAX, false);
+
return driver->sampler_is_format_supported_for_filter(p_format, p_sampler_filter);
}
@@ -2421,8 +2610,6 @@ bool RenderingDevice::sampler_is_format_supported_for_filter(DataFormat p_format
/***********************/
RID RenderingDevice::vertex_buffer_create(uint32_t p_size_bytes, const Vector<uint8_t> &p_data, bool p_use_as_storage) {
- _THREAD_SAFE_METHOD_
-
ERR_FAIL_COND_V(p_data.size() && (uint32_t)p_data.size() != p_size_bytes, RID());
Buffer buffer;
@@ -2441,10 +2628,12 @@ RID RenderingDevice::vertex_buffer_create(uint32_t p_size_bytes, const Vector<ui
}
if (p_data.size()) {
- _buffer_update(&buffer, RID(), 0, p_data.ptr(), p_data.size());
+ _buffer_initialize(&buffer, p_data.ptr(), p_data.size());
}
+ _THREAD_SAFE_LOCK_
buffer_memory += buffer.size;
+ _THREAD_SAFE_UNLOCK_
RID id = vertex_buffer_owner.make_rid(buffer);
#ifdef DEV_ENABLED
@@ -2544,6 +2733,11 @@ RID RenderingDevice::vertex_array_create(uint32_t p_vertex_count, VertexFormatID
} else {
vertex_array.untracked_buffers.insert(p_src_buffers[i]);
}
+
+ if (buffer->transfer_worker_index >= 0) {
+ vertex_array.transfer_worker_indices.push_back(buffer->transfer_worker_index);
+ vertex_array.transfer_worker_operations.push_back(buffer->transfer_worker_operation);
+ }
}
RID id = vertex_array_owner.make_rid(vertex_array);
@@ -2555,8 +2749,6 @@ RID RenderingDevice::vertex_array_create(uint32_t p_vertex_count, VertexFormatID
}
RID RenderingDevice::index_buffer_create(uint32_t p_index_count, IndexBufferFormat p_format, const Vector<uint8_t> &p_data, bool p_use_restart_indices) {
- _THREAD_SAFE_METHOD_
-
ERR_FAIL_COND_V(p_index_count == 0, RID());
IndexBuffer index_buffer;
@@ -2605,10 +2797,12 @@ RID RenderingDevice::index_buffer_create(uint32_t p_index_count, IndexBufferForm
}
if (p_data.size()) {
- _buffer_update(&index_buffer, RID(), 0, p_data.ptr(), p_data.size());
+ _buffer_initialize(&index_buffer, p_data.ptr(), p_data.size());
}
+ _THREAD_SAFE_LOCK_
buffer_memory += index_buffer.size;
+ _THREAD_SAFE_UNLOCK_
RID id = index_buffer_owner.make_rid(index_buffer);
#ifdef DEV_ENABLED
@@ -2635,6 +2829,8 @@ RID RenderingDevice::index_array_create(RID p_index_buffer, uint32_t p_index_off
index_array.indices = p_index_count;
index_array.format = index_buffer->format;
index_array.supports_restart_indices = index_buffer->supports_restart_indices;
+ index_array.transfer_worker_index = index_buffer->transfer_worker_index;
+ index_array.transfer_worker_operation = index_buffer->transfer_worker_operation;
RID id = index_array_owner.make_rid(index_array);
_add_dependency(id, p_index_buffer);
@@ -2753,6 +2949,8 @@ RID RenderingDevice::shader_create_from_bytecode(const Vector<uint8_t> &p_shader
}
RID RenderingDevice::shader_create_placeholder() {
+ _THREAD_SAFE_METHOD_
+
Shader shader;
return shader_owner.make_rid(shader);
}
@@ -2770,8 +2968,6 @@ uint64_t RenderingDevice::shader_get_vertex_input_attribute_mask(RID p_shader) {
/******************/
RID RenderingDevice::uniform_buffer_create(uint32_t p_size_bytes, const Vector<uint8_t> &p_data) {
- _THREAD_SAFE_METHOD_
-
ERR_FAIL_COND_V(p_data.size() && (uint32_t)p_data.size() != p_size_bytes, RID());
Buffer buffer;
@@ -2787,10 +2983,12 @@ RID RenderingDevice::uniform_buffer_create(uint32_t p_size_bytes, const Vector<u
}
if (p_data.size()) {
- _buffer_update(&buffer, RID(), 0, p_data.ptr(), p_data.size());
+ _buffer_initialize(&buffer, p_data.ptr(), p_data.size());
}
+ _THREAD_SAFE_LOCK_
buffer_memory += buffer.size;
+ _THREAD_SAFE_UNLOCK_
RID id = uniform_buffer_owner.make_rid(buffer);
#ifdef DEV_ENABLED
@@ -2921,6 +3119,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
driver_uniform.ids.push_back(*sampler_driver_id);
driver_uniform.ids.push_back(driver_id);
+ _check_transfer_worker_texture(texture);
}
} break;
case UNIFORM_TYPE_TEXTURE: {
@@ -2965,6 +3164,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
DEV_ASSERT(!texture->owner.is_valid() || texture_owner.get_or_null(texture->owner));
driver_uniform.ids.push_back(driver_id);
+ _check_transfer_worker_texture(texture);
}
} break;
case UNIFORM_TYPE_IMAGE: {
@@ -3008,6 +3208,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
DEV_ASSERT(!texture->owner.is_valid() || texture_owner.get_or_null(texture->owner));
driver_uniform.ids.push_back(texture->driver_id);
+ _check_transfer_worker_texture(texture);
}
} break;
case UNIFORM_TYPE_TEXTURE_BUFFER: {
@@ -3042,6 +3243,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
}
driver_uniform.ids.push_back(buffer->driver_id);
+ _check_transfer_worker_buffer(buffer);
}
} break;
case UNIFORM_TYPE_SAMPLER_WITH_TEXTURE_BUFFER: {
@@ -3070,6 +3272,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
driver_uniform.ids.push_back(*sampler_driver_id);
driver_uniform.ids.push_back(buffer->driver_id);
+ _check_transfer_worker_buffer(buffer);
}
} break;
case UNIFORM_TYPE_IMAGE_BUFFER: {
@@ -3094,6 +3297,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
}
driver_uniform.ids.push_back(buffer->driver_id);
+ _check_transfer_worker_buffer(buffer);
} break;
case UNIFORM_TYPE_STORAGE_BUFFER: {
ERR_FAIL_COND_V_MSG(uniform.get_id_count() != 1, RID(),
@@ -3133,6 +3337,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
}
driver_uniform.ids.push_back(buffer->driver_id);
+ _check_transfer_worker_buffer(buffer);
} break;
case UNIFORM_TYPE_INPUT_ATTACHMENT: {
ERR_FAIL_COND_V_MSG(shader->is_compute, RID(), "InputAttachment (binding: " + itos(uniform.binding) + ") supplied for compute shader (this is not allowed).");
@@ -3158,6 +3363,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
DEV_ASSERT(!texture->owner.is_valid() || texture_owner.get_or_null(texture->owner));
driver_uniform.ids.push_back(texture->driver_id);
+ _check_transfer_worker_texture(texture);
}
} break;
default: {
@@ -3197,10 +3403,14 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
}
bool RenderingDevice::uniform_set_is_valid(RID p_uniform_set) {
+ _THREAD_SAFE_METHOD_
+
return uniform_set_owner.owns(p_uniform_set);
}
void RenderingDevice::uniform_set_set_invalidation_callback(RID p_uniform_set, InvalidationCallback p_callback, void *p_userdata) {
+ _THREAD_SAFE_METHOD_
+
UniformSet *us = uniform_set_owner.get_or_null(p_uniform_set);
ERR_FAIL_NULL(us);
us->invalidated_callback = p_callback;
@@ -3212,21 +3422,22 @@ void RenderingDevice::uniform_set_set_invalidation_callback(RID p_uniform_set, I
/*******************/
RID RenderingDevice::render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, BitField<PipelineDynamicStateFlags> p_dynamic_state_flags, uint32_t p_for_render_pass, const Vector<PipelineSpecializationConstant> &p_specialization_constants) {
- _THREAD_SAFE_METHOD_
-
// Needs a shader.
Shader *shader = shader_owner.get_or_null(p_shader);
ERR_FAIL_NULL_V(shader, RID());
+ ERR_FAIL_COND_V_MSG(shader->is_compute, RID(), "Compute shaders can't be used in render pipelines");
- ERR_FAIL_COND_V_MSG(shader->is_compute, RID(),
- "Compute shaders can't be used in render pipelines");
+ FramebufferFormat fb_format;
+ {
+ _THREAD_SAFE_METHOD_
- if (p_framebuffer_format == INVALID_ID) {
- // If nothing provided, use an empty one (no attachments).
- p_framebuffer_format = framebuffer_format_create(Vector<AttachmentFormat>());
+ if (p_framebuffer_format == INVALID_ID) {
+ // If nothing provided, use an empty one (no attachments).
+ p_framebuffer_format = framebuffer_format_create(Vector<AttachmentFormat>());
+ }
+ ERR_FAIL_COND_V(!framebuffer_formats.has(p_framebuffer_format), RID());
+ fb_format = framebuffer_formats[p_framebuffer_format];
}
- ERR_FAIL_COND_V(!framebuffer_formats.has(p_framebuffer_format), RID());
- const FramebufferFormat &fb_format = framebuffer_formats[p_framebuffer_format];
// Validate shader vs. framebuffer.
{
@@ -3372,30 +3583,41 @@ RID RenderingDevice::render_pipeline_create(RID p_shader, FramebufferFormatID p_
};
pipeline.validation.primitive_minimum = primitive_minimum[p_render_primitive];
#endif
+
// Create ID to associate with this pipeline.
RID id = render_pipeline_owner.make_rid(pipeline);
+ {
+ _THREAD_SAFE_METHOD_
+
#ifdef DEV_ENABLED
- set_resource_name(id, "RID:" + itos(id.get_id()));
+ set_resource_name(id, "RID:" + itos(id.get_id()));
#endif
- // Now add all the dependencies.
- _add_dependency(id, p_shader);
+ // Now add all the dependencies.
+ _add_dependency(id, p_shader);
+ }
+
return id;
}
bool RenderingDevice::render_pipeline_is_valid(RID p_pipeline) {
_THREAD_SAFE_METHOD_
+
return render_pipeline_owner.owns(p_pipeline);
}
RID RenderingDevice::compute_pipeline_create(RID p_shader, const Vector<PipelineSpecializationConstant> &p_specialization_constants) {
- _THREAD_SAFE_METHOD_
+ Shader *shader;
- // Needs a shader.
- Shader *shader = shader_owner.get_or_null(p_shader);
- ERR_FAIL_NULL_V(shader, RID());
+ {
+ _THREAD_SAFE_METHOD_
- ERR_FAIL_COND_V_MSG(!shader->is_compute, RID(),
- "Non-compute shaders can't be used in compute pipelines");
+ // Needs a shader.
+ shader = shader_owner.get_or_null(p_shader);
+ ERR_FAIL_NULL_V(shader, RID());
+
+ ERR_FAIL_COND_V_MSG(!shader->is_compute, RID(),
+ "Non-compute shaders can't be used in compute pipelines");
+ }
for (int i = 0; i < shader->specialization_constants.size(); i++) {
const ShaderSpecializationConstant &sc = shader->specialization_constants[i];
@@ -3427,15 +3649,22 @@ RID RenderingDevice::compute_pipeline_create(RID p_shader, const Vector<Pipeline
// Create ID to associate with this pipeline.
RID id = compute_pipeline_owner.make_rid(pipeline);
+ {
+ _THREAD_SAFE_METHOD_
+
#ifdef DEV_ENABLED
- set_resource_name(id, "RID:" + itos(id.get_id()));
+ set_resource_name(id, "RID:" + itos(id.get_id()));
#endif
- // Now add all the dependencies.
- _add_dependency(id, p_shader);
+ // Now add all the dependencies.
+ _add_dependency(id, p_shader);
+ }
+
return id;
}
bool RenderingDevice::compute_pipeline_is_valid(RID p_pipeline) {
+ _THREAD_SAFE_METHOD_
+
return compute_pipeline_owner.owns(p_pipeline);
}
@@ -3515,6 +3744,7 @@ Error RenderingDevice::screen_prepare_for_drawing(DisplayServer::WindowID p_scre
int RenderingDevice::screen_get_width(DisplayServer::WindowID p_screen) const {
_THREAD_SAFE_METHOD_
+
RenderingContextDriver::SurfaceID surface = context->surface_get_from_window(p_screen);
ERR_FAIL_COND_V_MSG(surface == 0, 0, "A surface was not created for the screen.");
return context->surface_get_width(surface);
@@ -3522,6 +3752,7 @@ int RenderingDevice::screen_get_width(DisplayServer::WindowID p_screen) const {
int RenderingDevice::screen_get_height(DisplayServer::WindowID p_screen) const {
_THREAD_SAFE_METHOD_
+
RenderingContextDriver::SurfaceID surface = context->surface_get_from_window(p_screen);
ERR_FAIL_COND_V_MSG(surface == 0, 0, "A surface was not created for the screen.");
return context->surface_get_height(surface);
@@ -3568,7 +3799,7 @@ Error RenderingDevice::screen_free(DisplayServer::WindowID p_screen) {
/*******************/
RenderingDevice::DrawListID RenderingDevice::draw_list_begin_for_screen(DisplayServer::WindowID p_screen, const Color &p_clear_color) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(INVALID_ID);
ERR_FAIL_COND_V_MSG(draw_list != nullptr, INVALID_ID, "Only one draw list can be active at the same time.");
ERR_FAIL_COND_V_MSG(compute_list != nullptr, INVALID_ID, "Only one draw/compute list can be active at the same time.");
@@ -3594,8 +3825,8 @@ RenderingDevice::DrawListID RenderingDevice::draw_list_begin_for_screen(DisplayS
RDD::RenderPassID render_pass = driver->swap_chain_get_render_pass(sc_it->value);
draw_graph.add_draw_list_begin(render_pass, fb_it->value, viewport, clear_value, true, false, RDD::BreadcrumbMarker::BLIT_PASS);
- _draw_list_set_viewport(viewport);
- _draw_list_set_scissor(viewport);
+ draw_graph.add_draw_list_set_viewport(viewport);
+ draw_graph.add_draw_list_set_scissor(viewport);
return int64_t(ID_TYPE_DRAW_LIST) << ID_BASE_SHIFT;
}
@@ -3707,14 +3938,6 @@ Error RenderingDevice::_draw_list_render_pass_begin(Framebuffer *p_framebuffer,
return OK;
}
-void RenderingDevice::_draw_list_set_viewport(Rect2i p_rect) {
- draw_graph.add_draw_list_set_viewport(p_rect);
-}
-
-void RenderingDevice::_draw_list_set_scissor(Rect2i p_rect) {
- draw_graph.add_draw_list_set_scissor(p_rect);
-}
-
void RenderingDevice::_draw_list_insert_clear_region(DrawList *p_draw_list, Framebuffer *p_framebuffer, Point2i p_viewport_offset, Point2i p_viewport_size, bool p_clear_color, const Vector<Color> &p_clear_colors, bool p_clear_depth, float p_depth, uint32_t p_stencil) {
LocalVector<RDD::AttachmentClear> clear_attachments;
int color_index = 0;
@@ -3752,7 +3975,7 @@ void RenderingDevice::_draw_list_insert_clear_region(DrawList *p_draw_list, Fram
}
RenderingDevice::DrawListID RenderingDevice::draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values, float p_clear_depth, uint32_t p_clear_stencil, const Rect2 &p_region, uint32_t p_breadcrumb) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(INVALID_ID);
ERR_FAIL_COND_V_MSG(draw_list != nullptr, INVALID_ID, "Only one draw list can be active at the same time.");
@@ -3812,8 +4035,9 @@ RenderingDevice::DrawListID RenderingDevice::draw_list_begin(RID p_framebuffer,
#endif
draw_list_current_subpass = 0;
- _draw_list_set_viewport(Rect2i(viewport_offset, viewport_size));
- _draw_list_set_scissor(Rect2i(viewport_offset, viewport_size));
+ Rect2i viewport_rect(viewport_offset, viewport_size);
+ draw_graph.add_draw_list_set_viewport(viewport_rect);
+ draw_graph.add_draw_list_set_scissor(viewport_rect);
return int64_t(ID_TYPE_DRAW_LIST) << ID_BASE_SHIFT;
}
@@ -3839,6 +4063,8 @@ RenderingDevice::DrawList *RenderingDevice::_get_draw_list_ptr(DrawListID p_id)
}
void RenderingDevice::draw_list_set_blend_constants(DrawListID p_list, const Color &p_color) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
@@ -3849,6 +4075,8 @@ void RenderingDevice::draw_list_set_blend_constants(DrawListID p_list, const Col
}
void RenderingDevice::draw_list_bind_render_pipeline(DrawListID p_list, RID p_render_pipeline) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
@@ -3877,23 +4105,35 @@ void RenderingDevice::draw_list_bind_render_pipeline(DrawListID p_list, RID p_re
const uint32_t *pformats = pipeline->set_formats.ptr(); // Pipeline set formats.
uint32_t first_invalid_set = UINT32_MAX; // All valid by default.
- switch (driver->api_trait_get(RDD::API_TRAIT_SHADER_CHANGE_INVALIDATION)) {
- case RDD::SHADER_CHANGE_INVALIDATION_ALL_BOUND_UNIFORM_SETS: {
- first_invalid_set = 0;
- } break;
- case RDD::SHADER_CHANGE_INVALIDATION_INCOMPATIBLE_SETS_PLUS_CASCADE: {
- for (uint32_t i = 0; i < pcount; i++) {
- if (dl->state.sets[i].pipeline_expected_format != pformats[i]) {
- first_invalid_set = i;
- break;
- }
- }
- } break;
- case RDD::SHADER_CHANGE_INVALIDATION_ALL_OR_NONE_ACCORDING_TO_LAYOUT_HASH: {
- if (dl->state.pipeline_shader_layout_hash != pipeline->shader_layout_hash) {
+ if (pipeline->push_constant_size != dl->state.pipeline_push_constant_size) {
+ // All sets must be invalidated as the pipeline layout is not compatible if the push constant range is different.
+ dl->state.pipeline_push_constant_size = pipeline->push_constant_size;
+ first_invalid_set = 0;
+ } else {
+ switch (driver->api_trait_get(RDD::API_TRAIT_SHADER_CHANGE_INVALIDATION)) {
+ case RDD::SHADER_CHANGE_INVALIDATION_ALL_BOUND_UNIFORM_SETS: {
first_invalid_set = 0;
- }
- } break;
+ } break;
+ case RDD::SHADER_CHANGE_INVALIDATION_INCOMPATIBLE_SETS_PLUS_CASCADE: {
+ for (uint32_t i = 0; i < pcount; i++) {
+ if (dl->state.sets[i].pipeline_expected_format != pformats[i]) {
+ first_invalid_set = i;
+ break;
+ }
+ }
+ } break;
+ case RDD::SHADER_CHANGE_INVALIDATION_ALL_OR_NONE_ACCORDING_TO_LAYOUT_HASH: {
+ if (dl->state.pipeline_shader_layout_hash != pipeline->shader_layout_hash) {
+ first_invalid_set = 0;
+ }
+ } break;
+ }
+ }
+
+ if (pipeline->push_constant_size) {
+#ifdef DEBUG_ENABLED
+ dl->validation.pipeline_push_constant_supplied = false;
+#endif
}
for (uint32_t i = 0; i < pcount; i++) {
@@ -3908,12 +4148,6 @@ void RenderingDevice::draw_list_bind_render_pipeline(DrawListID p_list, RID p_re
dl->state.set_count = pcount; // Update set count.
- if (pipeline->push_constant_size) {
-#ifdef DEBUG_ENABLED
- dl->validation.pipeline_push_constant_supplied = false;
-#endif
- }
-
dl->state.pipeline_shader = pipeline->shader;
dl->state.pipeline_shader_driver_id = pipeline->shader_driver_id;
dl->state.pipeline_shader_layout_hash = pipeline->shader_layout_hash;
@@ -3932,6 +4166,8 @@ void RenderingDevice::draw_list_bind_render_pipeline(DrawListID p_list, RID p_re
}
void RenderingDevice::draw_list_bind_uniform_set(DrawListID p_list, RID p_uniform_set, uint32_t p_index) {
+ ERR_RENDER_THREAD_GUARD();
+
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_MSG(p_index >= driver->limit_get(LIMIT_MAX_BOUND_UNIFORM_SETS) || p_index >= MAX_UNIFORM_SETS,
"Attempting to bind a descriptor set (" + itos(p_index) + ") greater than what the hardware supports (" + itos(driver->limit_get(LIMIT_MAX_BOUND_UNIFORM_SETS)) + ").");
@@ -3972,19 +4208,23 @@ void RenderingDevice::draw_list_bind_uniform_set(DrawListID p_list, RID p_unifor
}
void RenderingDevice::draw_list_bind_vertex_array(DrawListID p_list, RID p_vertex_array) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_MSG(!dl->validation.active, "Submitted Draw Lists can no longer be modified.");
#endif
- const VertexArray *vertex_array = vertex_array_owner.get_or_null(p_vertex_array);
+ VertexArray *vertex_array = vertex_array_owner.get_or_null(p_vertex_array);
ERR_FAIL_NULL(vertex_array);
if (dl->state.vertex_array == p_vertex_array) {
return; // Already set.
}
+ _check_transfer_worker_vertex_array(vertex_array);
+
dl->state.vertex_array = p_vertex_array;
#ifdef DEBUG_ENABLED
@@ -4001,19 +4241,23 @@ void RenderingDevice::draw_list_bind_vertex_array(DrawListID p_list, RID p_verte
}
void RenderingDevice::draw_list_bind_index_array(DrawListID p_list, RID p_index_array) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_MSG(!dl->validation.active, "Submitted Draw Lists can no longer be modified.");
#endif
- const IndexArray *index_array = index_array_owner.get_or_null(p_index_array);
+ IndexArray *index_array = index_array_owner.get_or_null(p_index_array);
ERR_FAIL_NULL(index_array);
if (dl->state.index_array == p_index_array) {
return; // Already set.
}
+ _check_transfer_worker_index_array(index_array);
+
dl->state.index_array = p_index_array;
#ifdef DEBUG_ENABLED
dl->validation.index_array_max_index = index_array->max_index;
@@ -4029,6 +4273,8 @@ void RenderingDevice::draw_list_bind_index_array(DrawListID p_list, RID p_index_
}
void RenderingDevice::draw_list_set_line_width(DrawListID p_list, float p_width) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
@@ -4039,6 +4285,8 @@ void RenderingDevice::draw_list_set_line_width(DrawListID p_list, float p_width)
}
void RenderingDevice::draw_list_set_push_constant(DrawListID p_list, const void *p_data, uint32_t p_data_size) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
@@ -4059,6 +4307,8 @@ void RenderingDevice::draw_list_set_push_constant(DrawListID p_list, const void
}
void RenderingDevice::draw_list_draw(DrawListID p_list, bool p_use_indices, uint32_t p_instances, uint32_t p_procedural_vertices) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
@@ -4192,6 +4442,8 @@ void RenderingDevice::draw_list_draw(DrawListID p_list, bool p_use_indices, uint
}
void RenderingDevice::draw_list_enable_scissor(DrawListID p_list, const Rect2 &p_rect) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
@@ -4207,25 +4459,30 @@ void RenderingDevice::draw_list_enable_scissor(DrawListID p_list, const Rect2 &p
return;
}
- _draw_list_set_scissor(rect);
+ draw_graph.add_draw_list_set_scissor(rect);
}
void RenderingDevice::draw_list_disable_scissor(DrawListID p_list) {
+ ERR_RENDER_THREAD_GUARD();
+
DrawList *dl = _get_draw_list_ptr(p_list);
ERR_FAIL_NULL(dl);
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_MSG(!dl->validation.active, "Submitted Draw Lists can no longer be modified.");
#endif
- _draw_list_set_scissor(dl->viewport);
+ draw_graph.add_draw_list_set_scissor(dl->viewport);
}
uint32_t RenderingDevice::draw_list_get_current_pass() {
+ ERR_RENDER_THREAD_GUARD_V(0);
+
return draw_list_current_subpass;
}
RenderingDevice::DrawListID RenderingDevice::draw_list_switch_to_next_pass() {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(INVALID_ID);
+
ERR_FAIL_NULL_V(draw_list, INVALID_ID);
ERR_FAIL_COND_V(draw_list_current_subpass >= draw_list_subpass_count - 1, INVALID_FORMAT_ID);
@@ -4248,9 +4505,6 @@ Error RenderingDevice::draw_list_switch_to_next_pass_split(uint32_t p_splits, Dr
#endif
Error RenderingDevice::_draw_list_allocate(const Rect2i &p_viewport, uint32_t p_subpass) {
- // Lock while draw_list is active.
- _THREAD_SAFE_LOCK_
-
draw_list = memnew(DrawList);
draw_list->viewport = p_viewport;
@@ -4264,13 +4518,10 @@ void RenderingDevice::_draw_list_free(Rect2i *r_last_viewport) {
// Just end the list.
memdelete(draw_list);
draw_list = nullptr;
-
- // Draw_list is no longer active.
- _THREAD_SAFE_UNLOCK_
}
void RenderingDevice::draw_list_end() {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD();
ERR_FAIL_NULL_MSG(draw_list, "Immediate draw list is already inactive.");
@@ -4297,13 +4548,10 @@ void RenderingDevice::draw_list_end() {
/***********************/
RenderingDevice::ComputeListID RenderingDevice::compute_list_begin() {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(INVALID_ID);
ERR_FAIL_COND_V_MSG(compute_list != nullptr, INVALID_ID, "Only one draw/compute list can be active at the same time.");
- // Lock while compute_list is active.
- _THREAD_SAFE_LOCK_
-
compute_list = memnew(ComputeList);
draw_graph.add_compute_list_begin();
@@ -4312,7 +4560,7 @@ RenderingDevice::ComputeListID RenderingDevice::compute_list_begin() {
}
void RenderingDevice::compute_list_bind_compute_pipeline(ComputeListID p_list, RID p_compute_pipeline) {
- // Must be called within a compute list, the class mutex is locked during that time
+ ERR_RENDER_THREAD_GUARD();
ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST);
ERR_FAIL_NULL(compute_list);
@@ -4391,7 +4639,7 @@ void RenderingDevice::compute_list_bind_compute_pipeline(ComputeListID p_list, R
}
void RenderingDevice::compute_list_bind_uniform_set(ComputeListID p_list, RID p_uniform_set, uint32_t p_index) {
- // Must be called within a compute list, the class mutex is locked during that time
+ ERR_RENDER_THREAD_GUARD();
ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST);
ERR_FAIL_NULL(compute_list);
@@ -4436,6 +4684,8 @@ void RenderingDevice::compute_list_bind_uniform_set(ComputeListID p_list, RID p_
}
void RenderingDevice::compute_list_set_push_constant(ComputeListID p_list, const void *p_data, uint32_t p_data_size) {
+ ERR_RENDER_THREAD_GUARD();
+
ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST);
ERR_FAIL_NULL(compute_list);
ERR_FAIL_COND_MSG(p_data_size > MAX_PUSH_CONSTANT_SIZE, "Push constants can't be bigger than 128 bytes to maintain compatibility.");
@@ -4463,7 +4713,7 @@ void RenderingDevice::compute_list_set_push_constant(ComputeListID p_list, const
}
void RenderingDevice::compute_list_dispatch(ComputeListID p_list, uint32_t p_x_groups, uint32_t p_y_groups, uint32_t p_z_groups) {
- // Must be called within a compute list, the class mutex is locked during that time
+ ERR_RENDER_THREAD_GUARD();
ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST);
ERR_FAIL_NULL(compute_list);
@@ -4551,6 +4801,8 @@ void RenderingDevice::compute_list_dispatch(ComputeListID p_list, uint32_t p_x_g
}
void RenderingDevice::compute_list_dispatch_threads(ComputeListID p_list, uint32_t p_x_threads, uint32_t p_y_threads, uint32_t p_z_threads) {
+ ERR_RENDER_THREAD_GUARD();
+
ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST);
ERR_FAIL_NULL(compute_list);
@@ -4578,6 +4830,8 @@ void RenderingDevice::compute_list_dispatch_threads(ComputeListID p_list, uint32
}
void RenderingDevice::compute_list_dispatch_indirect(ComputeListID p_list, RID p_buffer, uint32_t p_offset) {
+ ERR_RENDER_THREAD_GUARD();
+
ERR_FAIL_COND(p_list != ID_TYPE_COMPUTE_LIST);
ERR_FAIL_NULL(compute_list);
@@ -4661,10 +4915,12 @@ void RenderingDevice::compute_list_dispatch_indirect(ComputeListID p_list, RID p
if (buffer->draw_tracker != nullptr) {
draw_graph.add_compute_list_usage(buffer->draw_tracker, RDG::RESOURCE_USAGE_INDIRECT_BUFFER_READ);
}
+
+ _check_transfer_worker_buffer(buffer);
}
void RenderingDevice::compute_list_add_barrier(ComputeListID p_list) {
- // Must be called within a compute list, the class mutex is locked during that time
+ ERR_RENDER_THREAD_GUARD();
compute_list_barrier_state = compute_list->state;
compute_list_end();
@@ -4686,15 +4942,14 @@ void RenderingDevice::compute_list_add_barrier(ComputeListID p_list) {
}
void RenderingDevice::compute_list_end() {
+ ERR_RENDER_THREAD_GUARD();
+
ERR_FAIL_NULL(compute_list);
draw_graph.add_compute_list_end();
memdelete(compute_list);
compute_list = nullptr;
-
- // Compute_list is no longer active.
- _THREAD_SAFE_UNLOCK_
}
#ifndef DISABLE_DEPRECATED
@@ -4707,6 +4962,282 @@ void RenderingDevice::full_barrier() {
}
#endif
+/*************************/
+/**** TRANSFER WORKER ****/
+/*************************/
+
+static uint32_t _get_alignment_offset(uint32_t p_offset, uint32_t p_required_align) {
+ uint32_t alignment_offset = (p_required_align > 0) ? (p_offset % p_required_align) : 0;
+ if (alignment_offset != 0) {
+ // If a particular alignment is required, add the offset as part of the required size.
+ alignment_offset = p_required_align - alignment_offset;
+ }
+
+ return alignment_offset;
+}
+
+RenderingDevice::TransferWorker *RenderingDevice::_acquire_transfer_worker(uint32_t p_transfer_size, uint32_t p_required_align, uint32_t &r_staging_offset) {
+ // Find the first worker that is not currently executing anything and has enough size for the transfer.
+ // If no workers are available, we make a new one. If we're not allowed to make new ones, we wait until one of them is available.
+ TransferWorker *transfer_worker = nullptr;
+ uint32_t available_list_index = 0;
+ bool transfer_worker_busy = true;
+ bool transfer_worker_full = true;
+ {
+ MutexLock pool_lock(transfer_worker_pool_mutex);
+
+ // If no workers are available and we've reached the max pool capacity, wait until one of them becomes available.
+ bool transfer_worker_pool_full = transfer_worker_pool.size() >= transfer_worker_pool_max_size;
+ while (transfer_worker_pool_available_list.is_empty() && transfer_worker_pool_full) {
+ transfer_worker_pool_condition.wait(pool_lock);
+ }
+
+ // Look at all available workers first.
+ for (uint32_t i = 0; i < transfer_worker_pool_available_list.size(); i++) {
+ uint32_t worker_index = transfer_worker_pool_available_list[i];
+ TransferWorker *candidate_worker = transfer_worker_pool[worker_index];
+ candidate_worker->thread_mutex.lock();
+
+ // Figure out if the worker can fit the transfer.
+ uint32_t alignment_offset = _get_alignment_offset(candidate_worker->staging_buffer_size_in_use, p_required_align);
+ uint32_t required_size = candidate_worker->staging_buffer_size_in_use + p_transfer_size + alignment_offset;
+ bool candidate_worker_busy = candidate_worker->submitted;
+ bool candidate_worker_full = required_size > candidate_worker->staging_buffer_size_allocated;
+ bool pick_candidate = false;
+ if (!candidate_worker_busy && !candidate_worker_full) {
+ // A worker that can fit the transfer and is not waiting for a previous execution is the best possible candidate.
+ pick_candidate = true;
+ } else if (!candidate_worker_busy) {
+ // The worker can't fit the transfer but it's not currently doing anything.
+ // We pick it as a possible candidate if the current one is busy.
+ pick_candidate = transfer_worker_busy;
+ } else if (!candidate_worker_full) {
+ // The worker can fit the transfer but it's currently executing previous work.
+ // We pick it as a possible candidate if the current one is both busy and full.
+ pick_candidate = transfer_worker_busy && transfer_worker_full;
+ } else if (transfer_worker == nullptr) {
+ // The worker can't fit the transfer and it's currently executing work, so it's the worst candidate.
+ // We only pick if no candidate has been picked yet.
+ pick_candidate = true;
+ }
+
+ if (pick_candidate) {
+ if (transfer_worker != nullptr) {
+ // Release the lock for the worker that was picked previously.
+ transfer_worker->thread_mutex.unlock();
+ }
+
+ // Keep the lock active for this worker.
+ transfer_worker = candidate_worker;
+ transfer_worker_busy = candidate_worker_busy;
+ transfer_worker_full = candidate_worker_full;
+ available_list_index = i;
+
+ if (!transfer_worker_busy && !transfer_worker_full) {
+ // Best possible candidate, stop searching early.
+ break;
+ }
+ } else {
+ // Release the lock for the candidate.
+ candidate_worker->thread_mutex.unlock();
+ }
+ }
+
+ if (transfer_worker != nullptr) {
+ // A worker was picked, remove it from the available list.
+ transfer_worker_pool_available_list.remove_at(available_list_index);
+ } else {
+ DEV_ASSERT(!transfer_worker_pool_full && "A transfer worker should never be created when the pool is full.");
+
+ // No existing worker was picked, we create a new one.
+ transfer_worker = memnew(TransferWorker);
+ transfer_worker->command_fence = driver->fence_create();
+ transfer_worker->command_semaphore = driver->semaphore_create();
+ transfer_worker->command_pool = driver->command_pool_create(transfer_queue_family, RDD::COMMAND_BUFFER_TYPE_PRIMARY);
+ transfer_worker->command_buffer = driver->command_buffer_create(transfer_worker->command_pool);
+ transfer_worker->index = transfer_worker_pool.size();
+ transfer_worker_pool.push_back(transfer_worker);
+ transfer_worker_operation_used_by_draw.push_back(0);
+ transfer_worker->thread_mutex.lock();
+ }
+ }
+
+ if (transfer_worker->submitted) {
+ // Wait for the worker if the command buffer was submitted but it hasn't finished processing yet.
+ _wait_for_transfer_worker(transfer_worker);
+ }
+
+ uint32_t alignment_offset = _get_alignment_offset(transfer_worker->staging_buffer_size_in_use, p_required_align);
+ transfer_worker->max_transfer_size = MAX(transfer_worker->max_transfer_size, p_transfer_size);
+
+ uint32_t required_size = transfer_worker->staging_buffer_size_in_use + p_transfer_size + alignment_offset;
+ if (required_size > transfer_worker->staging_buffer_size_allocated) {
+ // If there's not enough bytes to use on the staging buffer, we submit everything pending from the worker and wait for the work to be finished.
+ if (transfer_worker->recording) {
+ _end_transfer_worker(transfer_worker);
+ _submit_transfer_worker(transfer_worker, false);
+ }
+
+ if (transfer_worker->submitted) {
+ _wait_for_transfer_worker(transfer_worker);
+ }
+
+ alignment_offset = 0;
+
+ // If the staging buffer can't fit the transfer, we recreate the buffer.
+ const uint32_t expected_buffer_size_minimum = 16 * 1024;
+ uint32_t expected_buffer_size = MAX(transfer_worker->max_transfer_size, expected_buffer_size_minimum);
+ if (expected_buffer_size > transfer_worker->staging_buffer_size_allocated) {
+ if (transfer_worker->staging_buffer.id != 0) {
+ driver->buffer_free(transfer_worker->staging_buffer);
+ }
+
+ uint32_t new_staging_buffer_size = next_power_of_2(expected_buffer_size);
+ transfer_worker->staging_buffer_size_allocated = new_staging_buffer_size;
+ transfer_worker->staging_buffer = driver->buffer_create(new_staging_buffer_size, RDD::BUFFER_USAGE_TRANSFER_FROM_BIT, RDD::MEMORY_ALLOCATION_TYPE_CPU);
+ }
+ }
+
+ // Add the alignment before storing the offset that will be returned.
+ transfer_worker->staging_buffer_size_in_use += alignment_offset;
+
+ // Store the offset to return and increment the current size.
+ r_staging_offset = transfer_worker->staging_buffer_size_in_use;
+ transfer_worker->staging_buffer_size_in_use += p_transfer_size;
+
+ if (!transfer_worker->recording) {
+ // Begin the command buffer if the worker wasn't recording yet.
+ driver->command_buffer_begin(transfer_worker->command_buffer);
+ transfer_worker->recording = true;
+ }
+
+ return transfer_worker;
+}
+
+void RenderingDevice::_release_transfer_worker(TransferWorker *p_transfer_worker) {
+ p_transfer_worker->thread_mutex.unlock();
+
+ transfer_worker_pool_mutex.lock();
+ transfer_worker_pool_available_list.push_back(p_transfer_worker->index);
+ transfer_worker_pool_mutex.unlock();
+ transfer_worker_pool_condition.notify_one();
+}
+
+void RenderingDevice::_end_transfer_worker(TransferWorker *p_transfer_worker) {
+ driver->command_buffer_end(p_transfer_worker->command_buffer);
+ p_transfer_worker->recording = false;
+}
+
+void RenderingDevice::_submit_transfer_worker(TransferWorker *p_transfer_worker, bool p_signal_semaphore) {
+ const VectorView<RDD::SemaphoreID> execute_semaphore = p_signal_semaphore ? p_transfer_worker->command_semaphore : VectorView<RDD::SemaphoreID>();
+ driver->command_queue_execute_and_present(transfer_queue, {}, p_transfer_worker->command_buffer, execute_semaphore, p_transfer_worker->command_fence, {});
+ if (p_signal_semaphore) {
+ // Indicate the frame should wait on these semaphores before executing the main command buffer.
+ frames[frame].semaphores_to_wait_on.push_back(p_transfer_worker->command_semaphore);
+ }
+
+ p_transfer_worker->submitted = true;
+
+ {
+ MutexLock lock(p_transfer_worker->operations_mutex);
+ p_transfer_worker->operations_submitted = p_transfer_worker->operations_counter;
+ }
+}
+
+void RenderingDevice::_wait_for_transfer_worker(TransferWorker *p_transfer_worker) {
+ driver->fence_wait(p_transfer_worker->command_fence);
+ p_transfer_worker->staging_buffer_size_in_use = 0;
+ p_transfer_worker->submitted = false;
+
+ {
+ MutexLock lock(p_transfer_worker->operations_mutex);
+ p_transfer_worker->operations_processed = p_transfer_worker->operations_submitted;
+ }
+}
+
+void RenderingDevice::_check_transfer_worker_operation(uint32_t p_transfer_worker_index, uint64_t p_transfer_worker_operation) {
+ TransferWorker *transfer_worker = transfer_worker_pool[p_transfer_worker_index];
+ MutexLock lock(transfer_worker->operations_mutex);
+ uint64_t &dst_operation = transfer_worker_operation_used_by_draw[transfer_worker->index];
+ dst_operation = MAX(dst_operation, p_transfer_worker_operation);
+}
+
+void RenderingDevice::_check_transfer_worker_buffer(Buffer *p_buffer) {
+ if (p_buffer->transfer_worker_index >= 0) {
+ _check_transfer_worker_operation(p_buffer->transfer_worker_index, p_buffer->transfer_worker_operation);
+ p_buffer->transfer_worker_index = -1;
+ }
+}
+
+void RenderingDevice::_check_transfer_worker_texture(Texture *p_texture) {
+ if (p_texture->transfer_worker_index >= 0) {
+ _check_transfer_worker_operation(p_texture->transfer_worker_index, p_texture->transfer_worker_operation);
+ p_texture->transfer_worker_index = -1;
+ }
+}
+
+void RenderingDevice::_check_transfer_worker_vertex_array(VertexArray *p_vertex_array) {
+ if (!p_vertex_array->transfer_worker_indices.is_empty()) {
+ for (int i = 0; i < p_vertex_array->transfer_worker_indices.size(); i++) {
+ _check_transfer_worker_operation(p_vertex_array->transfer_worker_indices[i], p_vertex_array->transfer_worker_operations[i]);
+ }
+
+ p_vertex_array->transfer_worker_indices.clear();
+ p_vertex_array->transfer_worker_operations.clear();
+ }
+}
+
+void RenderingDevice::_check_transfer_worker_index_array(IndexArray *p_index_array) {
+ if (p_index_array->transfer_worker_index >= 0) {
+ _check_transfer_worker_operation(p_index_array->transfer_worker_index, p_index_array->transfer_worker_operation);
+ p_index_array->transfer_worker_index = -1;
+ }
+}
+
+void RenderingDevice::_submit_transfer_workers(bool p_operations_used_by_draw) {
+ MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
+ for (TransferWorker *worker : transfer_worker_pool) {
+ if (p_operations_used_by_draw) {
+ MutexLock lock(worker->operations_mutex);
+ if (worker->operations_processed >= transfer_worker_operation_used_by_draw[worker->index]) {
+ // The operation used by the draw has already been processed, we don't need to wait on the worker.
+ continue;
+ }
+ }
+
+ {
+ MutexLock lock(worker->thread_mutex);
+ if (worker->recording) {
+ _end_transfer_worker(worker);
+ _submit_transfer_worker(worker, true);
+ }
+ }
+ }
+}
+
+void RenderingDevice::_wait_for_transfer_workers() {
+ MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
+ for (TransferWorker *worker : transfer_worker_pool) {
+ MutexLock lock(worker->thread_mutex);
+ if (worker->submitted) {
+ _wait_for_transfer_worker(worker);
+ }
+ }
+}
+
+void RenderingDevice::_free_transfer_workers() {
+ MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
+ for (TransferWorker *worker : transfer_worker_pool) {
+ driver->semaphore_free(worker->command_semaphore);
+ driver->fence_free(worker->command_fence);
+ driver->buffer_free(worker->staging_buffer);
+ driver->command_pool_free(worker->command_pool);
+ memdelete(worker);
+ }
+
+ transfer_worker_pool.clear();
+}
+
/***********************/
/**** COMMAND GRAPH ****/
/***********************/
@@ -4851,7 +5382,7 @@ bool RenderingDevice::_dependency_make_mutable(RID p_id, RID p_resource_id, RDG:
}
}
-bool RenderingDevice::_dependencies_make_mutable(RID p_id, RDG::ResourceTracker *p_resource_tracker) {
+bool RenderingDevice::_dependencies_make_mutable_recursive(RID p_id, RDG::ResourceTracker *p_resource_tracker) {
bool made_mutable = false;
HashMap<RID, HashSet<RID>>::Iterator E = dependency_map.find(p_id);
if (E) {
@@ -4863,12 +5394,17 @@ bool RenderingDevice::_dependencies_make_mutable(RID p_id, RDG::ResourceTracker
return made_mutable;
}
+bool RenderingDevice::_dependencies_make_mutable(RID p_id, RDG::ResourceTracker *p_resource_tracker) {
+ _THREAD_SAFE_METHOD_
+ return _dependencies_make_mutable_recursive(p_id, p_resource_tracker);
+}
+
/**************************/
/**** FRAME MANAGEMENT ****/
/**************************/
void RenderingDevice::free(RID p_id) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD();
_free_dependencies(p_id); // Recursively erase dependencies first, to avoid potential API problems.
_free_internal(p_id);
@@ -4886,6 +5422,8 @@ void RenderingDevice::_free_internal(RID p_id) {
// Push everything so it's disposed of next time this frame index is processed (means, it's safe to do it).
if (texture_owner.owns(p_id)) {
Texture *texture = texture_owner.get_or_null(p_id);
+ _check_transfer_worker_texture(texture);
+
RDG::ResourceTracker *draw_tracker = texture->draw_tracker;
if (draw_tracker != nullptr) {
draw_tracker->reference_count--;
@@ -4919,6 +5457,8 @@ void RenderingDevice::_free_internal(RID p_id) {
sampler_owner.free(p_id);
} else if (vertex_buffer_owner.owns(p_id)) {
Buffer *vertex_buffer = vertex_buffer_owner.get_or_null(p_id);
+ _check_transfer_worker_buffer(vertex_buffer);
+
RDG::resource_tracker_free(vertex_buffer->draw_tracker);
frames[frame].buffers_to_dispose_of.push_back(*vertex_buffer);
vertex_buffer_owner.free(p_id);
@@ -4926,6 +5466,8 @@ void RenderingDevice::_free_internal(RID p_id) {
vertex_array_owner.free(p_id);
} else if (index_buffer_owner.owns(p_id)) {
IndexBuffer *index_buffer = index_buffer_owner.get_or_null(p_id);
+ _check_transfer_worker_buffer(index_buffer);
+
RDG::resource_tracker_free(index_buffer->draw_tracker);
frames[frame].buffers_to_dispose_of.push_back(*index_buffer);
index_buffer_owner.free(p_id);
@@ -4939,16 +5481,22 @@ void RenderingDevice::_free_internal(RID p_id) {
shader_owner.free(p_id);
} else if (uniform_buffer_owner.owns(p_id)) {
Buffer *uniform_buffer = uniform_buffer_owner.get_or_null(p_id);
+ _check_transfer_worker_buffer(uniform_buffer);
+
RDG::resource_tracker_free(uniform_buffer->draw_tracker);
frames[frame].buffers_to_dispose_of.push_back(*uniform_buffer);
uniform_buffer_owner.free(p_id);
} else if (texture_buffer_owner.owns(p_id)) {
Buffer *texture_buffer = texture_buffer_owner.get_or_null(p_id);
+ _check_transfer_worker_buffer(texture_buffer);
+
RDG::resource_tracker_free(texture_buffer->draw_tracker);
frames[frame].buffers_to_dispose_of.push_back(*texture_buffer);
texture_buffer_owner.free(p_id);
} else if (storage_buffer_owner.owns(p_id)) {
Buffer *storage_buffer = storage_buffer_owner.get_or_null(p_id);
+ _check_transfer_worker_buffer(storage_buffer);
+
RDG::resource_tracker_free(storage_buffer->draw_tracker);
frames[frame].buffers_to_dispose_of.push_back(*storage_buffer);
storage_buffer_owner.free(p_id);
@@ -4980,6 +5528,8 @@ void RenderingDevice::_free_internal(RID p_id) {
// The full list of resources that can be named is in the VkObjectType enum.
// We just expose the resources that are owned and can be accessed easily.
void RenderingDevice::set_resource_name(RID p_id, const String &p_name) {
+ _THREAD_SAFE_METHOD_
+
if (texture_owner.owns(p_id)) {
Texture *texture = texture_owner.get_or_null(p_id);
driver->set_object_name(RDD::OBJECT_TYPE_TEXTURE, texture->driver_id, p_name);
@@ -5026,6 +5576,8 @@ void RenderingDevice::set_resource_name(RID p_id, const String &p_name) {
}
void RenderingDevice::draw_command_begin_label(String p_label_name, const Color &p_color) {
+ ERR_RENDER_THREAD_GUARD();
+
if (!context->is_debug_utils_enabled()) {
return;
}
@@ -5040,6 +5592,8 @@ void RenderingDevice::draw_command_insert_label(String p_label_name, const Color
#endif
void RenderingDevice::draw_command_end_label() {
+ ERR_RENDER_THREAD_GUARD();
+
draw_graph.end_label();
}
@@ -5072,7 +5626,7 @@ String RenderingDevice::get_device_pipeline_cache_uuid() const {
}
void RenderingDevice::swap_buffers() {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD();
_end_frame();
_execute_frame(true);
@@ -5083,18 +5637,20 @@ void RenderingDevice::swap_buffers() {
}
void RenderingDevice::submit() {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD();
ERR_FAIL_COND_MSG(is_main_instance, "Only local devices can submit and sync.");
ERR_FAIL_COND_MSG(local_device_processing, "device already submitted, call sync to wait until done.");
+
_end_frame();
_execute_frame(false);
local_device_processing = true;
}
void RenderingDevice::sync() {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD();
ERR_FAIL_COND_MSG(is_main_instance, "Only local devices can submit and sync.");
ERR_FAIL_COND_MSG(!local_device_processing, "sync can only be called after a submit");
+
_begin_frame();
local_device_processing = false;
}
@@ -5207,17 +5763,16 @@ uint64_t RenderingDevice::get_memory_usage(MemoryType p_type) const {
void RenderingDevice::_begin_frame() {
// Before beginning this frame, wait on the fence if it was signaled to make sure its work is finished.
- if (frames[frame].draw_fence_signaled) {
- driver->fence_wait(frames[frame].draw_fence);
- frames[frame].draw_fence_signaled = false;
+ if (frames[frame].fence_signaled) {
+ driver->fence_wait(frames[frame].fence);
+ frames[frame].fence_signaled = false;
}
update_perf_report();
// Begin recording on the frame's command buffers.
driver->begin_segment(frame, frames_drawn++);
- driver->command_buffer_begin(frames[frame].setup_command_buffer);
- driver->command_buffer_begin(frames[frame].draw_command_buffer);
+ driver->command_buffer_begin(frames[frame].command_buffer);
// Reset the graph.
draw_graph.begin();
@@ -5233,7 +5788,7 @@ void RenderingDevice::_begin_frame() {
if (frames[frame].timestamp_count) {
driver->timestamp_query_pool_get_results(frames[frame].timestamp_pool, frames[frame].timestamp_count, frames[frame].timestamp_result_values.ptr());
- driver->command_timestamp_query_pool_reset(frames[frame].setup_command_buffer, frames[frame].timestamp_pool, frames[frame].timestamp_count);
+ driver->command_timestamp_query_pool_reset(frames[frame].command_buffer, frames[frame].timestamp_pool, frames[frame].timestamp_count);
SWAP(frames[frame].timestamp_names, frames[frame].timestamp_result_names);
SWAP(frames[frame].timestamp_cpu_values, frames[frame].timestamp_cpu_result_values);
}
@@ -5252,10 +5807,10 @@ void RenderingDevice::_end_frame() {
ERR_PRINT("Found open compute list at the end of the frame, this should never happen (further compute will likely not work).");
}
- driver->command_buffer_end(frames[frame].setup_command_buffer);
+ _submit_transfer_workers(true);
// The command buffer must be copied into a stack variable as the driver workarounds can change the command buffer in use.
- RDD::CommandBufferID command_buffer = frames[frame].draw_command_buffer;
+ RDD::CommandBufferID command_buffer = frames[frame].command_buffer;
draw_graph.end(RENDER_GRAPH_REORDER, RENDER_GRAPH_FULL_BARRIERS, command_buffer, frames[frame].command_buffer_pool);
driver->command_buffer_end(command_buffer);
driver->end_segment();
@@ -5268,9 +5823,6 @@ void RenderingDevice::_execute_frame(bool p_present) {
thread_local LocalVector<RDD::SwapChainID> swap_chains;
swap_chains.clear();
- // Execute the setup command buffer.
- driver->command_queue_execute_and_present(main_queue, {}, frames[frame].setup_command_buffer, frames[frame].setup_semaphore, {}, {});
-
// Execute command buffers and use semaphores to wait on the execution of the previous one. Normally there's only one command buffer,
// but driver workarounds can force situations where there'll be more.
uint32_t command_buffer_count = 1;
@@ -5280,7 +5832,9 @@ void RenderingDevice::_execute_frame(bool p_present) {
buffer_pool.buffers_used = 0;
}
- RDD::SemaphoreID wait_semaphore = frames[frame].setup_semaphore;
+ thread_local LocalVector<RDD::SemaphoreID> wait_semaphores;
+ wait_semaphores = frames[frame].semaphores_to_wait_on;
+
for (uint32_t i = 0; i < command_buffer_count; i++) {
RDD::CommandBufferID command_buffer;
RDD::SemaphoreID signal_semaphore;
@@ -5289,14 +5843,14 @@ void RenderingDevice::_execute_frame(bool p_present) {
command_buffer = buffer_pool.buffers[i - 1];
signal_semaphore = buffer_pool.semaphores[i - 1];
} else {
- command_buffer = frames[frame].draw_command_buffer;
- signal_semaphore = frames[frame].draw_semaphore;
+ command_buffer = frames[frame].command_buffer;
+ signal_semaphore = frames[frame].semaphore;
}
bool signal_semaphore_valid;
if (i == (command_buffer_count - 1)) {
// This is the last command buffer, it should signal the fence.
- signal_fence = frames[frame].draw_fence;
+ signal_fence = frames[frame].fence;
signal_semaphore_valid = false;
if (frame_can_present && separate_present_queue) {
@@ -5311,19 +5865,21 @@ void RenderingDevice::_execute_frame(bool p_present) {
signal_semaphore_valid = true;
}
- driver->command_queue_execute_and_present(main_queue, wait_semaphore, command_buffer, signal_semaphore_valid ? signal_semaphore : VectorView<RDD::SemaphoreID>(), signal_fence, swap_chains);
+ driver->command_queue_execute_and_present(main_queue, wait_semaphores, command_buffer, signal_semaphore_valid ? signal_semaphore : VectorView<RDD::SemaphoreID>(), signal_fence, swap_chains);
// Make the next command buffer wait on the semaphore signaled by this one.
- wait_semaphore = signal_semaphore;
+ wait_semaphores.resize(1);
+ wait_semaphores[0] = signal_semaphore;
}
// Indicate the fence has been signaled so the next time the frame's contents need to be used, the CPU needs to wait on the work to be completed.
- frames[frame].draw_fence_signaled = true;
+ frames[frame].semaphores_to_wait_on.clear();
+ frames[frame].fence_signaled = true;
if (frame_can_present) {
if (separate_present_queue) {
// Issue the presentation separately if the presentation queue is different from the main queue.
- driver->command_queue_execute_and_present(present_queue, wait_semaphore, {}, {}, {}, frames[frame].swap_chains_to_present);
+ driver->command_queue_execute_and_present(present_queue, wait_semaphores, {}, {}, {}, frames[frame].swap_chains_to_present);
}
frames[frame].swap_chains_to_present.clear();
@@ -5332,9 +5888,9 @@ void RenderingDevice::_execute_frame(bool p_present) {
void RenderingDevice::_stall_for_previous_frames() {
for (uint32_t i = 0; i < frames.size(); i++) {
- if (frames[i].draw_fence_signaled) {
- driver->fence_wait(frames[i].draw_fence);
- frames[i].draw_fence_signaled = false;
+ if (frames[i].fence_signaled) {
+ driver->fence_wait(frames[i].fence);
+ frames[i].fence_signaled = false;
}
}
}
@@ -5347,8 +5903,9 @@ void RenderingDevice::_flush_and_stall_for_all_frames() {
}
Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServer::WindowID p_main_window) {
- Error err;
+ ERR_RENDER_THREAD_GUARD_V(ERR_UNAVAILABLE);
+ Error err;
RenderingContextDriver::SurfaceID main_surface = 0;
is_main_instance = (singleton == this) && (p_main_window != DisplayServer::INVALID_WINDOW_ID);
if (p_main_window != DisplayServer::INVALID_WINDOW_ID) {
@@ -5436,12 +5993,25 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
main_queue = driver->command_queue_create(main_queue_family, true);
ERR_FAIL_COND_V(!main_queue, FAILED);
+ transfer_queue_family = driver->command_queue_family_get(RDD::COMMAND_QUEUE_FAMILY_TRANSFER_BIT);
+ if (transfer_queue_family) {
+ // Create the transfer queue.
+ transfer_queue = driver->command_queue_create(transfer_queue_family);
+ ERR_FAIL_COND_V(!transfer_queue, FAILED);
+ } else {
+ // Use main queue as the transfer queue.
+ transfer_queue = main_queue;
+ transfer_queue_family = main_queue_family;
+ }
+
if (present_queue_family) {
- // Create the presentation queue.
+ // Create the present queue.
present_queue = driver->command_queue_create(present_queue_family);
ERR_FAIL_COND_V(!present_queue, FAILED);
} else {
+ // Use main queue as the present queue.
present_queue = main_queue;
+ present_queue_family = main_queue_family;
}
// Create data for all the frames.
@@ -5451,17 +6021,13 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
// Create command pool, command buffers, semaphores and fences.
frames[i].command_pool = driver->command_pool_create(main_queue_family, RDD::COMMAND_BUFFER_TYPE_PRIMARY);
ERR_FAIL_COND_V(!frames[i].command_pool, FAILED);
- frames[i].setup_command_buffer = driver->command_buffer_create(frames[i].command_pool);
- ERR_FAIL_COND_V(!frames[i].setup_command_buffer, FAILED);
- frames[i].draw_command_buffer = driver->command_buffer_create(frames[i].command_pool);
- ERR_FAIL_COND_V(!frames[i].draw_command_buffer, FAILED);
- frames[i].setup_semaphore = driver->semaphore_create();
- ERR_FAIL_COND_V(!frames[i].setup_semaphore, FAILED);
- frames[i].draw_semaphore = driver->semaphore_create();
- ERR_FAIL_COND_V(!frames[i].draw_semaphore, FAILED);
- frames[i].draw_fence = driver->fence_create();
- ERR_FAIL_COND_V(!frames[i].draw_fence, FAILED);
- frames[i].draw_fence_signaled = false;
+ frames[i].command_buffer = driver->command_buffer_create(frames[i].command_pool);
+ ERR_FAIL_COND_V(!frames[i].command_buffer, FAILED);
+ frames[i].semaphore = driver->semaphore_create();
+ ERR_FAIL_COND_V(!frames[i].semaphore, FAILED);
+ frames[i].fence = driver->fence_create();
+ ERR_FAIL_COND_V(!frames[i].fence, FAILED);
+ frames[i].fence_signaled = false;
// Create query pool.
frames[i].timestamp_pool = driver->timestamp_query_pool_create(max_timestamp_query_elements);
@@ -5482,8 +6048,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
// Initialize recording on the first frame.
driver->begin_segment(frame, frames_drawn++);
- driver->command_buffer_begin(frames[0].setup_command_buffer);
- driver->command_buffer_begin(frames[0].draw_command_buffer);
+ driver->command_buffer_begin(frames[0].command_buffer);
// Create draw graph and start it initialized as well.
draw_graph.initialize(driver, device, frames.size(), main_queue_family, SECONDARY_COMMAND_BUFFERS_PER_FRAME);
@@ -5491,7 +6056,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
for (uint32_t i = 0; i < frames.size(); i++) {
// Reset all queries in a query pool before doing any operations with them..
- driver->command_timestamp_query_pool_reset(frames[0].setup_command_buffer, frames[i].timestamp_pool, max_timestamp_query_elements);
+ driver->command_timestamp_query_pool_reset(frames[0].command_buffer, frames[i].timestamp_pool, max_timestamp_query_elements);
}
// Convert block size from KB.
@@ -5522,6 +6087,9 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
ERR_FAIL_COND_V(err, FAILED);
}
+ // TODO: How should this max size be determined?
+ transfer_worker_pool_max_size = OS::get_singleton()->get_processor_count();
+
draw_list = nullptr;
compute_list = nullptr;
@@ -5560,6 +6128,8 @@ Vector<uint8_t> RenderingDevice::_load_pipeline_cache() {
}
void RenderingDevice::_update_pipeline_cache(bool p_closing) {
+ _THREAD_SAFE_METHOD_
+
{
bool still_saving = pipeline_cache_save_task != WorkerThreadPool::INVALID_TASK_ID && !WorkerThreadPool::get_singleton()->is_task_completed(pipeline_cache_save_task);
if (still_saving) {
@@ -5641,6 +6211,8 @@ void RenderingDevice::_free_rids(T &p_owner, const char *p_type) {
}
void RenderingDevice::capture_timestamp(const String &p_name) {
+ ERR_RENDER_THREAD_GUARD();
+
ERR_FAIL_COND_MSG(draw_list != nullptr && draw_list->state.draw_count > 0, "Capturing timestamps during draw list creation is not allowed. Offending timestamp was: " + p_name);
ERR_FAIL_COND_MSG(compute_list != nullptr && compute_list->state.dispatch_count > 0, "Capturing timestamps during compute list creation is not allowed. Offending timestamp was: " + p_name);
ERR_FAIL_COND_MSG(frames[frame].timestamp_count >= max_timestamp_query_elements, vformat("Tried capturing more timestamps than the configured maximum (%d). You can increase this limit in the project settings under 'Debug/Settings' called 'Max Timestamp Query Elements'.", max_timestamp_query_elements));
@@ -5653,7 +6225,7 @@ void RenderingDevice::capture_timestamp(const String &p_name) {
}
uint64_t RenderingDevice::get_driver_resource(DriverResource p_resource, RID p_rid, uint64_t p_index) {
- _THREAD_SAFE_METHOD_
+ ERR_RENDER_THREAD_GUARD_V(0);
uint64_t driver_id = 0;
switch (p_resource) {
@@ -5769,19 +6341,23 @@ uint64_t RenderingDevice::get_device_allocs_by_object_type(uint32_t type) const
}
uint32_t RenderingDevice::get_captured_timestamps_count() const {
+ ERR_RENDER_THREAD_GUARD_V(0);
return frames[frame].timestamp_result_count;
}
uint64_t RenderingDevice::get_captured_timestamps_frame() const {
+ ERR_RENDER_THREAD_GUARD_V(0);
return frames[frame].index;
}
uint64_t RenderingDevice::get_captured_timestamp_gpu_time(uint32_t p_index) const {
+ ERR_RENDER_THREAD_GUARD_V(0);
ERR_FAIL_UNSIGNED_INDEX_V(p_index, frames[frame].timestamp_result_count, 0);
return driver->timestamp_query_result_to_time(frames[frame].timestamp_result_values[p_index]);
}
uint64_t RenderingDevice::get_captured_timestamp_cpu_time(uint32_t p_index) const {
+ ERR_RENDER_THREAD_GUARD_V(0);
ERR_FAIL_UNSIGNED_INDEX_V(p_index, frames[frame].timestamp_result_count, 0);
return frames[frame].timestamp_cpu_result_values[p_index];
}
@@ -5796,11 +6372,17 @@ uint64_t RenderingDevice::limit_get(Limit p_limit) const {
}
void RenderingDevice::finalize() {
+ ERR_RENDER_THREAD_GUARD();
+
if (!frames.is_empty()) {
// Wait for all frames to have finished rendering.
_flush_and_stall_for_all_frames();
}
+ // Wait for transfer workers to finish.
+ _submit_transfer_workers(false);
+ _wait_for_transfer_workers();
+
// Delete everything the graph has created.
draw_graph.finalize();
@@ -5854,15 +6436,17 @@ void RenderingDevice::finalize() {
}
}
+ // Erase the transfer workers after all resources have been freed.
+ _free_transfer_workers();
+
// Free everything pending.
for (uint32_t i = 0; i < frames.size(); i++) {
int f = (frame + i) % frames.size();
_free_pending_resources(f);
driver->command_pool_free(frames[i].command_pool);
driver->timestamp_query_pool_free(frames[i].timestamp_pool);
- driver->semaphore_free(frames[i].setup_semaphore);
- driver->semaphore_free(frames[i].draw_semaphore);
- driver->fence_free(frames[i].draw_fence);
+ driver->semaphore_free(frames[i].semaphore);
+ driver->fence_free(frames[i].fence);
RDG::CommandBufferPool &buffer_pool = frames[i].command_buffer_pool;
for (uint32_t j = 0; j < buffer_pool.buffers.size(); j++) {
@@ -5909,6 +6493,15 @@ void RenderingDevice::finalize() {
present_queue = RDD::CommandQueueID();
}
+ if (transfer_queue) {
+ if (main_queue != transfer_queue) {
+ // Only delete the transfer queue if it's unique.
+ driver->command_queue_free(transfer_queue);
+ }
+
+ transfer_queue = RDD::CommandQueueID();
+ }
+
if (main_queue) {
driver->command_queue_free(main_queue);
main_queue = RDD::CommandQueueID();
@@ -6644,6 +7237,8 @@ RenderingDevice::RenderingDevice() {
if (singleton == nullptr) {
singleton = this;
}
+
+ render_thread_id = Thread::get_caller_id();
}
/*****************/
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index d8bf845756..19d21dfda2 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -33,6 +33,7 @@
#include "core/object/class_db.h"
#include "core/object/worker_thread_pool.h"
+#include "core/os/condition_variable.h"
#include "core/os/thread_safe.h"
#include "core/templates/local_vector.h"
#include "core/templates/oa_hash_map.h"
@@ -62,6 +63,10 @@ class RenderingDevice : public RenderingDeviceCommons {
GDCLASS(RenderingDevice, Object)
_THREAD_SAFE_CLASS_
+
+private:
+ Thread::ID render_thread_id;
+
public:
enum ShaderLanguage {
SHADER_LANGUAGE_GLSL,
@@ -178,10 +183,12 @@ private:
uint32_t size = 0;
BitField<RDD::BufferUsageBits> usage;
RDG::ResourceTracker *draw_tracker = nullptr;
+ int32_t transfer_worker_index = -1;
+ uint64_t transfer_worker_operation = 0;
};
Buffer *_get_buffer_from_owner(RID p_buffer);
- Error _buffer_update(Buffer *p_buffer, RID p_buffer_id, size_t p_offset, const uint8_t *p_data, size_t p_data_size, bool p_use_draw_queue = false, uint32_t p_required_align = 32);
+ Error _buffer_initialize(Buffer *p_buffer, const uint8_t *p_data, size_t p_data_size, uint32_t p_required_align = 32);
void update_perf_report();
@@ -189,9 +196,9 @@ private:
uint32_t copy_bytes_count = 0;
String perf_report_text;
- RID_Owner<Buffer> uniform_buffer_owner;
- RID_Owner<Buffer> storage_buffer_owner;
- RID_Owner<Buffer> texture_buffer_owner;
+ RID_Owner<Buffer, true> uniform_buffer_owner;
+ RID_Owner<Buffer, true> storage_buffer_owner;
+ RID_Owner<Buffer, true> texture_buffer_owner;
public:
Error buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size);
@@ -254,6 +261,8 @@ public:
RDG::ResourceTracker *draw_tracker = nullptr;
HashMap<Rect2i, RDG::ResourceTracker *> slice_trackers;
SharedFallback *shared_fallback = nullptr;
+ int32_t transfer_worker_index = -1;
+ uint64_t transfer_worker_operation = 0;
RDD::TextureSubresourceRange barrier_range() const {
RDD::TextureSubresourceRange r;
@@ -282,11 +291,13 @@ public:
}
};
- RID_Owner<Texture> texture_owner;
+ RID_Owner<Texture, true> texture_owner;
uint32_t texture_upload_region_size_px = 0;
Vector<uint8_t> _texture_get_data(Texture *tex, uint32_t p_layer, bool p_2d = false);
- Error _texture_update(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data, bool p_use_setup_queue, bool p_validate_can_update);
+ uint32_t _texture_layer_count(Texture *p_texture) const;
+ uint32_t _texture_alignment(Texture *p_texture) const;
+ Error _texture_initialize(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data);
void _texture_check_shared_fallback(Texture *p_texture);
void _texture_update_shared_fallback(RID p_texture_rid, Texture *p_texture, bool p_for_writing);
void _texture_free_shared_fallback(Texture *p_texture);
@@ -572,7 +583,7 @@ private:
uint32_t view_count;
};
- RID_Owner<Framebuffer> framebuffer_owner;
+ RID_Owner<Framebuffer, true> framebuffer_owner;
public:
// This ID is warranted to be unique for the same formats, does not need to be freed
@@ -593,7 +604,7 @@ public:
/**** SAMPLER ****/
/*****************/
private:
- RID_Owner<RDD::SamplerID> sampler_owner;
+ RID_Owner<RDD::SamplerID, true> sampler_owner;
public:
RID sampler_create(const SamplerState &p_state);
@@ -615,7 +626,7 @@ private:
// This mapping is done here internally, and it's not
// exposed.
- RID_Owner<Buffer> vertex_buffer_owner;
+ RID_Owner<Buffer, true> vertex_buffer_owner;
struct VertexDescriptionKey {
Vector<VertexAttribute> vertex_formats;
@@ -695,10 +706,12 @@ private:
Vector<RDD::BufferID> buffers; // Not owned, just referenced.
Vector<RDG::ResourceTracker *> draw_trackers; // Not owned, just referenced.
Vector<uint64_t> offsets;
+ Vector<int32_t> transfer_worker_indices;
+ Vector<uint64_t> transfer_worker_operations;
HashSet<RID> untracked_buffers;
};
- RID_Owner<VertexArray> vertex_array_owner;
+ RID_Owner<VertexArray, true> vertex_array_owner;
struct IndexBuffer : public Buffer {
uint32_t max_index = 0; // Used for validation.
@@ -707,7 +720,7 @@ private:
bool supports_restart_indices = false;
};
- RID_Owner<IndexBuffer> index_buffer_owner;
+ RID_Owner<IndexBuffer, true> index_buffer_owner;
struct IndexArray {
uint32_t max_index = 0; // Remember the maximum index here too, for validation.
@@ -717,9 +730,11 @@ private:
uint32_t indices = 0;
IndexBufferFormat format = INDEX_BUFFER_FORMAT_UINT16;
bool supports_restart_indices = false;
+ int32_t transfer_worker_index = -1;
+ uint64_t transfer_worker_operation = 0;
};
- RID_Owner<IndexArray> index_array_owner;
+ RID_Owner<IndexArray, true> index_array_owner;
public:
RID vertex_buffer_create(uint32_t p_size_bytes, const Vector<uint8_t> &p_data = Vector<uint8_t>(), bool p_use_as_storage = false);
@@ -796,7 +811,7 @@ private:
String _shader_uniform_debug(RID p_shader, int p_set = -1);
- RID_Owner<Shader> shader_owner;
+ RID_Owner<Shader, true> shader_owner;
#ifndef DISABLE_DEPRECATED
public:
@@ -977,7 +992,7 @@ private:
void *invalidated_callback_userdata = nullptr;
};
- RID_Owner<UniformSet> uniform_set_owner;
+ RID_Owner<UniformSet, true> uniform_set_owner;
void _uniform_set_update_shared(UniformSet *p_uniform_set);
@@ -1024,7 +1039,7 @@ private:
uint32_t push_constant_size = 0;
};
- RID_Owner<RenderPipeline> render_pipeline_owner;
+ RID_Owner<RenderPipeline, true> render_pipeline_owner;
bool pipeline_cache_enabled = false;
size_t pipeline_cache_size = 0;
@@ -1045,7 +1060,7 @@ private:
uint32_t local_group_size[3] = { 0, 0, 0 };
};
- RID_Owner<ComputePipeline> compute_pipeline_owner;
+ RID_Owner<ComputePipeline, true> compute_pipeline_owner;
public:
RID render_pipeline_create(RID p_shader, FramebufferFormatID p_framebuffer_format, VertexFormatID p_vertex_format, RenderPrimitive p_render_primitive, const PipelineRasterizationState &p_rasterization_state, const PipelineMultisampleState &p_multisample_state, const PipelineDepthStencilState &p_depth_stencil_state, const PipelineColorBlendState &p_blend_state, BitField<PipelineDynamicStateFlags> p_dynamic_state_flags = 0, uint32_t p_for_render_pass = 0, const Vector<PipelineSpecializationConstant> &p_specialization_constants = Vector<PipelineSpecializationConstant>());
@@ -1101,6 +1116,7 @@ private:
RID pipeline_shader;
RDD::ShaderID pipeline_shader_driver_id;
uint32_t pipeline_shader_layout_hash = 0;
+ uint32_t pipeline_push_constant_size = 0;
RID vertex_array;
RID index_array;
uint32_t draw_count = 0;
@@ -1153,8 +1169,6 @@ private:
void _draw_list_insert_clear_region(DrawList *p_draw_list, Framebuffer *p_framebuffer, Point2i p_viewport_offset, Point2i p_viewport_size, bool p_clear_color, const Vector<Color> &p_clear_colors, bool p_clear_depth, float p_depth, uint32_t p_stencil);
Error _draw_list_setup_framebuffer(Framebuffer *p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, RDD::FramebufferID *r_framebuffer, RDD::RenderPassID *r_render_pass, uint32_t *r_subpass_count);
Error _draw_list_render_pass_begin(Framebuffer *p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_colors, float p_clear_depth, uint32_t p_clear_stencil, Point2i p_viewport_offset, Point2i p_viewport_size, RDD::FramebufferID p_framebuffer_driver_id, RDD::RenderPassID p_render_pass, uint32_t p_breadcrumb);
- void _draw_list_set_viewport(Rect2i p_rect);
- void _draw_list_set_scissor(Rect2i p_rect);
_FORCE_INLINE_ DrawList *_get_draw_list_ptr(DrawListID p_id);
Error _draw_list_allocate(const Rect2i &p_viewport, uint32_t p_subpass);
void _draw_list_free(Rect2i *r_last_viewport = nullptr);
@@ -1240,6 +1254,50 @@ public:
void compute_list_end();
private:
+ /*************************/
+ /**** TRANSFER WORKER ****/
+ /*************************/
+
+ struct TransferWorker {
+ uint32_t index = 0;
+ RDD::BufferID staging_buffer;
+ uint32_t max_transfer_size = 0;
+ uint32_t staging_buffer_size_in_use = 0;
+ uint32_t staging_buffer_size_allocated = 0;
+ RDD::CommandBufferID command_buffer;
+ RDD::CommandPoolID command_pool;
+ RDD::FenceID command_fence;
+ RDD::SemaphoreID command_semaphore;
+ bool recording = false;
+ bool submitted = false;
+ BinaryMutex thread_mutex;
+ uint64_t operations_processed = 0;
+ uint64_t operations_submitted = 0;
+ uint64_t operations_counter = 0;
+ BinaryMutex operations_mutex;
+ };
+
+ LocalVector<TransferWorker *> transfer_worker_pool;
+ uint32_t transfer_worker_pool_max_size = 1;
+ LocalVector<uint64_t> transfer_worker_operation_used_by_draw;
+ LocalVector<uint32_t> transfer_worker_pool_available_list;
+ BinaryMutex transfer_worker_pool_mutex;
+ ConditionVariable transfer_worker_pool_condition;
+
+ TransferWorker *_acquire_transfer_worker(uint32_t p_transfer_size, uint32_t p_required_align, uint32_t &r_staging_offset);
+ void _release_transfer_worker(TransferWorker *p_transfer_worker);
+ void _end_transfer_worker(TransferWorker *p_transfer_worker);
+ void _submit_transfer_worker(TransferWorker *p_transfer_worker, bool p_signal_semaphore);
+ void _wait_for_transfer_worker(TransferWorker *p_transfer_worker);
+ void _check_transfer_worker_operation(uint32_t p_transfer_worker_index, uint64_t p_transfer_worker_operation);
+ void _check_transfer_worker_buffer(Buffer *p_buffer);
+ void _check_transfer_worker_texture(Texture *p_texture);
+ void _check_transfer_worker_vertex_array(VertexArray *p_vertex_array);
+ void _check_transfer_worker_index_array(IndexArray *p_index_array);
+ void _submit_transfer_workers(bool p_operations_used_by_draw);
+ void _wait_for_transfer_workers();
+ void _free_transfer_workers();
+
/***********************/
/**** COMMAND GRAPH ****/
/***********************/
@@ -1250,6 +1308,7 @@ private:
bool _index_array_make_mutable(IndexArray *p_index_array, RDG::ResourceTracker *p_resource_tracker);
bool _uniform_set_make_mutable(UniformSet *p_uniform_set, RID p_resource_id, RDG::ResourceTracker *p_resource_tracker);
bool _dependency_make_mutable(RID p_id, RID p_resource_id, RDG::ResourceTracker *p_resource_tracker);
+ bool _dependencies_make_mutable_recursive(RID p_id, RDG::ResourceTracker *p_resource_tracker);
bool _dependencies_make_mutable(RID p_id, RDG::ResourceTracker *p_resource_tracker);
RenderingDeviceGraph draw_graph;
@@ -1259,8 +1318,10 @@ private:
/**************************/
RDD::CommandQueueFamilyID main_queue_family;
+ RDD::CommandQueueFamilyID transfer_queue_family;
RDD::CommandQueueFamilyID present_queue_family;
RDD::CommandQueueID main_queue;
+ RDD::CommandQueueID transfer_queue;
RDD::CommandQueueID present_queue;
/**************************/
@@ -1292,28 +1353,21 @@ private:
List<RenderPipeline> render_pipelines_to_dispose_of;
List<ComputePipeline> compute_pipelines_to_dispose_of;
+ // The command pool used by the command buffer.
RDD::CommandPoolID command_pool;
- // Used at the beginning of every frame for set-up.
- // Used for filling up newly created buffers with data provided on creation.
- // Primarily intended to be accessed by worker threads.
- // Ideally this command buffer should use an async transfer queue.
- RDD::CommandBufferID setup_command_buffer;
-
- // The main command buffer for drawing and compute.
- // Primarily intended to be used by the main thread to do most stuff.
- RDD::CommandBufferID draw_command_buffer;
+ // The command buffer used by the main thread when recording the frame.
+ RDD::CommandBufferID command_buffer;
- // Signaled by the setup submission. Draw must wait on this semaphore.
- RDD::SemaphoreID setup_semaphore;
+ // Signaled by the command buffer submission. Present must wait on this semaphore.
+ RDD::SemaphoreID semaphore;
- // Signaled by the draw submission. Present must wait on this semaphore.
- RDD::SemaphoreID draw_semaphore;
+ // Signaled by the command buffer submission. Must wait on this fence before beginning command recording for the frame.
+ RDD::FenceID fence;
+ bool fence_signaled = false;
- // Signaled by the draw submission. Must wait on this fence before beginning
- // command recording for the frame.
- RDD::FenceID draw_fence;
- bool draw_fence_signaled = false;
+ // Semaphores the frame must wait on before executing the command buffer.
+ LocalVector<RDD::SemaphoreID> semaphores_to_wait_on;
// Swap chains prepared for drawing during the frame that must be presented.
LocalVector<RDD::SwapChainID> swap_chains_to_present;
diff --git a/servers/rendering/rendering_device_commons.cpp b/servers/rendering/rendering_device_commons.cpp
index 4dbd0e3964..03fad5493a 100644
--- a/servers/rendering/rendering_device_commons.cpp
+++ b/servers/rendering/rendering_device_commons.cpp
@@ -600,7 +600,7 @@ void RenderingDeviceCommons::get_compressed_image_format_block_dimensions(DataFo
}
}
-uint32_t RenderingDeviceCommons::get_compressed_image_format_block_byte_size(DataFormat p_format) {
+uint32_t RenderingDeviceCommons::get_compressed_image_format_block_byte_size(DataFormat p_format) const {
switch (p_format) {
case DATA_FORMAT_BC1_RGB_UNORM_BLOCK:
case DATA_FORMAT_BC1_RGB_SRGB_BLOCK:
diff --git a/servers/rendering/rendering_device_commons.h b/servers/rendering/rendering_device_commons.h
index d72265958c..d516d968af 100644
--- a/servers/rendering/rendering_device_commons.h
+++ b/servers/rendering/rendering_device_commons.h
@@ -893,7 +893,7 @@ protected:
static uint32_t get_image_format_pixel_size(DataFormat p_format);
static void get_compressed_image_format_block_dimensions(DataFormat p_format, uint32_t &r_w, uint32_t &r_h);
- uint32_t get_compressed_image_format_block_byte_size(DataFormat p_format);
+ uint32_t get_compressed_image_format_block_byte_size(DataFormat p_format) const;
static uint32_t get_compressed_image_format_pixel_rshift(DataFormat p_format);
static uint32_t get_image_format_required_size(DataFormat p_format, uint32_t p_width, uint32_t p_height, uint32_t p_depth, uint32_t p_mipmaps, uint32_t *r_blockw = nullptr, uint32_t *r_blockh = nullptr, uint32_t *r_depth = nullptr);
static uint32_t get_image_required_mipmaps(uint32_t p_width, uint32_t p_height, uint32_t p_depth);
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index 97c84c9d05..91da67c8d7 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -104,14 +104,14 @@ struct VersatileResourceTemplate {
uint8_t data[MAX_RESOURCE_SIZE];
template <typename T>
- static T *allocate(PagedAllocator<VersatileResourceTemplate> &p_allocator) {
+ static T *allocate(PagedAllocator<VersatileResourceTemplate, true> &p_allocator) {
T *obj = (T *)p_allocator.alloc();
memnew_placement(obj, T);
return obj;
}
template <typename T>
- static void free(PagedAllocator<VersatileResourceTemplate> &p_allocator, T *p_object) {
+ static void free(PagedAllocator<VersatileResourceTemplate, true> &p_allocator, T *p_object) {
p_object->~T();
p_allocator.free((VersatileResourceTemplate *)p_object);
}
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index f6212faf08..4c277ac215 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -118,6 +118,11 @@ public:
virtual Variant instance_geometry_get_shader_parameter(RID p_instance, const StringName &p_parameter) const = 0;
virtual Variant instance_geometry_get_shader_parameter_default_value(RID p_instance, const StringName &p_parameter) const = 0;
+ /* PIPELINES */
+
+ virtual void mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) = 0;
+ virtual uint32_t get_pipeline_compilations(RS::PipelineSource p_source) = 0;
+
/* SKY API */
virtual RID sky_allocate() = 0;
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index b994ebf337..20f1f9ad6f 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -281,6 +281,16 @@ uint64_t RenderingServerDefault::get_rendering_info(RenderingInfo p_info) {
return RSG::viewport->get_total_primitives_drawn();
} else if (p_info == RENDERING_INFO_TOTAL_DRAW_CALLS_IN_FRAME) {
return RSG::viewport->get_total_draw_calls_used();
+ } else if (p_info == RENDERING_INFO_PIPELINE_COMPILATIONS_CANVAS) {
+ return RSG::canvas_render->get_pipeline_compilations(PIPELINE_SOURCE_CANVAS);
+ } else if (p_info == RENDERING_INFO_PIPELINE_COMPILATIONS_MESH) {
+ return RSG::canvas_render->get_pipeline_compilations(PIPELINE_SOURCE_MESH) + RSG::scene->get_pipeline_compilations(PIPELINE_SOURCE_MESH);
+ } else if (p_info == RENDERING_INFO_PIPELINE_COMPILATIONS_SURFACE) {
+ return RSG::scene->get_pipeline_compilations(PIPELINE_SOURCE_SURFACE);
+ } else if (p_info == RENDERING_INFO_PIPELINE_COMPILATIONS_DRAW) {
+ return RSG::canvas_render->get_pipeline_compilations(PIPELINE_SOURCE_DRAW) + RSG::scene->get_pipeline_compilations(PIPELINE_SOURCE_DRAW);
+ } else if (p_info == RENDERING_INFO_PIPELINE_COMPILATIONS_SPECIALIZATION) {
+ return RSG::canvas_render->get_pipeline_compilations(PIPELINE_SOURCE_SPECIALIZATION) + RSG::scene->get_pipeline_compilations(PIPELINE_SOURCE_SPECIALIZATION);
}
return RSG::utilities->get_rendering_info(p_info);
}
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 2dcdc3f254..225a67fb52 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -129,32 +129,32 @@ public:
#define ServerName RendererTextureStorage
#define server_name RSG::texture_storage
-#define FUNCRIDTEX0(m_type) \
- virtual RID m_type##_create() override { \
- RID ret = RSG::texture_storage->texture_allocate(); \
- if (Thread::get_caller_id() == server_thread || RSG::texture_storage->can_create_resources_async()) { \
- RSG::texture_storage->m_type##_initialize(ret); \
- } else { \
- command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret); \
- } \
- return ret; \
+#define FUNCRIDTEX0(m_type) \
+ virtual RID m_type##_create() override { \
+ RID ret = RSG::texture_storage->texture_allocate(); \
+ if (Thread::get_caller_id() == server_thread || RSG::rasterizer->can_create_resources_async()) { \
+ RSG::texture_storage->m_type##_initialize(ret); \
+ } else { \
+ command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret); \
+ } \
+ return ret; \
}
-#define FUNCRIDTEX1(m_type, m_type1) \
- virtual RID m_type##_create(m_type1 p1) override { \
- RID ret = RSG::texture_storage->texture_allocate(); \
- if (Thread::get_caller_id() == server_thread || RSG::texture_storage->can_create_resources_async()) { \
- RSG::texture_storage->m_type##_initialize(ret, p1); \
- } else { \
- command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret, p1); \
- } \
- return ret; \
+#define FUNCRIDTEX1(m_type, m_type1) \
+ virtual RID m_type##_create(m_type1 p1) override { \
+ RID ret = RSG::texture_storage->texture_allocate(); \
+ if (Thread::get_caller_id() == server_thread || RSG::rasterizer->can_create_resources_async()) { \
+ RSG::texture_storage->m_type##_initialize(ret, p1); \
+ } else { \
+ command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret, p1); \
+ } \
+ return ret; \
}
#define FUNCRIDTEX2(m_type, m_type1, m_type2) \
virtual RID m_type##_create(m_type1 p1, m_type2 p2) override { \
RID ret = RSG::texture_storage->texture_allocate(); \
- if (Thread::get_caller_id() == server_thread || RSG::texture_storage->can_create_resources_async()) { \
+ if (Thread::get_caller_id() == server_thread || RSG::rasterizer->can_create_resources_async()) { \
RSG::texture_storage->m_type##_initialize(ret, p1, p2); \
} else { \
command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret, p1, p2); \
@@ -165,7 +165,7 @@ public:
#define FUNCRIDTEX3(m_type, m_type1, m_type2, m_type3) \
virtual RID m_type##_create(m_type1 p1, m_type2 p2, m_type3 p3) override { \
RID ret = RSG::texture_storage->texture_allocate(); \
- if (Thread::get_caller_id() == server_thread || RSG::texture_storage->can_create_resources_async()) { \
+ if (Thread::get_caller_id() == server_thread || RSG::rasterizer->can_create_resources_async()) { \
RSG::texture_storage->m_type##_initialize(ret, p1, p2, p3); \
} else { \
command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret, p1, p2, p3); \
@@ -176,7 +176,7 @@ public:
#define FUNCRIDTEX6(m_type, m_type1, m_type2, m_type3, m_type4, m_type5, m_type6) \
virtual RID m_type##_create(m_type1 p1, m_type2 p2, m_type3 p3, m_type4 p4, m_type5 p5, m_type6 p6) override { \
RID ret = RSG::texture_storage->texture_allocate(); \
- if (Thread::get_caller_id() == server_thread || RSG::texture_storage->can_create_resources_async()) { \
+ if (Thread::get_caller_id() == server_thread || RSG::rasterizer->can_create_resources_async()) { \
RSG::texture_storage->m_type##_initialize(ret, p1, p2, p3, p4, p5, p6); \
} else { \
command_queue.push(RSG::texture_storage, &RendererTextureStorage::m_type##_initialize, ret, p1, p2, p3, p4, p5, p6); \
@@ -245,6 +245,26 @@ public:
FUNCRIDSPLIT(shader)
+ virtual RID shader_create_from_code(const String &p_code, const String &p_path_hint = String()) override {
+ RID shader = RSG::material_storage->shader_allocate();
+ bool using_server_thread = Thread::get_caller_id() == server_thread;
+ if (using_server_thread || RSG::rasterizer->can_create_resources_async()) {
+ if (using_server_thread) {
+ command_queue.flush_if_pending();
+ }
+
+ RSG::material_storage->shader_initialize(shader);
+ RSG::material_storage->shader_set_code(shader, p_code);
+ RSG::material_storage->shader_set_path_hint(shader, p_path_hint);
+ } else {
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::shader_initialize, shader);
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::shader_set_code, shader, p_code);
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::shader_set_path_hint, shader, p_path_hint);
+ }
+
+ return shader;
+ }
+
FUNC2(shader_set_code, RID, const String &)
FUNC2(shader_set_path_hint, RID, const String &)
FUNC1RC(String, shader_get_code, RID)
@@ -261,6 +281,28 @@ public:
FUNCRIDSPLIT(material)
+ virtual RID material_create_from_shader(RID p_next_pass, int p_render_priority, RID p_shader) override {
+ RID material = RSG::material_storage->material_allocate();
+ bool using_server_thread = Thread::get_caller_id() == server_thread;
+ if (using_server_thread || RSG::rasterizer->can_create_resources_async()) {
+ if (using_server_thread) {
+ command_queue.flush_if_pending();
+ }
+
+ RSG::material_storage->material_initialize(material);
+ RSG::material_storage->material_set_next_pass(material, p_next_pass);
+ RSG::material_storage->material_set_render_priority(material, p_render_priority);
+ RSG::material_storage->material_set_shader(material, p_shader);
+ } else {
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::material_initialize, material);
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::material_set_next_pass, material, p_next_pass);
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::material_set_render_priority, material, p_render_priority);
+ command_queue.push(RSG::material_storage, &RendererMaterialStorage::material_set_shader, material, p_shader);
+ }
+
+ return material;
+ }
+
FUNC2(material_set_shader, RID, RID)
FUNC3(material_set_param, RID, const StringName &, const Variant &)
@@ -281,10 +323,9 @@ public:
virtual RID mesh_create_from_surfaces(const Vector<SurfaceData> &p_surfaces, int p_blend_shape_count = 0) override {
RID mesh = RSG::mesh_storage->mesh_allocate();
- // TODO once we have RSG::mesh_storage, add can_create_resources_async and call here instead of texture_storage!!
-
- if (Thread::get_caller_id() == server_thread || RSG::texture_storage->can_create_resources_async()) {
- if (Thread::get_caller_id() == server_thread) {
+ bool using_server_thread = Thread::get_caller_id() == server_thread;
+ if (using_server_thread || RSG::rasterizer->can_create_resources_async()) {
+ if (using_server_thread) {
command_queue.flush_if_pending();
}
RSG::mesh_storage->mesh_initialize(mesh);
@@ -292,12 +333,14 @@ public:
for (int i = 0; i < p_surfaces.size(); i++) {
RSG::mesh_storage->mesh_add_surface(mesh, p_surfaces[i]);
}
+ RSG::scene->mesh_generate_pipelines(mesh, using_server_thread);
} else {
command_queue.push(RSG::mesh_storage, &RendererMeshStorage::mesh_initialize, mesh);
command_queue.push(RSG::mesh_storage, &RendererMeshStorage::mesh_set_blend_shape_count, mesh, p_blend_shape_count);
for (int i = 0; i < p_surfaces.size(); i++) {
command_queue.push(RSG::mesh_storage, &RendererMeshStorage::mesh_add_surface, mesh, p_surfaces[i]);
}
+ command_queue.push(RSG::scene, &RenderingMethod::mesh_generate_pipelines, mesh, true);
}
return mesh;
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 3a0b9cf158..119ac677eb 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -355,7 +355,7 @@ void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const Str
}
header += " ";
- header += _mkid(fnode->name);
+ header += _mkid(fnode->rname);
header += "(";
for (int i = 0; i < fnode->arguments.size(); i++) {
@@ -951,7 +951,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
code = _get_global_shader_uniform_from_type_and_index(p_default_actions.global_buffer_array_variable, code, u.type);
} else if (u.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) {
//instance variable, index it as such
- code = "(" + p_default_actions.instance_uniform_index_variable + "+" + itos(u.instance_index) + ")";
+ code = "(" + p_default_actions.instance_uniform_index_variable + "+" + itos(u.instance_index) + "u)";
code = _get_global_shader_uniform_from_type_and_index(p_default_actions.global_buffer_array_variable, code, u.type);
} else {
//regular uniform, index from UBO
@@ -1051,7 +1051,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
code = _get_global_shader_uniform_from_type_and_index(p_default_actions.global_buffer_array_variable, code, u.type);
} else if (u.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) {
//instance variable, index it as such
- code = "(" + p_default_actions.instance_uniform_index_variable + "+" + itos(u.instance_index) + ")";
+ code = "(" + p_default_actions.instance_uniform_index_variable + "+" + itos(u.instance_index) + "u)";
code = _get_global_shader_uniform_from_type_and_index(p_default_actions.global_buffer_array_variable, code, u.type);
} else {
//regular uniform, index from UBO
@@ -1190,7 +1190,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
} else if (p_default_actions.renames.has(vnode->name)) {
code += p_default_actions.renames[vnode->name];
} else {
- code += _mkid(vnode->name);
+ code += _mkid(vnode->rname);
}
}
@@ -1354,7 +1354,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
}
code += ")";
if (is_screen_texture && !texture_func_returns_data && actions.apply_luminance_multiplier) {
- code = "(" + code + " * vec4(vec3(sc_luminance_multiplier), 1.0))";
+ code = "(" + code + " * vec4(vec3(sc_luminance_multiplier()), 1.0))";
}
if (is_normal_roughness_texture && !texture_func_returns_data) {
code = "normal_roughness_compatibility(" + code + ")";
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 879a83f519..b6770c773c 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1305,6 +1305,7 @@ void ShaderLanguage::clear() {
include_markers_handled.clear();
calls_info.clear();
+ function_overload_count.clear();
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
@@ -3554,6 +3555,7 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
ERR_FAIL_COND_V(p_func->arguments[0]->type != Node::NODE_TYPE_VARIABLE, false);
StringName name = static_cast<VariableNode *>(p_func->arguments[0])->name.operator String();
+ StringName rname = static_cast<VariableNode *>(p_func->arguments[0])->rname.operator String();
for (int i = 1; i < p_func->arguments.size(); i++) {
args.push_back(p_func->arguments[i]->get_datatype());
@@ -3891,9 +3893,10 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
}
bool fail = false;
+ bool use_constant_conversion = function_overload_count[rname] == 0;
for (int j = 0; j < args.size(); j++) {
- if (get_scalar_type(args[j]) == args[j] && p_func->arguments[j + 1]->type == Node::NODE_TYPE_CONSTANT && args3[j] == 0 && convert_constant(static_cast<ConstantNode *>(p_func->arguments[j + 1]), pfunc->arguments[j].type)) {
+ if (use_constant_conversion && get_scalar_type(args[j]) == args[j] && p_func->arguments[j + 1]->type == Node::NODE_TYPE_CONSTANT && args3[j] == 0 && convert_constant(static_cast<ConstantNode *>(p_func->arguments[j + 1]), pfunc->arguments[j].type)) {
//all good, but it needs implicit conversion later
} else if (args[j] != pfunc->arguments[j].type || (args[j] == TYPE_STRUCT && args2[j] != pfunc->arguments[j].struct_name) || args3[j] != pfunc->arguments[j].array_size) {
String func_arg_name;
@@ -3919,7 +3922,7 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
arg_name += "]";
}
- _set_error(vformat(RTR("Invalid argument for \"%s(%s)\" function: argument %d should be %s but is %s."), String(name), arg_list, j + 1, func_arg_name, arg_name));
+ _set_error(vformat(RTR("Invalid argument for \"%s(%s)\" function: argument %d should be %s but is %s."), String(rname), arg_list, j + 1, func_arg_name, arg_name));
fail = true;
break;
}
@@ -3960,9 +3963,9 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
if (exists) {
if (last_arg_count > args.size()) {
- _set_error(vformat(RTR("Too few arguments for \"%s(%s)\" call. Expected at least %d but received %d."), String(name), arg_list, last_arg_count, args.size()));
+ _set_error(vformat(RTR("Too few arguments for \"%s(%s)\" call. Expected at least %d but received %d."), String(rname), arg_list, last_arg_count, args.size()));
} else if (last_arg_count < args.size()) {
- _set_error(vformat(RTR("Too many arguments for \"%s(%s)\" call. Expected at most %d but received %d."), String(name), arg_list, last_arg_count, args.size()));
+ _set_error(vformat(RTR("Too many arguments for \"%s(%s)\" call. Expected at most %d but received %d."), String(rname), arg_list, last_arg_count, args.size()));
}
}
@@ -5822,42 +5825,16 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
}
- const StringName &name = identifier;
-
- if (name != current_function) { // Recursion is not allowed.
- // Register call.
- if (calls_info.has(name)) {
- calls_info[current_function].calls.push_back(&calls_info[name]);
- }
-
- int idx = 0;
- bool is_builtin = false;
-
- while (frag_only_func_defs[idx].name) {
- if (frag_only_func_defs[idx].name == name) {
- // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
- if (!is_supported_frag_only_funcs) {
- _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
- return nullptr;
- }
- // Register usage of the restricted function.
- calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
- is_builtin = true;
- break;
- }
- idx++;
- }
-
- // Recursively checks for the restricted function call.
- if (is_supported_frag_only_funcs && current_function == "vertex" && stages->has(current_function) && !_validate_restricted_func(name, &calls_info[current_function], is_builtin)) {
- return nullptr;
- }
- }
+ const StringName &rname = identifier;
+ StringName name = identifier;
OperatorNode *func = alloc_node<OperatorNode>();
func->op = OP_CALL;
+
VariableNode *funcname = alloc_node<VariableNode>();
funcname->name = name;
+ funcname->rname = name;
+
func->arguments.push_back(funcname);
int carg = -1;
@@ -5874,22 +5851,72 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
bnode = bnode->parent_block;
}
- //test if function was parsed first
+ // Test if function was parsed first.
int function_index = -1;
- for (int i = 0; i < shader->vfunctions.size(); i++) {
- if (shader->vfunctions[i].name == name) {
- //add to current function as dependency
- for (int j = 0; j < shader->vfunctions.size(); j++) {
- if (shader->vfunctions[j].name == current_function) {
- shader->vfunctions.write[j].uses_function.insert(name);
- break;
+ for (int i = 0, max_valid_args = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable || shader->vfunctions[i].rname != rname) {
+ continue;
+ }
+
+ bool found = true;
+ int valid_args = 0;
+
+ // Search for correct overload.
+ for (int j = 1; j < func->arguments.size(); j++) {
+ if (j - 1 == shader->vfunctions[i].function->arguments.size()) {
+ found = false;
+ break;
+ }
+
+ const FunctionNode::Argument &a = shader->vfunctions[i].function->arguments[j - 1];
+ Node *b = func->arguments[j];
+
+ if (a.type == b->get_datatype() && a.array_size == b->get_array_size()) {
+ if (a.type == TYPE_STRUCT) {
+ if (a.struct_name != b->get_datatype_name()) {
+ found = false;
+ break;
+ } else {
+ valid_args++;
+ }
+ } else {
+ valid_args++;
}
+ } else {
+ if (function_overload_count[rname] == 0 && get_scalar_type(a.type) == a.type && b->type == Node::NODE_TYPE_CONSTANT && a.array_size == 0 && convert_constant(static_cast<ConstantNode *>(b), a.type)) {
+ // Implicit cast if no overloads.
+ continue;
+ }
+ found = false;
+ break;
}
+ }
- //see if texture arguments must connect
- function_index = i;
- break;
+ // Using the best match index for completion hint if the function not found.
+ if (valid_args > max_valid_args) {
+ name = shader->vfunctions[i].name;
+ funcname->name = name;
+ max_valid_args = valid_args;
}
+
+ if (!found) {
+ continue;
+ }
+
+ // Add to current function as dependency.
+ for (int j = 0; j < shader->vfunctions.size(); j++) {
+ if (shader->vfunctions[j].name == current_function) {
+ shader->vfunctions.write[j].uses_function.insert(name);
+ break;
+ }
+ }
+
+ name = shader->vfunctions[i].name;
+ funcname->name = name;
+
+ // See if texture arguments must connect.
+ function_index = i;
+ break;
}
if (carg >= 0) {
@@ -5904,9 +5931,39 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
return nullptr;
}
+ if (name != current_function) { // Recursion is not allowed.
+ // Register call.
+ if (calls_info.has(name)) {
+ calls_info[current_function].calls.push_back(&calls_info[name]);
+ }
+
+ int idx = 0;
+ bool is_builtin = false;
+
+ while (frag_only_func_defs[idx].name) {
+ if (frag_only_func_defs[idx].name == name) {
+ // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
+ if (!is_supported_frag_only_funcs) {
+ _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
+ return nullptr;
+ }
+ // Register usage of the restricted function.
+ calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
+ is_builtin = true;
+ break;
+ }
+ idx++;
+ }
+
+ // Recursively checks for the restricted function call.
+ if (is_supported_frag_only_funcs && current_function == "vertex" && stages->has(current_function) && !_validate_restricted_func(name, &calls_info[current_function], is_builtin)) {
+ return nullptr;
+ }
+ }
+
bool is_custom_func = false;
if (!_validate_function_call(p_block, p_function_info, func, &func->return_cache, &func->struct_name, &is_custom_func)) {
- _set_error(vformat(RTR("No matching function found for: '%s'."), String(funcname->name)));
+ _set_error(vformat(RTR("No matching function found for: '%s'."), String(funcname->rname)));
return nullptr;
}
completion_class = TAG_GLOBAL; // reset sub-class
@@ -6095,7 +6152,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
expr = func;
#ifdef DEBUG_ENABLED
- if (check_warnings) {
+ if (check_warnings && is_custom_func) {
StringName func_name;
if (p_block && p_block->parent_function) {
@@ -9089,10 +9146,6 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
_set_error(vformat(RTR("Uniform instances are not yet implemented for '%s' shaders."), shader_type_identifier));
return ERR_PARSE_ERROR;
}
- if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
- _set_error(RTR("Uniform instances are not supported in gl_compatibility shaders."));
- return ERR_PARSE_ERROR;
- }
if (uniform_scope == ShaderNode::Uniform::SCOPE_LOCAL) {
tk = _get_token();
if (tk.type != TK_UNIFORM) {
@@ -9936,7 +9989,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
return ERR_PARSE_ERROR;
}
- if (shader->structs.has(name) || _find_identifier(nullptr, false, constants, name) || has_builtin(p_functions, name, !is_constant)) {
+ IdentifierType itype;
+ if (shader->structs.has(name) || (_find_identifier(nullptr, false, constants, name, nullptr, &itype) && itype != IDENTIFIER_FUNCTION) || has_builtin(p_functions, name, !is_constant)) {
_set_redefinition_error(String(name));
return ERR_PARSE_ERROR;
}
@@ -10264,20 +10318,13 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
function.callable = !p_functions.has(name);
function.name = name;
+ function.rname = name;
FunctionNode *func_node = alloc_node<FunctionNode>();
-
function.function = func_node;
- shader->functions.insert(name, function);
- shader->vfunctions.push_back(function);
-
- CallInfo call_info;
- call_info.name = name;
-
- calls_info.insert(name, call_info);
-
func_node->name = name;
+ func_node->rname = name;
func_node->return_type = type;
func_node->return_struct_name = struct_name;
func_node->return_precision = precision;
@@ -10285,12 +10332,12 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
if (p_functions.has(name)) {
func_node->can_discard = p_functions[name].can_discard;
+ }
+
+ if (!function_overload_count.has(name)) {
+ function_overload_count.insert(name, 0);
} else {
-#ifdef DEBUG_ENABLED
- if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_FUNCTION_FLAG)) {
- used_functions.insert(name, Usage(tk_line));
- }
-#endif // DEBUG_ENABLED
+ function_overload_count[name]++;
}
func_node->body = alloc_node<BlockNode>();
@@ -10470,7 +10517,6 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
param_name = tk.text;
- ShaderLanguage::IdentifierType itype;
if (_find_identifier(func_node->body, false, builtins, param_name, (ShaderLanguage::DataType *)nullptr, &itype)) {
if (itype != IDENTIFIER_FUNCTION) {
_set_redefinition_error(String(param_name));
@@ -10516,6 +10562,66 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
}
}
+ // Searches for function index and check for the exact duplicate in overloads.
+ int function_index = 0;
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable || shader->vfunctions[i].rname != name) {
+ continue;
+ }
+
+ function_index++;
+
+ if (shader->vfunctions[i].function->arguments.size() != func_node->arguments.size()) {
+ continue;
+ }
+
+ bool is_same = true;
+
+ for (int j = 0; j < shader->vfunctions[i].function->arguments.size(); j++) {
+ FunctionNode::Argument a = func_node->arguments[j];
+ FunctionNode::Argument b = shader->vfunctions[i].function->arguments[j];
+
+ if (a.type == b.type && a.array_size == b.array_size) {
+ if (a.type == TYPE_STRUCT) {
+ is_same = a.struct_name == b.struct_name;
+ }
+ } else {
+ is_same = false;
+ }
+
+ if (!is_same) {
+ break;
+ }
+ }
+
+ if (is_same) {
+ _set_redefinition_error(String(name));
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ // Creates a fake name for function overload, which will be replaced by the real name by the compiler.
+ String name2 = name;
+ if (function_index > 0) {
+ name2 = vformat("%s@%s", name, itos(function_index + 1));
+
+ function.name = name2;
+ func_node->name = name2;
+ }
+
+ shader->functions.insert(name2, function);
+ shader->vfunctions.push_back(function);
+
+ CallInfo call_info;
+ call_info.name = name2;
+ calls_info.insert(name2, call_info);
+
+#ifdef DEBUG_ENABLED
+ if (check_warnings && HAS_WARNING(ShaderWarning::UNUSED_FUNCTION_FLAG) && !p_functions.has(name)) {
+ used_functions.insert(name2, Usage(tk_line));
+ }
+#endif // DEBUG_ENABLED
+
if (p_functions.has(name)) {
//if one of the core functions, make sure they are of the correct form
if (func_node->arguments.size() > 0) {
@@ -10535,7 +10641,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
return ERR_PARSE_ERROR;
}
- current_function = name;
+ current_function = name2;
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_BLOCK;
@@ -11048,7 +11154,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
if (!shader->vfunctions[i].callable || shader->vfunctions[i].name == skip_function) {
continue;
}
- matches.insert(String(shader->vfunctions[i].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ matches.insert(String(shader->vfunctions[i].rname), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
}
int idx = 0;
@@ -11099,6 +11205,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
case COMPLETION_CALL_ARGUMENTS: {
StringName block_function;
BlockNode *block = completion_block;
+ String calltip;
while (block) {
if (block->parent_function) {
@@ -11107,85 +11214,83 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
block = block->parent_block;
}
- for (int i = 0; i < shader->vfunctions.size(); i++) {
- if (!shader->vfunctions[i].callable) {
+ for (int i = 0, overload_index = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable || shader->vfunctions[i].rname != completion_function) {
continue;
}
- if (shader->vfunctions[i].name == completion_function) {
- String calltip;
- if (shader->vfunctions[i].function->return_type == TYPE_STRUCT) {
- calltip += String(shader->vfunctions[i].function->return_struct_name);
- } else {
- calltip += get_datatype_name(shader->vfunctions[i].function->return_type);
- }
-
- if (shader->vfunctions[i].function->return_array_size > 0) {
- calltip += "[";
- calltip += itos(shader->vfunctions[i].function->return_array_size);
- calltip += "]";
- }
+ if (shader->vfunctions[i].function->return_type == TYPE_STRUCT) {
+ calltip += String(shader->vfunctions[i].function->return_struct_name);
+ } else {
+ calltip += get_datatype_name(shader->vfunctions[i].function->return_type);
+ }
- calltip += " ";
- calltip += shader->vfunctions[i].name;
- calltip += "(";
+ if (shader->vfunctions[i].function->return_array_size > 0) {
+ calltip += "[";
+ calltip += itos(shader->vfunctions[i].function->return_array_size);
+ calltip += "]";
+ }
- for (int j = 0; j < shader->vfunctions[i].function->arguments.size(); j++) {
- if (j > 0) {
- calltip += ", ";
- } else {
- calltip += " ";
- }
+ calltip += " ";
+ calltip += shader->vfunctions[i].rname;
+ calltip += "(";
- if (j == completion_argument) {
- calltip += char32_t(0xFFFF);
- }
+ for (int j = 0; j < shader->vfunctions[i].function->arguments.size(); j++) {
+ if (j > 0) {
+ calltip += ", ";
+ } else {
+ calltip += " ";
+ }
- if (shader->vfunctions[i].function->arguments[j].is_const) {
- calltip += "const ";
- }
+ if (j == completion_argument) {
+ calltip += char32_t(0xFFFF);
+ }
- if (shader->vfunctions[i].function->arguments[j].qualifier != ArgumentQualifier::ARGUMENT_QUALIFIER_IN) {
- if (shader->vfunctions[i].function->arguments[j].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT) {
- calltip += "out ";
- } else { // ArgumentQualifier::ARGUMENT_QUALIFIER_INOUT
- calltip += "inout ";
- }
- }
+ if (shader->vfunctions[i].function->arguments[j].is_const) {
+ calltip += "const ";
+ }
- if (shader->vfunctions[i].function->arguments[j].type == TYPE_STRUCT) {
- calltip += String(shader->vfunctions[i].function->arguments[j].struct_name);
- } else {
- calltip += get_datatype_name(shader->vfunctions[i].function->arguments[j].type);
+ if (shader->vfunctions[i].function->arguments[j].qualifier != ArgumentQualifier::ARGUMENT_QUALIFIER_IN) {
+ if (shader->vfunctions[i].function->arguments[j].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT) {
+ calltip += "out ";
+ } else { // ArgumentQualifier::ARGUMENT_QUALIFIER_INOUT
+ calltip += "inout ";
}
- calltip += " ";
- calltip += shader->vfunctions[i].function->arguments[j].name;
+ }
- if (shader->vfunctions[i].function->arguments[j].array_size > 0) {
- calltip += "[";
- calltip += itos(shader->vfunctions[i].function->arguments[j].array_size);
- calltip += "]";
- }
+ if (shader->vfunctions[i].function->arguments[j].type == TYPE_STRUCT) {
+ calltip += String(shader->vfunctions[i].function->arguments[j].struct_name);
+ } else {
+ calltip += get_datatype_name(shader->vfunctions[i].function->arguments[j].type);
+ }
+ calltip += " ";
+ calltip += shader->vfunctions[i].function->arguments[j].name;
- if (j == completion_argument) {
- calltip += char32_t(0xFFFF);
- }
+ if (shader->vfunctions[i].function->arguments[j].array_size > 0) {
+ calltip += "[";
+ calltip += itos(shader->vfunctions[i].function->arguments[j].array_size);
+ calltip += "]";
}
- if (shader->vfunctions[i].function->arguments.size()) {
- calltip += " ";
+ if (j == completion_argument) {
+ calltip += char32_t(0xFFFF);
}
- calltip += ")";
+ }
- r_call_hint = calltip;
- return OK;
+ if (shader->vfunctions[i].function->arguments.size()) {
+ calltip += " ";
}
- }
+ calltip += ")";
- int idx = 0;
+ if (overload_index < function_overload_count[shader->vfunctions[i].rname]) {
+ overload_index++;
+ calltip += "\n";
+ continue;
+ }
- String calltip;
- bool low_end = RenderingServer::get_singleton()->is_low_end();
+ r_call_hint = calltip;
+ return OK;
+ }
if (stages && stages->has(block_function)) {
for (const KeyValue<StringName, StageFunctionInfo> &E : (*stages)[block_function].stage_functions) {
@@ -11226,6 +11331,9 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
}
+ int idx = 0;
+ bool low_end = RenderingServer::get_singleton()->is_low_end();
+
while (builtin_func_defs[idx].name) {
if ((low_end && builtin_func_defs[idx].high_end) || _check_restricted_func(builtin_func_defs[idx].name, block_function)) {
idx++;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 48df77f6bb..fb0a526230 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -427,6 +427,7 @@ public:
struct VariableNode : public Node {
DataType datatype_cache = TYPE_VOID;
StringName name;
+ StringName rname;
StringName struct_name;
bool is_const = false;
bool is_local = false;
@@ -604,6 +605,7 @@ public:
struct Function {
StringName name;
+ StringName rname;
FunctionNode *function = nullptr;
HashSet<StringName> uses_function;
bool callable;
@@ -729,6 +731,7 @@ public:
};
StringName name;
+ StringName rname;
DataType return_type = TYPE_VOID;
StringName return_struct_name;
DataPrecision return_precision = PRECISION_DEFAULT;
@@ -944,6 +947,7 @@ private:
Vector<FilePosition> include_positions;
HashSet<String> include_markers_handled;
+ HashMap<StringName, int> function_overload_count;
// Additional function information (eg. call hierarchy). No need to expose it to compiler.
struct CallInfo {
diff --git a/servers/rendering/storage/texture_storage.h b/servers/rendering/storage/texture_storage.h
index fd17b052ee..e528384891 100644
--- a/servers/rendering/storage/texture_storage.h
+++ b/servers/rendering/storage/texture_storage.h
@@ -59,7 +59,6 @@ public:
virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) = 0;
/* Texture API */
- virtual bool can_create_resources_async() const = 0;
virtual ~RendererTextureStorage() {}
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 0ad56961c0..32ef5261f3 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3447,6 +3447,18 @@ void RenderingServer::_bind_methods() {
BIND_ENUM_CONSTANT(RENDERING_INFO_TEXTURE_MEM_USED);
BIND_ENUM_CONSTANT(RENDERING_INFO_BUFFER_MEM_USED);
BIND_ENUM_CONSTANT(RENDERING_INFO_VIDEO_MEM_USED);
+ BIND_ENUM_CONSTANT(RENDERING_INFO_PIPELINE_COMPILATIONS_CANVAS);
+ BIND_ENUM_CONSTANT(RENDERING_INFO_PIPELINE_COMPILATIONS_MESH);
+ BIND_ENUM_CONSTANT(RENDERING_INFO_PIPELINE_COMPILATIONS_SURFACE);
+ BIND_ENUM_CONSTANT(RENDERING_INFO_PIPELINE_COMPILATIONS_DRAW);
+ BIND_ENUM_CONSTANT(RENDERING_INFO_PIPELINE_COMPILATIONS_SPECIALIZATION);
+
+ BIND_ENUM_CONSTANT(PIPELINE_SOURCE_CANVAS);
+ BIND_ENUM_CONSTANT(PIPELINE_SOURCE_MESH);
+ BIND_ENUM_CONSTANT(PIPELINE_SOURCE_SURFACE);
+ BIND_ENUM_CONSTANT(PIPELINE_SOURCE_DRAW);
+ BIND_ENUM_CONSTANT(PIPELINE_SOURCE_SPECIALIZATION);
+ BIND_ENUM_CONSTANT(PIPELINE_SOURCE_MAX);
ADD_SIGNAL(MethodInfo("frame_pre_draw"));
ADD_SIGNAL(MethodInfo("frame_post_draw"));
@@ -3580,8 +3592,7 @@ void RenderingServer::init() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/global_illumination/voxel_gi/quality", PROPERTY_HINT_ENUM, "Low (4 Cones - Fast),High (6 Cones - Slow)"), 0);
- GLOBAL_DEF("rendering/shading/overrides/force_vertex_shading", false);
- GLOBAL_DEF("rendering/shading/overrides/force_vertex_shading.mobile", true);
+ GLOBAL_DEF_RST("rendering/shading/overrides/force_vertex_shading", false);
GLOBAL_DEF("rendering/shading/overrides/force_lambert_over_burley", false);
GLOBAL_DEF("rendering/shading/overrides/force_lambert_over_burley.mobile", true);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 878b02eaf1..0208a640a5 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -199,6 +199,17 @@ public:
virtual RID texture_get_rd_texture(RID p_texture, bool p_srgb = false) const = 0;
virtual uint64_t texture_get_native_handle(RID p_texture, bool p_srgb = false) const = 0;
+ /* PIPELINES API */
+
+ enum PipelineSource {
+ PIPELINE_SOURCE_CANVAS,
+ PIPELINE_SOURCE_MESH,
+ PIPELINE_SOURCE_SURFACE,
+ PIPELINE_SOURCE_DRAW,
+ PIPELINE_SOURCE_SPECIALIZATION,
+ PIPELINE_SOURCE_MAX
+ };
+
/* SHADER API */
enum ShaderMode {
@@ -211,6 +222,7 @@ public:
};
virtual RID shader_create() = 0;
+ virtual RID shader_create_from_code(const String &p_code, const String &p_path_hint = String()) = 0;
virtual void shader_set_code(RID p_shader, const String &p_code) = 0;
virtual void shader_set_path_hint(RID p_shader, const String &p_path) = 0;
@@ -242,6 +254,7 @@ public:
};
virtual RID material_create() = 0;
+ virtual RID material_create_from_shader(RID p_next_pass, int p_render_priority, RID p_shader) = 0;
virtual void material_set_shader(RID p_shader_material, RID p_shader) = 0;
@@ -1697,6 +1710,11 @@ public:
RENDERING_INFO_TEXTURE_MEM_USED,
RENDERING_INFO_BUFFER_MEM_USED,
RENDERING_INFO_VIDEO_MEM_USED,
+ RENDERING_INFO_PIPELINE_COMPILATIONS_CANVAS,
+ RENDERING_INFO_PIPELINE_COMPILATIONS_MESH,
+ RENDERING_INFO_PIPELINE_COMPILATIONS_SURFACE,
+ RENDERING_INFO_PIPELINE_COMPILATIONS_DRAW,
+ RENDERING_INFO_PIPELINE_COMPILATIONS_SPECIALIZATION,
RENDERING_INFO_MAX
};
@@ -1807,6 +1825,7 @@ private:
VARIANT_ENUM_CAST(RenderingServer::TextureType);
VARIANT_ENUM_CAST(RenderingServer::TextureLayeredType);
VARIANT_ENUM_CAST(RenderingServer::CubeMapLayer);
+VARIANT_ENUM_CAST(RenderingServer::PipelineSource);
VARIANT_ENUM_CAST(RenderingServer::ShaderMode);
VARIANT_ENUM_CAST(RenderingServer::ArrayType);
VARIANT_BITFIELD_CAST(RenderingServer::ArrayFormat);
diff --git a/tests/core/io/test_packet_peer.h b/tests/core/io/test_packet_peer.h
new file mode 100644
index 0000000000..59c8dadad8
--- /dev/null
+++ b/tests/core/io/test_packet_peer.h
@@ -0,0 +1,204 @@
+/**************************************************************************/
+/* test_packet_peer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_PACKET_PEER_H
+#define TEST_PACKET_PEER_H
+
+#include "core/io/packet_peer.h"
+#include "tests/test_macros.h"
+
+namespace TestPacketPeer {
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Encode buffer max size") {
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+
+ SUBCASE("Default value") {
+ CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
+ }
+
+ SUBCASE("Max encode buffer must be at least 1024 bytes") {
+ ERR_PRINT_OFF;
+ pps->set_encode_buffer_max_size(42);
+ ERR_PRINT_ON;
+
+ CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
+ }
+
+ SUBCASE("Max encode buffer cannot exceed 256 MiB") {
+ ERR_PRINT_OFF;
+ pps->set_encode_buffer_max_size((256 * 1024 * 1024) + 42);
+ ERR_PRINT_ON;
+
+ CHECK_EQ(pps->get_encode_buffer_max_size(), 8 * 1024 * 1024);
+ }
+
+ SUBCASE("Should be next power of two") {
+ pps->set_encode_buffer_max_size(2000);
+
+ CHECK_EQ(pps->get_encode_buffer_max_size(), 2048);
+ }
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer") {
+ String godot_rules = "Godot Rules!!!";
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ spb->put_var(godot_rules);
+ spb->seek(0);
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+
+ Variant value;
+ CHECK_EQ(pps->get_var(value), Error::OK);
+ CHECK_EQ(String(value), godot_rules);
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Read a variant from peer fails") {
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+
+ Variant value;
+ ERR_PRINT_OFF;
+ CHECK_EQ(pps->get_var(value), Error::ERR_UNCONFIGURED);
+ ERR_PRINT_ON;
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer") {
+ String godot_rules = "Godot Rules!!!";
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+
+ CHECK_EQ(pps->put_var(godot_rules), Error::OK);
+
+ spb->seek(0);
+ CHECK_EQ(String(spb->get_var()), godot_rules);
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Put a variant to peer out of memory failure") {
+ String more_than_1mb = String("*").repeat(1024 + 1);
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+ pps->set_encode_buffer_max_size(1024);
+
+ ERR_PRINT_OFF;
+ CHECK_EQ(pps->put_var(more_than_1mb), Error::ERR_OUT_OF_MEMORY);
+ ERR_PRINT_ON;
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer") {
+ String godot_rules = "Godot Rules!!!";
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ // First 4 bytes are the length of the string.
+ CharString cs = godot_rules.ascii();
+ Vector<uint8_t> buffer = { (uint8_t)(cs.length() + 1), 0, 0, 0 };
+ buffer.resize_zeroed(4 + cs.length() + 1);
+ memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
+ spb->set_data_array(buffer);
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+
+ buffer.clear();
+ CHECK_EQ(pps->get_packet_buffer(buffer), Error::OK);
+
+ CHECK_EQ(String(reinterpret_cast<const char *>(buffer.ptr())), godot_rules);
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Get packet buffer from an empty peer") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+
+ Vector<uint8_t> buffer;
+ ERR_PRINT_OFF;
+ CHECK_EQ(pps->get_packet_buffer(buffer), Error::ERR_UNAVAILABLE);
+ ERR_PRINT_ON;
+ CHECK_EQ(buffer.size(), 0);
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer") {
+ String godot_rules = "Godot Rules!!!";
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+
+ CHECK_EQ(pps->put_packet_buffer(godot_rules.to_ascii_buffer()), Error::OK);
+
+ spb->seek(0);
+ CHECK_EQ(spb->get_string(), godot_rules);
+ // First 4 bytes are the length of the string.
+ CharString cs = godot_rules.ascii();
+ Vector<uint8_t> buffer = { (uint8_t)cs.length(), 0, 0, 0 };
+ buffer.resize(4 + cs.length());
+ memcpy(buffer.ptrw() + 4, cs.get_data(), cs.length());
+ CHECK_EQ(spb->get_data_array(), buffer);
+}
+
+TEST_CASE("[PacketPeer][PacketPeerStream] Put packet buffer when is empty") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ Ref<PacketPeerStream> pps;
+ pps.instantiate();
+ pps->set_stream_peer(spb);
+
+ Vector<uint8_t> buffer;
+ CHECK_EQ(pps->put_packet_buffer(buffer), Error::OK);
+
+ CHECK_EQ(spb->get_size(), 0);
+}
+
+} // namespace TestPacketPeer
+
+#endif // TEST_PACKET_PEER_H
diff --git a/tests/core/io/test_stream_peer.h b/tests/core/io/test_stream_peer.h
new file mode 100644
index 0000000000..31bd69edd0
--- /dev/null
+++ b/tests/core/io/test_stream_peer.h
@@ -0,0 +1,289 @@
+/**************************************************************************/
+/* test_stream_peer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_STREAM_PEER_H
+#define TEST_STREAM_PEER_H
+
+#include "core/io/stream_peer.h"
+#include "tests/test_macros.h"
+
+namespace TestStreamPeer {
+
+TEST_CASE("[StreamPeer] Initialization through StreamPeerBuffer") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ CHECK_EQ(spb->is_big_endian_enabled(), false);
+}
+
+TEST_CASE("[StreamPeer] Get and sets through StreamPeerBuffer") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ SUBCASE("A int8_t value") {
+ int8_t value = 42;
+
+ spb->clear();
+ spb->put_8(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_8(), value);
+ }
+
+ SUBCASE("A uint8_t value") {
+ uint8_t value = 42;
+
+ spb->clear();
+ spb->put_u8(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u8(), value);
+ }
+
+ SUBCASE("A int16_t value") {
+ int16_t value = 42;
+
+ spb->clear();
+ spb->put_16(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_16(), value);
+ }
+
+ SUBCASE("A uint16_t value") {
+ uint16_t value = 42;
+
+ spb->clear();
+ spb->put_u16(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u16(), value);
+ }
+
+ SUBCASE("A int32_t value") {
+ int32_t value = 42;
+
+ spb->clear();
+ spb->put_32(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_32(), value);
+ }
+
+ SUBCASE("A uint32_t value") {
+ uint32_t value = 42;
+
+ spb->clear();
+ spb->put_u32(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u32(), value);
+ }
+
+ SUBCASE("A int64_t value") {
+ int64_t value = 42;
+
+ spb->clear();
+ spb->put_64(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_64(), value);
+ }
+
+ SUBCASE("A int64_t value") {
+ uint64_t value = 42;
+
+ spb->clear();
+ spb->put_u64(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u64(), value);
+ }
+
+ SUBCASE("A float value") {
+ float value = 42.0f;
+
+ spb->clear();
+ spb->put_float(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_float(), value);
+ }
+
+ SUBCASE("A double value") {
+ double value = 42.0;
+
+ spb->clear();
+ spb->put_double(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_double(), value);
+ }
+
+ SUBCASE("A string value") {
+ String value = "Hello, World!";
+
+ spb->clear();
+ spb->put_string(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_string(), value);
+ }
+
+ SUBCASE("A utf8 string value") {
+ String value = String::utf8("Hello✩, World✩!");
+
+ spb->clear();
+ spb->put_utf8_string(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_utf8_string(), value);
+ }
+
+ SUBCASE("A variant value") {
+ Array value;
+ value.push_front(42);
+ value.push_front("Hello, World!");
+
+ spb->clear();
+ spb->put_var(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_var(), value);
+ }
+}
+
+TEST_CASE("[StreamPeer] Get and sets big endian through StreamPeerBuffer") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ spb->set_big_endian(true);
+
+ SUBCASE("A int16_t value") {
+ int16_t value = 42;
+
+ spb->clear();
+ spb->put_16(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_16(), value);
+ }
+
+ SUBCASE("A uint16_t value") {
+ uint16_t value = 42;
+
+ spb->clear();
+ spb->put_u16(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u16(), value);
+ }
+
+ SUBCASE("A int32_t value") {
+ int32_t value = 42;
+
+ spb->clear();
+ spb->put_32(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_32(), value);
+ }
+
+ SUBCASE("A uint32_t value") {
+ uint32_t value = 42;
+
+ spb->clear();
+ spb->put_u32(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u32(), value);
+ }
+
+ SUBCASE("A int64_t value") {
+ int64_t value = 42;
+
+ spb->clear();
+ spb->put_64(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_64(), value);
+ }
+
+ SUBCASE("A int64_t value") {
+ uint64_t value = 42;
+
+ spb->clear();
+ spb->put_u64(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_u64(), value);
+ }
+
+ SUBCASE("A float value") {
+ float value = 42.0f;
+
+ spb->clear();
+ spb->put_float(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_float(), value);
+ }
+
+ SUBCASE("A double value") {
+ double value = 42.0;
+
+ spb->clear();
+ spb->put_double(value);
+ spb->seek(0);
+
+ CHECK_EQ(spb->get_double(), value);
+ }
+}
+
+TEST_CASE("[StreamPeer] Get string when there is no string") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ ERR_PRINT_OFF;
+ CHECK_EQ(spb->get_string(), "");
+ ERR_PRINT_ON;
+}
+
+TEST_CASE("[StreamPeer] Get UTF8 string when there is no string") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+
+ ERR_PRINT_OFF;
+ CHECK_EQ(spb->get_utf8_string(), "");
+ ERR_PRINT_ON;
+}
+
+} // namespace TestStreamPeer
+
+#endif // TEST_STREAM_PEER_H
diff --git a/tests/core/io/test_stream_peer_buffer.h b/tests/core/io/test_stream_peer_buffer.h
new file mode 100644
index 0000000000..8ba9c0a72c
--- /dev/null
+++ b/tests/core/io/test_stream_peer_buffer.h
@@ -0,0 +1,185 @@
+/**************************************************************************/
+/* test_stream_peer_buffer.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_STREAM_PEER_BUFFER_H
+#define TEST_STREAM_PEER_BUFFER_H
+
+#include "core/io/stream_peer.h"
+#include "tests/test_macros.h"
+
+namespace TestStreamPeerBuffer {
+
+TEST_CASE("[StreamPeerBuffer] Initialization") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ CHECK_EQ(spb->get_size(), 0);
+ CHECK_EQ(spb->get_position(), 0);
+ CHECK_EQ(spb->get_available_bytes(), 0);
+}
+
+TEST_CASE("[StreamPeerBuffer] Seek") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ uint8_t first = 5;
+ uint8_t second = 7;
+ uint8_t third = 11;
+
+ spb->put_u8(first);
+ spb->put_u8(second);
+ spb->put_u8(third);
+
+ spb->seek(0);
+ CHECK_EQ(spb->get_u8(), first);
+ CHECK_EQ(spb->get_u8(), second);
+ CHECK_EQ(spb->get_u8(), third);
+
+ spb->seek(1);
+ CHECK_EQ(spb->get_position(), 1);
+ CHECK_EQ(spb->get_u8(), second);
+
+ spb->seek(1);
+ ERR_PRINT_OFF;
+ spb->seek(-1);
+ ERR_PRINT_ON;
+ CHECK_EQ(spb->get_position(), 1);
+ ERR_PRINT_OFF;
+ spb->seek(5);
+ ERR_PRINT_ON;
+ CHECK_EQ(spb->get_position(), 1);
+}
+
+TEST_CASE("[StreamPeerBuffer] Resize") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ CHECK_EQ(spb->get_size(), 0);
+ CHECK_EQ(spb->get_position(), 0);
+ CHECK_EQ(spb->get_available_bytes(), 0);
+
+ spb->resize(42);
+ CHECK_EQ(spb->get_size(), 42);
+ CHECK_EQ(spb->get_position(), 0);
+ CHECK_EQ(spb->get_available_bytes(), 42);
+
+ spb->seek(21);
+ CHECK_EQ(spb->get_size(), 42);
+ CHECK_EQ(spb->get_position(), 21);
+ CHECK_EQ(spb->get_available_bytes(), 21);
+}
+
+TEST_CASE("[StreamPeerBuffer] Get underlying data array") {
+ uint8_t first = 5;
+ uint8_t second = 7;
+ uint8_t third = 11;
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ spb->put_u8(first);
+ spb->put_u8(second);
+ spb->put_u8(third);
+
+ Vector<uint8_t> data_array = spb->get_data_array();
+
+ CHECK_EQ(data_array[0], first);
+ CHECK_EQ(data_array[1], second);
+ CHECK_EQ(data_array[2], third);
+}
+
+TEST_CASE("[StreamPeerBuffer] Set underlying data array") {
+ uint8_t first = 5;
+ uint8_t second = 7;
+ uint8_t third = 11;
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ spb->put_u8(1);
+ spb->put_u8(2);
+ spb->put_u8(3);
+
+ Vector<uint8_t> new_data_array;
+ new_data_array.push_back(first);
+ new_data_array.push_back(second);
+ new_data_array.push_back(third);
+
+ spb->set_data_array(new_data_array);
+
+ CHECK_EQ(spb->get_u8(), first);
+ CHECK_EQ(spb->get_u8(), second);
+ CHECK_EQ(spb->get_u8(), third);
+}
+
+TEST_CASE("[StreamPeerBuffer] Duplicate") {
+ uint8_t first = 5;
+ uint8_t second = 7;
+ uint8_t third = 11;
+
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ spb->put_u8(first);
+ spb->put_u8(second);
+ spb->put_u8(third);
+
+ Ref<StreamPeerBuffer> spb2 = spb->duplicate();
+
+ CHECK_EQ(spb2->get_u8(), first);
+ CHECK_EQ(spb2->get_u8(), second);
+ CHECK_EQ(spb2->get_u8(), third);
+}
+
+TEST_CASE("[StreamPeerBuffer] Put data with size equal to zero does nothing") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ uint8_t data = 42;
+
+ Error error = spb->put_data((const uint8_t *)&data, 0);
+
+ CHECK_EQ(error, OK);
+ CHECK_EQ(spb->get_size(), 0);
+ CHECK_EQ(spb->get_position(), 0);
+ CHECK_EQ(spb->get_available_bytes(), 0);
+}
+
+TEST_CASE("[StreamPeerBuffer] Get data with invalid size returns an error") {
+ Ref<StreamPeerBuffer> spb;
+ spb.instantiate();
+ uint8_t data = 42;
+ spb->put_u8(data);
+ spb->seek(0);
+
+ uint8_t data_out = 0;
+ Error error = spb->get_data(&data_out, 3);
+
+ CHECK_EQ(error, ERR_INVALID_PARAMETER);
+ CHECK_EQ(spb->get_size(), 1);
+ CHECK_EQ(spb->get_position(), 1);
+}
+
+} // namespace TestStreamPeerBuffer
+
+#endif // TEST_STREAM_PEER_BUFFER_H
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 301771a3de..8559737e74 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -1988,6 +1988,61 @@ TEST_CASE("[String] Variant ptr indexed set") {
CHECK_EQ(s, String("azcd"));
}
+TEST_CASE("[String][URL] Parse URL") {
+#define CHECK_URL(m_url_to_parse, m_expected_schema, m_expected_host, m_expected_port, m_expected_path, m_expected_fragment, m_expected_error) \
+ if (true) { \
+ int port; \
+ String url(m_url_to_parse), schema, host, path, fragment; \
+ \
+ CHECK_EQ(url.parse_url(schema, host, port, path, fragment), m_expected_error); \
+ CHECK_EQ(schema, m_expected_schema); \
+ CHECK_EQ(host, m_expected_host); \
+ CHECK_EQ(path, m_expected_path); \
+ CHECK_EQ(fragment, m_expected_fragment); \
+ CHECK_EQ(port, m_expected_port); \
+ } else \
+ ((void)0)
+
+ // All elements.
+ CHECK_URL("https://www.example.com:8080/path/to/file.html#fragment", "https://", "www.example.com", 8080, "/path/to/file.html", "fragment", Error::OK);
+
+ // Valid URLs.
+ CHECK_URL("https://godotengine.org", "https://", "godotengine.org", 0, "", "", Error::OK);
+ CHECK_URL("https://godotengine.org/", "https://", "godotengine.org", 0, "/", "", Error::OK);
+ CHECK_URL("godotengine.org/", "", "godotengine.org", 0, "/", "", Error::OK);
+ CHECK_URL("HTTPS://godotengine.org/", "https://", "godotengine.org", 0, "/", "", Error::OK);
+ CHECK_URL("https://GODOTENGINE.ORG/", "https://", "godotengine.org", 0, "/", "", Error::OK);
+ CHECK_URL("http://godotengine.org", "http://", "godotengine.org", 0, "", "", Error::OK);
+ CHECK_URL("https://godotengine.org:8080", "https://", "godotengine.org", 8080, "", "", Error::OK);
+ CHECK_URL("https://godotengine.org/blog", "https://", "godotengine.org", 0, "/blog", "", Error::OK);
+ CHECK_URL("https://godotengine.org/blog/", "https://", "godotengine.org", 0, "/blog/", "", Error::OK);
+ CHECK_URL("https://docs.godotengine.org/en/stable", "https://", "docs.godotengine.org", 0, "/en/stable", "", Error::OK);
+ CHECK_URL("https://docs.godotengine.org/en/stable/", "https://", "docs.godotengine.org", 0, "/en/stable/", "", Error::OK);
+ CHECK_URL("https://me:secret@godotengine.org", "https://", "godotengine.org", 0, "", "", Error::OK);
+ CHECK_URL("https://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/ipv6", "https://", "fedc:ba98:7654:3210:fedc:ba98:7654:3210", 0, "/ipv6", "", Error::OK);
+
+ // Scheme vs Fragment.
+ CHECK_URL("google.com/#goto=http://redirect_url/", "", "google.com", 0, "/", "goto=http://redirect_url/", Error::OK);
+
+ // Invalid URLs.
+
+ // Invalid Scheme.
+ CHECK_URL("https_://godotengine.org", "", "https_", 0, "//godotengine.org", "", Error::ERR_INVALID_PARAMETER);
+
+ // Multiple ports.
+ CHECK_URL("https://godotengine.org:8080:433", "https://", "", 0, "", "", Error::ERR_INVALID_PARAMETER);
+ // Missing ] on literal IPv6.
+ CHECK_URL("https://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/ipv6", "https://", "", 0, "/ipv6", "", Error::ERR_INVALID_PARAMETER);
+ // Missing host.
+ CHECK_URL("https:///blog", "https://", "", 0, "/blog", "", Error::ERR_INVALID_PARAMETER);
+ // Invalid ports.
+ CHECK_URL("https://godotengine.org:notaport", "https://", "godotengine.org", 0, "", "", Error::ERR_INVALID_PARAMETER);
+ CHECK_URL("https://godotengine.org:-8080", "https://", "godotengine.org", -8080, "", "", Error::ERR_INVALID_PARAMETER);
+ CHECK_URL("https://godotengine.org:88888", "https://", "godotengine.org", 88888, "", "", Error::ERR_INVALID_PARAMETER);
+
+#undef CHECK_URL
+}
+
TEST_CASE("[Stress][String] Empty via ' == String()'") {
for (int i = 0; i < 100000; ++i) {
String str = "Hello World!";
diff --git a/modules/squish/image_decompress_squish.cpp b/tests/scene/test_physics_material.h
index 3841ba8db1..a078166f42 100644
--- a/modules/squish/image_decompress_squish.cpp
+++ b/tests/scene/test_physics_material.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* image_decompress_squish.cpp */
+/* test_physics_material.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,69 +28,80 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "image_decompress_squish.h"
+#ifndef TEST_PHYSICS_MATERIAL_H
+#define TEST_PHYSICS_MATERIAL_H
-#include <squish.h>
+#include "scene/resources/physics_material.h"
+#include "tests/test_macros.h"
-void image_decompress_squish(Image *p_image) {
- int w = p_image->get_width();
- int h = p_image->get_height();
+namespace TestPhysics_material {
- Image::Format source_format = p_image->get_format();
- Image::Format target_format = Image::FORMAT_RGBA8;
+TEST_CASE("[Physics_material] Defaults") {
+ Ref<PhysicsMaterial> physics_material;
+ physics_material.instantiate();
- Vector<uint8_t> data;
- int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
- int mm_count = p_image->get_mipmap_count();
- data.resize(target_size);
+ CHECK(physics_material->get_friction() == 1.);
+ CHECK(physics_material->is_rough() == false);
+ CHECK(physics_material->get_bounce() == 0.);
+ CHECK(physics_material->is_absorbent() == false);
+}
- const uint8_t *rb = p_image->get_data().ptr();
- uint8_t *wb = data.ptrw();
+TEST_CASE("[Physics_material] Friction") {
+ Ref<PhysicsMaterial> physics_material;
+ physics_material.instantiate();
- int squish_flags = 0;
+ real_t friction = 0.314;
+ physics_material->set_friction(friction);
+ CHECK(physics_material->get_friction() == friction);
+}
- switch (source_format) {
- case Image::FORMAT_DXT1:
- squish_flags = squish::kDxt1;
- break;
+TEST_CASE("[Physics_material] Rough") {
+ Ref<PhysicsMaterial> physics_material;
+ physics_material.instantiate();
- case Image::FORMAT_DXT3:
- squish_flags = squish::kDxt3;
- break;
+ bool rough = true;
+ physics_material->set_rough(rough);
+ CHECK(physics_material->is_rough() == rough);
- case Image::FORMAT_DXT5:
- case Image::FORMAT_DXT5_RA_AS_RG:
- squish_flags = squish::kDxt5;
- break;
+ real_t friction = 0.314;
+ physics_material->set_friction(friction);
+ CHECK(physics_material->computed_friction() == -friction);
- case Image::FORMAT_RGTC_R:
- squish_flags = squish::kBc4;
- break;
+ rough = false;
+ physics_material->set_rough(rough);
+ CHECK(physics_material->is_rough() == rough);
- case Image::FORMAT_RGTC_RG:
- squish_flags = squish::kBc5;
- break;
+ CHECK(physics_material->computed_friction() == friction);
+}
- default:
- ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + ".");
- break;
- }
+TEST_CASE("[Physics_material] Bounce") {
+ Ref<PhysicsMaterial> physics_material;
+ physics_material.instantiate();
- for (int i = 0; i <= mm_count; i++) {
- int64_t src_ofs = 0, mipmap_size = 0;
- int mipmap_w = 0, mipmap_h = 0;
- p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h);
+ real_t bounce = 0.271;
+ physics_material->set_bounce(bounce);
+ CHECK(physics_material->get_bounce() == bounce);
+}
- int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i);
- squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags);
+TEST_CASE("[Physics_material] Absorbent") {
+ Ref<PhysicsMaterial> physics_material;
+ physics_material.instantiate();
- w >>= 1;
- h >>= 1;
- }
+ bool absorbent = true;
+ physics_material->set_absorbent(absorbent);
+ CHECK(physics_material->is_absorbent() == absorbent);
- p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
+ real_t bounce = 0.271;
+ physics_material->set_bounce(bounce);
+ CHECK(physics_material->computed_bounce() == -bounce);
- if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
- p_image->convert_ra_rgba8_to_rg();
- }
+ absorbent = false;
+ physics_material->set_absorbent(absorbent);
+ CHECK(physics_material->is_absorbent() == absorbent);
+
+ CHECK(physics_material->computed_bounce() == bounce);
}
+
+} // namespace TestPhysics_material
+
+#endif // TEST_PHYSICS_MATERIAL_H
diff --git a/tests/scene/test_sky.h b/tests/scene/test_sky.h
new file mode 100644
index 0000000000..812ea9b5ad
--- /dev/null
+++ b/tests/scene/test_sky.h
@@ -0,0 +1,141 @@
+/**************************************************************************/
+/* test_sky.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_SKY_H
+#define TEST_SKY_H
+
+#include "scene/resources/sky.h"
+
+#include "tests/test_macros.h"
+
+namespace TestSky {
+
+TEST_CASE("[SceneTree][Sky] Constructor") {
+ Sky *test_sky = memnew(Sky);
+
+ CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC);
+ CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
+ CHECK(test_sky->get_material().is_null());
+ memdelete(test_sky);
+}
+
+TEST_CASE("[SceneTree][Sky] Radiance size setter and getter") {
+ Sky *test_sky = memnew(Sky);
+
+ // Check default.
+ CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_256);
+
+ test_sky->set_radiance_size(Sky::RADIANCE_SIZE_1024);
+ CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024);
+
+ ERR_PRINT_OFF;
+ // Check setting invalid radiance size.
+ test_sky->set_radiance_size(Sky::RADIANCE_SIZE_MAX);
+ ERR_PRINT_ON;
+
+ CHECK(test_sky->get_radiance_size() == Sky::RADIANCE_SIZE_1024);
+
+ memdelete(test_sky);
+}
+
+TEST_CASE("[SceneTree][Sky] Process mode setter and getter") {
+ Sky *test_sky = memnew(Sky);
+
+ // Check default.
+ CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_AUTOMATIC);
+
+ test_sky->set_process_mode(Sky::PROCESS_MODE_INCREMENTAL);
+ CHECK(test_sky->get_process_mode() == Sky::PROCESS_MODE_INCREMENTAL);
+
+ memdelete(test_sky);
+}
+
+TEST_CASE("[SceneTree][Sky] Material setter and getter") {
+ Sky *test_sky = memnew(Sky);
+ Ref<Material> material = memnew(Material);
+
+ SUBCASE("Material passed to the class should remain the same") {
+ test_sky->set_material(material);
+ CHECK(test_sky->get_material() == material);
+ }
+ SUBCASE("Material passed many times to the class should remain the same") {
+ test_sky->set_material(material);
+ test_sky->set_material(material);
+ test_sky->set_material(material);
+ CHECK(test_sky->get_material() == material);
+ }
+ SUBCASE("Material rewrite testing") {
+ Ref<Material> material1 = memnew(Material);
+ Ref<Material> material2 = memnew(Material);
+
+ test_sky->set_material(material1);
+ test_sky->set_material(material2);
+ CHECK_MESSAGE(test_sky->get_material() != material1,
+ "After rewrite, second material should be in class.");
+ CHECK_MESSAGE(test_sky->get_material() == material2,
+ "After rewrite, second material should be in class.");
+ }
+
+ SUBCASE("Assign same material to two skys") {
+ Sky *sky2 = memnew(Sky);
+
+ test_sky->set_material(material);
+ sky2->set_material(material);
+ CHECK_MESSAGE(test_sky->get_material() == sky2->get_material(),
+ "Both skys should have the same material.");
+ memdelete(sky2);
+ }
+
+ SUBCASE("Swapping materials between two skys") {
+ Sky *sky2 = memnew(Sky);
+ Ref<Material> material1 = memnew(Material);
+ Ref<Material> material2 = memnew(Material);
+
+ test_sky->set_material(material1);
+ sky2->set_material(material2);
+ CHECK(test_sky->get_material() == material1);
+ CHECK(sky2->get_material() == material2);
+
+ // Do the swap.
+ Ref<Material> temp = test_sky->get_material();
+ test_sky->set_material(sky2->get_material());
+ sky2->set_material(temp);
+
+ CHECK(test_sky->get_material() == material2);
+ CHECK(sky2->get_material() == material1);
+ memdelete(sky2);
+ }
+
+ memdelete(test_sky);
+}
+
+} // namespace TestSky
+
+#endif // TEST_SKY_H
diff --git a/tests/scene/test_tab_bar.h b/tests/scene/test_tab_bar.h
new file mode 100644
index 0000000000..147513bb69
--- /dev/null
+++ b/tests/scene/test_tab_bar.h
@@ -0,0 +1,864 @@
+/**************************************************************************/
+/* test_tab_bar.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_TAB_BAR_H
+#define TEST_TAB_BAR_H
+
+#include "scene/gui/tab_bar.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestTabBar {
+static inline Array build_array() {
+ return Array();
+}
+template <typename... Targs>
+static inline Array build_array(Variant item, Targs... Fargs) {
+ Array a = build_array(Fargs...);
+ a.push_front(item);
+ return a;
+}
+
+TEST_CASE("[SceneTree][TabBar] tab operations") {
+ TabBar *tab_bar = memnew(TabBar);
+ SceneTree::get_singleton()->get_root()->add_child(tab_bar);
+ tab_bar->set_clip_tabs(false);
+ MessageQueue::get_singleton()->flush();
+
+ SIGNAL_WATCH(tab_bar, "tab_selected");
+ SIGNAL_WATCH(tab_bar, "tab_changed");
+
+ SUBCASE("[TabBar] no tabs") {
+ CHECK(tab_bar->get_tab_count() == 0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ }
+
+ SUBCASE("[TabBar] add tabs") {
+ tab_bar->add_tab("tab0");
+ CHECK(tab_bar->get_tab_count() == 1);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+
+ tab_bar->add_tab("tab1");
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_bar->add_tab("tab2");
+ CHECK(tab_bar->get_tab_count() == 3);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ CHECK(tab_bar->get_tab_title(0) == "tab0");
+ CHECK(tab_bar->get_tab_tooltip(0) == "");
+ CHECK(tab_bar->get_tab_text_direction(0) == Control::TEXT_DIRECTION_INHERITED);
+ CHECK_FALSE(tab_bar->is_tab_disabled(0));
+ CHECK_FALSE(tab_bar->is_tab_hidden(0));
+
+ CHECK(tab_bar->get_tab_title(1) == "tab1");
+ CHECK(tab_bar->get_tab_tooltip(1) == "");
+ CHECK(tab_bar->get_tab_text_direction(1) == Control::TEXT_DIRECTION_INHERITED);
+ CHECK_FALSE(tab_bar->is_tab_disabled(1));
+ CHECK_FALSE(tab_bar->is_tab_hidden(1));
+
+ CHECK(tab_bar->get_tab_title(2) == "tab2");
+ CHECK(tab_bar->get_tab_tooltip(2) == "");
+ CHECK(tab_bar->get_tab_text_direction(2) == Control::TEXT_DIRECTION_INHERITED);
+ CHECK_FALSE(tab_bar->is_tab_disabled(2));
+ CHECK_FALSE(tab_bar->is_tab_hidden(2));
+ }
+
+ SUBCASE("[TabBar] set tab count") {
+ // Adds multiple tabs at once.
+ tab_bar->set_tab_count(3);
+ CHECK(tab_bar->get_tab_count() == 3);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ CHECK(tab_bar->get_tab_title(0) == "");
+ CHECK(tab_bar->get_tab_tooltip(0) == "");
+ CHECK(tab_bar->get_tab_text_direction(0) == Control::TEXT_DIRECTION_INHERITED);
+ CHECK_FALSE(tab_bar->is_tab_disabled(0));
+ CHECK_FALSE(tab_bar->is_tab_hidden(0));
+
+ CHECK(tab_bar->get_tab_title(1) == "");
+ CHECK(tab_bar->get_tab_tooltip(1) == "");
+ CHECK(tab_bar->get_tab_text_direction(1) == Control::TEXT_DIRECTION_INHERITED);
+ CHECK_FALSE(tab_bar->is_tab_disabled(1));
+ CHECK_FALSE(tab_bar->is_tab_hidden(1));
+
+ CHECK(tab_bar->get_tab_title(2) == "");
+ CHECK(tab_bar->get_tab_tooltip(2) == "");
+ CHECK(tab_bar->get_tab_text_direction(2) == Control::TEXT_DIRECTION_INHERITED);
+ CHECK_FALSE(tab_bar->is_tab_disabled(2));
+ CHECK_FALSE(tab_bar->is_tab_hidden(2));
+
+ // Setting to less tabs than there are removes from the end.
+ tab_bar->set_tab_title(0, "tab0");
+ tab_bar->set_tab_title(1, "tab1");
+ tab_bar->set_tab_title(2, "tab2");
+
+ tab_bar->set_tab_count(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ CHECK(tab_bar->get_tab_title(0) == "tab0");
+ CHECK(tab_bar->get_tab_title(1) == "tab1");
+
+ // Remove all tabs.
+ tab_bar->set_tab_count(0);
+ CHECK(tab_bar->get_tab_count() == 0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ }
+
+ SUBCASE("[TabBar] clear tabs") {
+ CHECK(tab_bar->get_tab_count() == 0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+
+ tab_bar->set_tab_count(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ tab_bar->clear_tabs();
+ CHECK(tab_bar->get_tab_count() == 0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SUBCASE("[TabBar] remove tabs") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_tab_count() == 3);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Remove first tab.
+ tab_bar->remove_tab(0);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_tab_title(0) == "tab1");
+ CHECK(tab_bar->get_tab_title(1) == "tab2");
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Remove last tab.
+ tab_bar->remove_tab(1);
+ CHECK(tab_bar->get_tab_count() == 1);
+ CHECK(tab_bar->get_tab_title(0) == "tab1");
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Remove only tab.
+ tab_bar->remove_tab(0);
+ CHECK(tab_bar->get_tab_count() == 0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
+
+ // Remove current tab when there are other tabs.
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ tab_bar->set_current_tab(1);
+ tab_bar->set_current_tab(2);
+ CHECK(tab_bar->get_tab_count() == 3);
+ CHECK(tab_bar->get_current_tab() == 2);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ tab_bar->remove_tab(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ }
+
+ SUBCASE("[TabBar] move tabs") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Don't move if index is the same.
+ tab_bar->move_tab(0, 0);
+ CHECK(tab_bar->get_tab_title(0) == "tab0");
+ CHECK(tab_bar->get_tab_title(1) == "tab1");
+ CHECK(tab_bar->get_tab_title(2) == "tab2");
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Move the first tab to the end.
+ tab_bar->move_tab(0, 2);
+ CHECK(tab_bar->get_tab_title(0) == "tab1");
+ CHECK(tab_bar->get_tab_title(1) == "tab2");
+ CHECK(tab_bar->get_tab_title(2) == "tab0");
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 2);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Move the second tab to the front.
+ tab_bar->move_tab(1, 0);
+ CHECK(tab_bar->get_tab_title(0) == "tab2");
+ CHECK(tab_bar->get_tab_title(1) == "tab1");
+ CHECK(tab_bar->get_tab_title(2) == "tab0");
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 2);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SUBCASE("[TabBar] set current tab") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+
+ // Set the current tab.
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+
+ // Set to same tab.
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Out of bounds.
+ ERR_PRINT_OFF;
+ tab_bar->set_current_tab(-5);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_bar->set_current_tab(5);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("[TabBar] deselection enabled") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Setting deselect enabled doesn't change current tab.
+ tab_bar->set_deselect_enabled(true);
+ CHECK(tab_bar->get_deselect_enabled());
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Can deselect all tabs by setting current to -1.
+ tab_bar->set_current_tab(-1);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(-1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
+
+ // Adding a tab will still set the current tab to 0.
+ tab_bar->clear_tabs();
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ CHECK(tab_bar->get_tab_count() == 3);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+
+ tab_bar->set_current_tab(-1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Disabling while at -1 will select the first available tab.
+ tab_bar->set_deselect_enabled(false);
+ CHECK_FALSE(tab_bar->get_deselect_enabled());
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+
+ // Cannot set to -1 if disabled.
+ ERR_PRINT_OFF;
+ tab_bar->set_current_tab(-1);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ ERR_PRINT_ON;
+
+ // Disabling while at -1 skips any disabled or hidden tabs.
+ tab_bar->set_deselect_enabled(true);
+ tab_bar->set_tab_disabled(0, true);
+ tab_bar->set_tab_hidden(1, true);
+ tab_bar->set_current_tab(-1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+ tab_bar->set_deselect_enabled(false);
+ CHECK(tab_bar->get_current_tab() == 2);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(2)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(2)));
+ }
+
+ SUBCASE("[TabBar] hidden tabs") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ CHECK_FALSE(tab_bar->is_tab_hidden(1));
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ MessageQueue::get_singleton()->flush();
+ Vector<Rect2> tab_rects = {
+ tab_bar->get_tab_rect(0),
+ tab_bar->get_tab_rect(1),
+ tab_bar->get_tab_rect(2)
+ };
+
+ // Hiding a tab does not affect current tab.
+ tab_bar->set_tab_hidden(1, true);
+ CHECK(tab_bar->is_tab_hidden(1));
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // The tabs after are moved over.
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_bar->get_tab_rect(0) == tab_rects[0]);
+ CHECK(tab_bar->get_tab_rect(2) == tab_rects[1]);
+
+ // Unhiding a tab does not affect current tab.
+ tab_bar->set_tab_hidden(1, false);
+ CHECK_FALSE(tab_bar->is_tab_hidden(1));
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // The tabs are back where they were.
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_bar->get_tab_rect(0) == tab_rects[0]);
+ CHECK(tab_bar->get_tab_rect(1) == tab_rects[1]);
+ CHECK(tab_bar->get_tab_rect(2) == tab_rects[2]);
+ }
+
+ SUBCASE("[TabBar] disabled tabs") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ CHECK_FALSE(tab_bar->is_tab_disabled(1));
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ CHECK_FALSE(tab_bar->is_tab_hidden(1));
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Disabling a tab does not affect current tab.
+ tab_bar->set_tab_disabled(1, true);
+ CHECK(tab_bar->is_tab_disabled(1));
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Enabling a tab does not affect current tab.
+ tab_bar->set_tab_disabled(1, false);
+ CHECK_FALSE(tab_bar->is_tab_disabled(1));
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SUBCASE("[TabBar] select next available") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ tab_bar->add_tab("tab3");
+ tab_bar->add_tab("tab4");
+ tab_bar->set_tab_disabled(2, true);
+ tab_bar->set_tab_hidden(3, true);
+ tab_bar->set_current_tab(0);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Selects the next tab.
+ CHECK(tab_bar->select_next_available());
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+
+ // Skips over disabled and hidden tabs.
+ CHECK(tab_bar->select_next_available());
+ CHECK(tab_bar->get_current_tab() == 4);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(4)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(4)));
+
+ // Does not wrap around.
+ CHECK_FALSE(tab_bar->select_next_available());
+ CHECK(tab_bar->get_current_tab() == 4);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Fails if there is only one valid tab.
+ tab_bar->remove_tab(0);
+ tab_bar->remove_tab(3);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ CHECK_FALSE(tab_bar->select_next_available());
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Fails if there are no valid tabs.
+ tab_bar->remove_tab(0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ CHECK_FALSE(tab_bar->select_next_available());
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Fails if there are no tabs.
+ tab_bar->clear_tabs();
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ CHECK_FALSE(tab_bar->select_next_available());
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SUBCASE("[TabBar] select previous available") {
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1");
+ tab_bar->add_tab("tab2");
+ tab_bar->add_tab("tab3");
+ tab_bar->add_tab("tab4");
+ tab_bar->set_tab_disabled(1, true);
+ tab_bar->set_tab_hidden(2, true);
+ tab_bar->set_current_tab(4);
+ CHECK(tab_bar->get_current_tab() == 4);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Selects the previous tab.
+ CHECK(tab_bar->select_previous_available());
+ CHECK(tab_bar->get_current_tab() == 3);
+ CHECK(tab_bar->get_previous_tab() == 4);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(3)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(3)));
+
+ // Skips over disabled and hidden tabs.
+ CHECK(tab_bar->select_previous_available());
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 3);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+
+ // Does not wrap around.
+ CHECK_FALSE(tab_bar->select_previous_available());
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 3);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Fails if there is only one valid tab.
+ tab_bar->remove_tab(4);
+ tab_bar->remove_tab(3);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 2);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ CHECK_FALSE(tab_bar->select_previous_available());
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == 2);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Fails if there are no valid tabs.
+ tab_bar->remove_tab(0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ CHECK_FALSE(tab_bar->select_previous_available());
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Fails if there are no tabs.
+ tab_bar->clear_tabs();
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ CHECK_FALSE(tab_bar->select_previous_available());
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SIGNAL_UNWATCH(tab_bar, "tab_selected");
+ SIGNAL_UNWATCH(tab_bar, "tab_changed");
+
+ memdelete(tab_bar);
+}
+
+TEST_CASE("[SceneTree][TabBar] initialization") {
+ TabBar *tab_bar = memnew(TabBar);
+
+ SIGNAL_WATCH(tab_bar, "tab_selected");
+ SIGNAL_WATCH(tab_bar, "tab_changed");
+
+ SUBCASE("[TabBar] current tab can be set before tabs are set") {
+ // This queues the current tab to update on when tabs are set.
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_bar->set_tab_count(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+
+ // Does not work again.
+ ERR_PRINT_OFF;
+ tab_bar->set_current_tab(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_bar->set_tab_count(3);
+ CHECK(tab_bar->get_tab_count() == 3);
+ CHECK(tab_bar->get_current_tab() == 1);
+ CHECK(tab_bar->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("[TabBar] setting tabs works normally if no current tab was set") {
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+
+ tab_bar->set_tab_count(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SUBCASE("[TabBar] cannot set current tab to an invalid value before tabs are set") {
+ tab_bar->set_current_tab(100);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // This will print an error message as if `set_current_tab` was called after.
+ ERR_PRINT_OFF;
+ tab_bar->set_tab_count(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("[TabBar] setting the current tab before tabs only works when out of tree") {
+ tab_bar->set_current_tab(1);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ SceneTree::get_singleton()->get_root()->add_child(tab_bar);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_bar->get_tab_count() == 0);
+ CHECK(tab_bar->get_current_tab() == -1);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Works normally.
+ tab_bar->set_tab_count(2);
+ CHECK(tab_bar->get_tab_count() == 2);
+ CHECK(tab_bar->get_current_tab() == 0);
+ CHECK(tab_bar->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SIGNAL_UNWATCH(tab_bar, "tab_selected");
+ SIGNAL_UNWATCH(tab_bar, "tab_changed");
+
+ memdelete(tab_bar);
+}
+
+TEST_CASE("[SceneTree][TabBar] layout and offset") {
+ TabBar *tab_bar = memnew(TabBar);
+ SceneTree::get_singleton()->get_root()->add_child(tab_bar);
+
+ tab_bar->set_clip_tabs(false);
+ tab_bar->add_tab("tab0");
+ tab_bar->add_tab("tab1 ");
+ tab_bar->add_tab("tab2 ");
+ MessageQueue::get_singleton()->flush();
+ Size2 all_tabs_size = tab_bar->get_size();
+
+ Vector<Rect2> tab_rects = {
+ tab_bar->get_tab_rect(0),
+ tab_bar->get_tab_rect(1),
+ tab_bar->get_tab_rect(2)
+ };
+
+ SUBCASE("[TabBar] tabs are arranged next to each other") {
+ // Horizontal positions are next to each other.
+ CHECK(tab_rects[0].position.x == 0);
+ CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
+ CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
+
+ // Fills the entire width.
+ CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x);
+
+ // Horizontal sizes are positive.
+ CHECK(tab_rects[0].size.x > 0);
+ CHECK(tab_rects[1].size.x > 0);
+ CHECK(tab_rects[2].size.x > 0);
+
+ // Vertical positions are at 0.
+ CHECK(tab_rects[0].position.y == 0);
+ CHECK(tab_rects[1].position.y == 0);
+ CHECK(tab_rects[2].position.y == 0);
+
+ // Vertical sizes are the same.
+ CHECK(tab_rects[0].size.y == tab_rects[1].size.y);
+ CHECK(tab_rects[1].size.y == tab_rects[2].size.y);
+ }
+
+ SUBCASE("[TabBar] tab alignment") {
+ // Add extra space so the alignment can be seen.
+ tab_bar->set_size(Size2(all_tabs_size.x + 100, all_tabs_size.y));
+
+ // Left alignment.
+ tab_bar->set_tab_alignment(TabBar::ALIGNMENT_LEFT);
+ MessageQueue::get_singleton()->flush();
+ tab_rects = {
+ tab_bar->get_tab_rect(0),
+ tab_bar->get_tab_rect(1),
+ tab_bar->get_tab_rect(2)
+ };
+ CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_LEFT);
+ CHECK(tab_rects[0].position.x == 0);
+ CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
+ CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
+
+ // Right alignment.
+ tab_bar->set_tab_alignment(TabBar::ALIGNMENT_RIGHT);
+ MessageQueue::get_singleton()->flush();
+ tab_rects = {
+ tab_bar->get_tab_rect(0),
+ tab_bar->get_tab_rect(1),
+ tab_bar->get_tab_rect(2)
+ };
+ CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_RIGHT);
+ CHECK(tab_rects[2].position.x == tab_bar->get_size().x - tab_rects[2].size.x);
+ CHECK(tab_rects[1].position.x == tab_rects[2].position.x - tab_rects[1].size.x);
+ CHECK(tab_rects[0].position.x == tab_rects[1].position.x - tab_rects[0].size.x);
+
+ // Center alignment.
+ tab_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER);
+ MessageQueue::get_singleton()->flush();
+ tab_rects = {
+ tab_bar->get_tab_rect(0),
+ tab_bar->get_tab_rect(1),
+ tab_bar->get_tab_rect(2)
+ };
+ CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_CENTER);
+ float center_pos = tab_bar->get_size().x / 2;
+ CHECK(tab_rects[0].position.x == center_pos - all_tabs_size.x / 2);
+ CHECK(tab_rects[1].position.x == tab_rects[0].position.x + tab_rects[0].size.x);
+ CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
+ }
+
+ SUBCASE("[TabBar] clip tabs") {
+ // Clip tabs disabled means all tabs are visible and the minimum size holds all of them.
+ tab_bar->set_clip_tabs(false);
+ CHECK_FALSE(tab_bar->get_clip_tabs());
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_bar->get_tab_offset() == 0);
+ CHECK(tab_bar->get_minimum_size() == tab_bar->get_size());
+ CHECK(tab_bar->get_size().x == tab_rects[0].size.x + tab_rects[1].size.x + tab_rects[2].size.x);
+ CHECK(tab_bar->get_size().y == MAX(tab_rects[0].size.y, MAX(tab_rects[1].size.y, tab_rects[2].size.y)));
+
+ tab_bar->set_clip_tabs(true);
+ CHECK(tab_bar->get_clip_tabs());
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_bar->get_tab_offset() == 0);
+
+ // Horizontal size and minimum size get set to 0.
+ CHECK(tab_bar->get_minimum_size().x == 0);
+ CHECK(tab_bar->get_minimum_size().y == all_tabs_size.y);
+ CHECK(tab_bar->get_size().x == 0);
+ CHECK(tab_bar->get_size().y == all_tabs_size.y);
+ }
+
+ SUBCASE("[TabBar] ensure tab visible") {
+ tab_bar->set_scroll_to_selected(false);
+ tab_bar->set_clip_tabs(true);
+
+ // Resize tab bar to only be able to fit 2 tabs.
+ const float offset_button_size = tab_bar->get_theme_icon("decrement_icon")->get_width() + tab_bar->get_theme_icon("increment_icon")->get_width();
+ tab_bar->set_size(Size2(tab_rects[2].size.x + tab_rects[1].size.x + offset_button_size, all_tabs_size.y));
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_bar->get_tab_offset() == 0);
+ CHECK(tab_bar->get_offset_buttons_visible());
+
+ // Scroll right to a tab that is not visible.
+ tab_bar->ensure_tab_visible(2);
+ CHECK(tab_bar->get_tab_offset() == 1);
+ CHECK(tab_bar->get_tab_rect(1).position.x == 0);
+ CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x);
+
+ tab_bar->set_tab_offset(2);
+ CHECK(tab_bar->get_tab_offset() == 2);
+ CHECK(tab_bar->get_tab_rect(2).position.x == 0);
+
+ // Scroll left to a previous tab.
+ tab_bar->ensure_tab_visible(1);
+ CHECK(tab_bar->get_tab_offset() == 1);
+ CHECK(tab_bar->get_tab_rect(1).position.x == 0);
+ CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x);
+
+ // Will not scroll if the tab is already visible.
+ tab_bar->ensure_tab_visible(2);
+ CHECK(tab_bar->get_tab_offset() == 1);
+ CHECK(tab_bar->get_tab_rect(1).position.x == 0);
+ CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x);
+ }
+
+ memdelete(tab_bar);
+}
+
+// FIXME: Add tests for mouse click, keyboard navigation, and drag and drop.
+
+} // namespace TestTabBar
+
+#endif // TEST_TAB_BAR_H
diff --git a/tests/scene/test_tab_container.h b/tests/scene/test_tab_container.h
new file mode 100644
index 0000000000..2a211a2c09
--- /dev/null
+++ b/tests/scene/test_tab_container.h
@@ -0,0 +1,671 @@
+/**************************************************************************/
+/* test_tab_container.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_TAB_CONTAINER_H
+#define TEST_TAB_CONTAINER_H
+
+#include "scene/gui/tab_container.h"
+
+#include "tests/test_macros.h"
+
+namespace TestTabContainer {
+static inline Array build_array() {
+ return Array();
+}
+template <typename... Targs>
+static inline Array build_array(Variant item, Targs... Fargs) {
+ Array a = build_array(Fargs...);
+ a.push_front(item);
+ return a;
+}
+
+TEST_CASE("[SceneTree][TabContainer] tab operations") {
+ TabContainer *tab_container = memnew(TabContainer);
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ MessageQueue::get_singleton()->flush();
+ SIGNAL_WATCH(tab_container, "tab_selected");
+ SIGNAL_WATCH(tab_container, "tab_changed");
+
+ Control *tab0 = memnew(Control);
+ tab0->set_name("tab0");
+ Control *tab1 = memnew(Control);
+ tab1->set_name("tab1");
+ Control *tab2 = memnew(Control);
+ tab2->set_name("tab2");
+
+ SUBCASE("[TabContainer] add tabs by adding children") {
+ CHECK(tab_container->get_tab_count() == 0);
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ // Add first tab child.
+ tab_container->add_child(tab0);
+ // MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+
+ // Add second tab child.
+ tab_container->add_child(tab1);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Check default values, the title is the name of the child.
+ CHECK(tab_container->get_tab_control(0) == tab0);
+ CHECK(tab_container->get_tab_idx_from_control(tab0) == 0);
+ CHECK(tab_container->get_tab_title(0) == "tab0");
+ CHECK(tab_container->get_tab_tooltip(0) == "");
+ CHECK_FALSE(tab_container->is_tab_disabled(0));
+ CHECK_FALSE(tab_container->is_tab_hidden(0));
+
+ CHECK(tab_container->get_tab_control(1) == tab1);
+ CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
+ CHECK(tab_container->get_tab_title(1) == "tab1");
+ CHECK(tab_container->get_tab_tooltip(1) == "");
+ CHECK_FALSE(tab_container->is_tab_disabled(1));
+ CHECK_FALSE(tab_container->is_tab_hidden(1));
+ }
+
+ SUBCASE("[TabContainer] remove tabs by removing children") {
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+ tab_container->set_current_tab(1);
+ CHECK(tab_container->get_tab_count() == 3);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Remove first tab.
+ tab_container->remove_child(tab0);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_tab_title(0) == "tab1");
+ CHECK(tab_container->get_tab_title(1) == "tab2");
+ CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
+ CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Remove last tab.
+ tab_container->remove_child(tab2);
+ CHECK(tab_container->get_tab_count() == 1);
+ CHECK(tab_container->get_tab_title(0) == "tab1");
+ CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Remove only tab.
+ tab_container->remove_child(tab1);
+ CHECK(tab_container->get_tab_count() == 0);
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
+
+ // Remove current tab when there are other tabs.
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+ tab_container->set_current_tab(1);
+ tab_container->set_current_tab(2);
+ CHECK(tab_container->get_tab_count() == 3);
+ CHECK(tab_container->get_current_tab() == 2);
+ CHECK(tab_container->get_previous_tab() == 1);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ tab_container->remove_child(tab2);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ }
+
+ SUBCASE("[TabContainer] move tabs by moving children") {
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+ tab_container->set_current_tab(1);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+
+ // Move the first tab to the end.
+ tab_container->move_child(tab0, 2);
+ CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
+ CHECK(tab_container->get_tab_idx_from_control(tab1) == 0);
+ CHECK(tab_container->get_tab_idx_from_control(tab2) == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == 2);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Move the second tab to the front.
+ tab_container->move_child(tab2, 0);
+ CHECK(tab_container->get_tab_idx_from_control(tab0) == 2);
+ CHECK(tab_container->get_tab_idx_from_control(tab1) == 1);
+ CHECK(tab_container->get_tab_idx_from_control(tab2) == 0);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 2);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ }
+
+ SUBCASE("[TabContainer] set current tab") {
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(0)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(0)));
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab0->is_visible());
+ CHECK_FALSE(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ // Set the current tab.
+ tab_container->set_current_tab(1);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ // Set to same tab.
+ tab_container->set_current_tab(1);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK_FALSE("tab_changed");
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ // Out of bounds.
+ ERR_PRINT_OFF;
+ tab_container->set_current_tab(-5);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ tab_container->set_current_tab(5);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("[TabContainer] change current tab by changing visibility of children") {
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab0->is_visible());
+ CHECK_FALSE(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ // Show a child to make it the current tab.
+ tab1->show();
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ // Hide the visible child to select the next tab.
+ tab1->hide();
+ CHECK(tab_container->get_current_tab() == 2);
+ CHECK(tab_container->get_previous_tab() == 1);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(2)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(2)));
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK_FALSE(tab1->is_visible());
+ CHECK(tab2->is_visible());
+
+ // Hide the visible child to select the previous tab if there is no next.
+ tab2->hide();
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 2);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+
+ // Cannot hide if there is only one valid child since deselection is not enabled.
+ tab_container->remove_child(tab1);
+ tab_container->remove_child(tab2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_DISCARD("tab_selected");
+ SIGNAL_DISCARD("tab_changed");
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab0->is_visible());
+
+ tab0->hide();
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab0->is_visible());
+
+ // Can hide the last tab if deselection is enabled.
+ tab_container->set_deselect_enabled(true);
+ tab0->hide();
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(-1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(-1)));
+ MessageQueue::get_singleton()->flush();
+ CHECK_FALSE(tab0->is_visible());
+ }
+
+ SIGNAL_UNWATCH(tab_container, "tab_selected");
+ SIGNAL_UNWATCH(tab_container, "tab_changed");
+
+ memdelete(tab2);
+ memdelete(tab1);
+ memdelete(tab0);
+ memdelete(tab_container);
+}
+
+TEST_CASE("[SceneTree][TabContainer] initialization") {
+ TabContainer *tab_container = memnew(TabContainer);
+
+ Control *tab0 = memnew(Control);
+ tab0->set_name("tab0");
+ Control *tab1 = memnew(Control);
+ tab1->set_name("tab1 ");
+ Control *tab2 = memnew(Control);
+ tab2->set_name("tab2 ");
+
+ SIGNAL_WATCH(tab_container, "tab_selected");
+ SIGNAL_WATCH(tab_container, "tab_changed");
+
+ SUBCASE("[TabContainer] add children before entering tree") {
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ tab_container->add_child(tab0);
+ CHECK(tab_container->get_tab_count() == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ tab_container->add_child(tab1);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ CHECK(tab0->is_visible());
+ CHECK_FALSE(tab1->is_visible());
+ }
+
+ SUBCASE("[TabContainer] current tab can be set before children are added") {
+ // Set the current tab before there are any tabs.
+ // This queues the current tab to update on entering the tree.
+ tab_container->set_current_tab(1);
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_container->add_child(tab0);
+ CHECK(tab_container->get_tab_count() == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ tab_container->add_child(tab1);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ tab_container->add_child(tab2);
+ CHECK(tab_container->get_tab_count() == 3);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // Current tab is set when entering the tree.
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 3);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+ }
+
+ SUBCASE("[TabContainer] cannot set current tab to an invalid value before tabs are set") {
+ tab_container->set_current_tab(100);
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_container->add_child(tab0);
+ CHECK(tab_container->get_tab_count() == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ tab_container->add_child(tab1);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+
+ // This will print an error message as if `set_current_tab` was called after.
+ ERR_PRINT_OFF;
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ ERR_PRINT_ON;
+ }
+
+ SUBCASE("[TabContainer] children visibility before entering tree") {
+ CHECK(tab_container->get_current_tab() == -1);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ // Adding a hidden child first will change visibility because it is the current tab.
+ tab0->hide();
+ tab_container->add_child(tab0);
+ CHECK(tab_container->get_tab_count() == 1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab0->is_visible());
+
+ // Adding a visible child after will hide it because it is not the current tab.
+ tab_container->add_child(tab1);
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab0->is_visible());
+ CHECK_FALSE(tab1->is_visible());
+
+ // Can change current by showing child now after children have been added.
+ // This queues the current tab to update on entering the tree.
+ tab1->show();
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ CHECK(tab0->is_visible());
+ CHECK(tab1->is_visible());
+
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 2);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ }
+
+ SUBCASE("[TabContainer] setting current tab and changing child visibility after adding") {
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ tab2->show();
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 3);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+ SIGNAL_CHECK_FALSE("tab_selected");
+ SIGNAL_CHECK_FALSE("tab_changed");
+ CHECK(tab0->is_visible());
+ CHECK_FALSE(tab1->is_visible());
+ CHECK(tab2->is_visible());
+
+ // Whichever happens last will have priority.
+ tab_container->set_current_tab(1);
+ CHECK(tab_container->get_current_tab() == 0);
+ CHECK(tab_container->get_previous_tab() == -1);
+
+ // Current tab is set when entering the tree.
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ MessageQueue::get_singleton()->flush();
+ CHECK(tab_container->get_tab_count() == 3);
+ CHECK(tab_container->get_current_tab() == 1);
+ CHECK(tab_container->get_previous_tab() == 0);
+ SIGNAL_CHECK("tab_selected", build_array(build_array(1)));
+ SIGNAL_CHECK("tab_changed", build_array(build_array(1)));
+ CHECK_FALSE(tab0->is_visible());
+ CHECK(tab1->is_visible());
+ CHECK_FALSE(tab2->is_visible());
+ }
+
+ SIGNAL_UNWATCH(tab_container, "tab_selected");
+ SIGNAL_UNWATCH(tab_container, "tab_changed");
+
+ memdelete(tab2);
+ memdelete(tab1);
+ memdelete(tab0);
+ memdelete(tab_container);
+}
+
+TEST_CASE("[SceneTree][TabContainer] layout and offset") {
+ TabContainer *tab_container = memnew(TabContainer);
+ SceneTree::get_singleton()->get_root()->add_child(tab_container);
+ tab_container->set_clip_tabs(false);
+
+ Control *tab0 = memnew(Control);
+ tab0->set_name("tab0");
+ Control *tab1 = memnew(Control);
+ tab1->set_name("tab1 ");
+ Control *tab2 = memnew(Control);
+ tab2->set_name("tab2 ");
+
+ tab_container->add_child(tab0);
+ tab_container->add_child(tab1);
+ tab_container->add_child(tab2);
+
+ MessageQueue::get_singleton()->flush();
+
+ Size2 all_tabs_size = tab_container->get_size();
+ const float side_margin = tab_container->get_theme_constant("side_margin");
+
+ TabBar *tab_bar = tab_container->get_tab_bar();
+
+ Vector<Rect2> tab_rects = {
+ tab_bar->get_tab_rect(0),
+ tab_bar->get_tab_rect(1),
+ tab_bar->get_tab_rect(2)
+ };
+
+ SUBCASE("[TabContainer] tabs are arranged next to each other") {
+ // Horizontal positions are next to each other.
+ CHECK(tab_rects[0].position.x == 0);
+ CHECK(tab_rects[1].position.x == tab_rects[0].size.x);
+ CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x);
+
+ // Fills the entire width.
+ CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x - side_margin);
+
+ // Horizontal sizes are positive.
+ CHECK(tab_rects[0].size.x > 0);
+ CHECK(tab_rects[1].size.x > 0);
+ CHECK(tab_rects[2].size.x > 0);
+
+ // Vertical positions are at 0.
+ CHECK(tab_rects[0].position.y == 0);
+ CHECK(tab_rects[1].position.y == 0);
+ CHECK(tab_rects[2].position.y == 0);
+
+ // Vertical sizes are the same.
+ CHECK(tab_rects[0].size.y == tab_rects[1].size.y);
+ CHECK(tab_rects[1].size.y == tab_rects[2].size.y);
+ }
+
+ SUBCASE("[TabContainer] tab position") {
+ float tab_height = tab_rects[0].size.y;
+ Ref<StyleBox> panel_style = tab_container->get_theme_stylebox("panel_style");
+
+ // Initial position, same as top position.
+ // Tab bar is at the top.
+ CHECK(tab_bar->get_anchor(SIDE_TOP) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
+ CHECK(tab_bar->get_offset(SIDE_TOP) == 0);
+ CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height);
+ CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
+ CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
+
+ // Child is expanded and below the tab bar.
+ CHECK(tab0->get_anchor(SIDE_TOP) == 0);
+ CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
+ CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
+ CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
+ CHECK(tab0->get_offset(SIDE_TOP) == tab_height);
+ CHECK(tab0->get_offset(SIDE_BOTTOM) == 0);
+ CHECK(tab0->get_offset(SIDE_LEFT) == 0);
+ CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
+
+ // Bottom position.
+ tab_container->set_tabs_position(TabContainer::POSITION_BOTTOM);
+ CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_BOTTOM);
+ MessageQueue::get_singleton()->flush();
+
+ // Tab bar is at the bottom.
+ CHECK(tab_bar->get_anchor(SIDE_TOP) == 1);
+ CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 1);
+ CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
+ CHECK(tab_bar->get_offset(SIDE_TOP) == -tab_height);
+ CHECK(tab_bar->get_offset(SIDE_BOTTOM) == 0);
+ CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
+ CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
+
+ // Child is expanded and above the tab bar.
+ CHECK(tab0->get_anchor(SIDE_TOP) == 0);
+ CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
+ CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
+ CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
+ CHECK(tab0->get_offset(SIDE_TOP) == 0);
+ CHECK(tab0->get_offset(SIDE_BOTTOM) == -tab_height);
+ CHECK(tab0->get_offset(SIDE_LEFT) == 0);
+ CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
+
+ // Top position.
+ tab_container->set_tabs_position(TabContainer::POSITION_TOP);
+ CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_TOP);
+ MessageQueue::get_singleton()->flush();
+
+ // Tab bar is at the top.
+ CHECK(tab_bar->get_anchor(SIDE_TOP) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0);
+ CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1);
+ CHECK(tab_bar->get_offset(SIDE_TOP) == 0);
+ CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height);
+ CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin);
+ CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0);
+
+ // Child is expanded and below the tab bar.
+ CHECK(tab0->get_anchor(SIDE_TOP) == 0);
+ CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1);
+ CHECK(tab0->get_anchor(SIDE_LEFT) == 0);
+ CHECK(tab0->get_anchor(SIDE_RIGHT) == 1);
+ CHECK(tab0->get_offset(SIDE_TOP) == tab_height);
+ CHECK(tab0->get_offset(SIDE_BOTTOM) == 0);
+ CHECK(tab0->get_offset(SIDE_LEFT) == 0);
+ CHECK(tab0->get_offset(SIDE_RIGHT) == 0);
+ }
+
+ memdelete(tab_container);
+}
+
+// FIXME: Add tests for mouse click, keyboard navigation, and drag and drop.
+
+} // namespace TestTabContainer
+
+#endif // TEST_TAB_CONTAINER_H
diff --git a/tests/test_macros.h b/tests/test_macros.h
index 10f4c59a90..d32b26f111 100644
--- a/tests/test_macros.h
+++ b/tests/test_macros.h
@@ -376,6 +376,9 @@ public:
bool check_false(const String &p_name) {
bool has = _signals.has(p_name);
+ if (has) {
+ MESSAGE("Signal has " << _signals[p_name] << " expected none.");
+ }
discard_signal(p_name);
return !has;
}
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 12ff3ad4bc..465484d605 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -50,8 +50,11 @@
#include "tests/core/io/test_json.h"
#include "tests/core/io/test_json_native.h"
#include "tests/core/io/test_marshalls.h"
+#include "tests/core/io/test_packet_peer.h"
#include "tests/core/io/test_pck_packer.h"
#include "tests/core/io/test_resource.h"
+#include "tests/core/io/test_stream_peer.h"
+#include "tests/core/io/test_stream_peer_buffer.h"
#include "tests/core/io/test_xml_parser.h"
#include "tests/core/math/test_aabb.h"
#include "tests/core/math/test_astar.h"
@@ -122,6 +125,7 @@
#include "tests/scene/test_parallax_2d.h"
#include "tests/scene/test_path_2d.h"
#include "tests/scene/test_path_follow_2d.h"
+#include "tests/scene/test_physics_material.h"
#include "tests/scene/test_sprite_frames.h"
#include "tests/scene/test_style_box_texture.h"
#include "tests/scene/test_theme.h"
@@ -138,6 +142,8 @@
#include "tests/scene/test_color_picker.h"
#include "tests/scene/test_graph_node.h"
#include "tests/scene/test_option_button.h"
+#include "tests/scene/test_tab_bar.h"
+#include "tests/scene/test_tab_container.h"
#include "tests/scene/test_text_edit.h"
#include "tests/scene/test_tree.h"
#endif // ADVANCED_GUI_DISABLED
@@ -161,6 +167,7 @@
#include "tests/scene/test_path_follow_3d.h"
#include "tests/scene/test_primitives.h"
#include "tests/scene/test_skeleton_3d.h"
+#include "tests/scene/test_sky.h"
#endif // _3D_DISABLED
#include "modules/modules_tests.gen.h"
diff --git a/thirdparty/README.md b/thirdparty/README.md
index b3de256405..2ce82e82df 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -59,12 +59,13 @@ Files extracted from upstream source:
## basis_universal
- Upstream: https://github.com/BinomialLLC/basis_universal
-- Version: 1.16.4 (900e40fb5d2502927360fe2f31762bdbb624455f, 2023)
+- Version: 1.50.0 (051ad6d8a64bb95a79e8601c317055fd1782ad3e, 2024)
- License: Apache 2.0
Files extracted from upstream source:
-- `encoder/` and `transcoder/` folders, minus `jpgd.{cpp,h}`
+- `encoder/` and `transcoder/` folders, with the following files removed from `encoder`:
+ `jpgd.{cpp,h}`, `3rdparty/{qoi.h,tinydds.h,tinyexr.cpp,tinyexr.h}`
- `LICENSE`
Applied upstream PR https://github.com/BinomialLLC/basis_universal/pull/344 to
@@ -78,7 +79,7 @@ fix build with our own copy of zstd (patch in `patches`).
Files extracted from upstream source:
-- `bc6h.glsl`, `bc1.glsl`, `CrossPlatformSettings_piece_all.glsl` and `UavCrossPlatform_piece_all.glsl`.
+- `bc6h.glsl`, `bc1.glsl`, `bc4.glsl`, `CrossPlatformSettings_piece_all.glsl` and `UavCrossPlatform_piece_all.glsl`.
- `LICENSE.md`
@@ -388,7 +389,7 @@ Files extracted from upstream source:
## harfbuzz
- Upstream: https://github.com/harfbuzz/harfbuzz
-- Version: 8.5.0 (30485ee8c3d43c553afb9d78b9924cb71c8d2f19, 2024)
+- Version: 10.0.1 (a1d9bfe62818ef0fa9cf63b6e6d51436b1c93cbc, 2024)
- License: MIT
Files extracted from upstream source:
@@ -650,6 +651,10 @@ comments and a patch is provided in the `patches` folder.
Collection of single-file libraries used in Godot components.
+- `bcdec.h`
+ * Upstream: https://github.com/iOrange/bcdec
+ * Version: git (3b29f8f44466c7d59852670f82f53905cf627d48, 2024)
+ * License: MIT
- `clipper.{cpp,hpp}`
* Upstream: https://sourceforge.net/projects/polyclipping
* Version: 6.4.2 (2017) + Godot changes (added optional exceptions handling)
@@ -873,23 +878,6 @@ They can be reapplied using the patches included in the `patches`
folder, in order.
-## squish
-
-- Upstream: https://sourceforge.net/projects/libsquish
-- Version: 1.15 (r104, 2017)
-- License: MIT
-
-Files extracted from upstream source:
-
-- `LICENSE.txt`
-- All `.cpp`, `.h` and `.inl` files
-
-Some downstream changes have been made and are identified by
-`// -- GODOT begin --` and `// -- GODOT end --` comments.
-They can be reapplied using the patches included in the `patches`
-folder.
-
-
## tinyexr
- Upstream: https://github.com/syoyo/tinyexr
diff --git a/thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.cpp b/thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.cpp
new file mode 100644
index 0000000000..5abfe2faf9
--- /dev/null
+++ b/thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.cpp
@@ -0,0 +1,2052 @@
+// File: android_astc_decomp.cpp
+
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Tester Core
+ * ----------------------------------------
+ *
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * rg: Removed external dependencies, minor fix to decompress() so it converts non-sRGB
+ * output to 8-bits correctly. I've compared this decoder's output
+ * vs. astc-codec with random inputs.
+ *
+ *//*!
+ * \file
+ * \brief ASTC Utilities.
+ *//*--------------------------------------------------------------------*/
+#include "android_astc_decomp.h"
+#include <assert.h>
+#include <algorithm>
+#include <fenv.h>
+#include <math.h>
+
+#define DE_LENGTH_OF_ARRAY(x) (sizeof(x)/sizeof(x[0]))
+#define DE_UNREF(x) (void)x
+
+typedef uint8_t deUint8;
+typedef int8_t deInt8;
+typedef uint32_t deUint32;
+typedef int32_t deInt32;
+typedef uint16_t deUint16;
+typedef int16_t deInt16;
+typedef int64_t deInt64;
+typedef uint64_t deUint64;
+
+#define DE_ASSERT assert
+
+#ifdef _MSC_VER
+#pragma warning (disable:4505) // unreferenced local function has been removed
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+namespace basisu_astc
+{
+ template <typename S> inline S maximum(S a, S b) { return (a > b) ? a : b; }
+ template <typename S> inline S maximum(S a, S b, S c) { return maximum(maximum(a, b), c); }
+ template <typename S> inline S maximum(S a, S b, S c, S d) { return maximum(maximum(maximum(a, b), c), d); }
+
+ static bool inBounds(int v, int l, int h)
+ {
+ return (v >= l) && (v < h);
+ }
+
+ static bool inRange(int v, int l, int h)
+ {
+ return (v >= l) && (v <= h);
+ }
+
+ template<typename T>
+ static inline T max(T a, T b)
+ {
+ return (a > b) ? a : b;
+ }
+
+ template<typename T>
+ static inline T min(T a, T b)
+ {
+ return (a < b) ? a : b;
+ }
+
+ template<typename T>
+ static inline T clamp(T a, T l, T h)
+ {
+ if (a < l)
+ return l;
+ else if (a > h)
+ return h;
+ return a;
+ }
+
+ struct UVec4
+ {
+ uint32_t m_c[4];
+
+ UVec4()
+ {
+ m_c[0] = 0;
+ m_c[1] = 0;
+ m_c[2] = 0;
+ m_c[3] = 0;
+ }
+
+ UVec4(uint32_t x, uint32_t y, uint32_t z, uint32_t w)
+ {
+ m_c[0] = x;
+ m_c[1] = y;
+ m_c[2] = z;
+ m_c[3] = w;
+ }
+
+ uint32_t x() const { return m_c[0]; }
+ uint32_t y() const { return m_c[1]; }
+ uint32_t z() const { return m_c[2]; }
+ uint32_t w() const { return m_c[3]; }
+
+ uint32_t& x() { return m_c[0]; }
+ uint32_t& y() { return m_c[1]; }
+ uint32_t& z() { return m_c[2]; }
+ uint32_t& w() { return m_c[3]; }
+
+ uint32_t operator[] (uint32_t idx) const { assert(idx < 4); return m_c[idx]; }
+ uint32_t& operator[] (uint32_t idx) { assert(idx < 4); return m_c[idx]; }
+ };
+
+ struct IVec4
+ {
+ int32_t m_c[4];
+
+ IVec4()
+ {
+ m_c[0] = 0;
+ m_c[1] = 0;
+ m_c[2] = 0;
+ m_c[3] = 0;
+ }
+
+ IVec4(int32_t x, int32_t y, int32_t z, int32_t w)
+ {
+ m_c[0] = x;
+ m_c[1] = y;
+ m_c[2] = z;
+ m_c[3] = w;
+ }
+
+ int32_t x() const { return m_c[0]; }
+ int32_t y() const { return m_c[1]; }
+ int32_t z() const { return m_c[2]; }
+ int32_t w() const { return m_c[3]; }
+
+ int32_t& x() { return m_c[0]; }
+ int32_t& y() { return m_c[1]; }
+ int32_t& z() { return m_c[2]; }
+ int32_t& w() { return m_c[3]; }
+
+ UVec4 asUint() const
+ {
+ return UVec4(maximum(0, m_c[0]), maximum(0, m_c[1]), maximum(0, m_c[2]), maximum(0, m_c[3]));
+ }
+
+ int32_t operator[] (uint32_t idx) const { assert(idx < 4); return m_c[idx]; }
+ int32_t& operator[] (uint32_t idx) { assert(idx < 4); return m_c[idx]; }
+ };
+
+ struct IVec3
+ {
+ int32_t m_c[3];
+
+ IVec3()
+ {
+ m_c[0] = 0;
+ m_c[1] = 0;
+ m_c[2] = 0;
+ }
+
+ IVec3(int32_t x, int32_t y, int32_t z)
+ {
+ m_c[0] = x;
+ m_c[1] = y;
+ m_c[2] = z;
+ }
+
+ int32_t x() const { return m_c[0]; }
+ int32_t y() const { return m_c[1]; }
+ int32_t z() const { return m_c[2]; }
+
+ int32_t& x() { return m_c[0]; }
+ int32_t& y() { return m_c[1]; }
+ int32_t& z() { return m_c[2]; }
+
+ int32_t operator[] (uint32_t idx) const { assert(idx < 3); return m_c[idx]; }
+ int32_t& operator[] (uint32_t idx) { assert(idx < 3); return m_c[idx]; }
+ };
+
+ static uint32_t deDivRoundUp32(uint32_t a, uint32_t b)
+ {
+ return (a + b - 1) / b;
+ }
+
+ static bool deInBounds32(uint32_t v, uint32_t l, uint32_t h)
+ {
+ return (v >= l) && (v < h);
+ }
+
+namespace astc
+{
+
+using std::vector;
+
+namespace
+{
+
+// Common utilities
+enum
+{
+ MAX_BLOCK_WIDTH = 12,
+ MAX_BLOCK_HEIGHT = 12
+};
+
+inline deUint32 getBit (deUint32 src, int ndx)
+{
+ DE_ASSERT(basisu_astc::inBounds(ndx, 0, 32));
+ return (src >> ndx) & 1;
+}
+
+inline deUint32 getBits (deUint32 src, int low, int high)
+{
+ const int numBits = (high-low) + 1;
+ DE_ASSERT(basisu_astc::inRange(numBits, 1, 32));
+
+ if (numBits < 32)
+ return (deUint32)((src >> low) & ((1u<<numBits)-1));
+ else
+ return (deUint32)((src >> low) & 0xFFFFFFFFu);
+}
+
+inline bool isBitSet (deUint32 src, int ndx)
+{
+ return getBit(src, ndx) != 0;
+}
+
+inline deUint32 reverseBits (deUint32 src, int numBits)
+{
+ DE_ASSERT(basisu_astc::inRange(numBits, 0, 32));
+
+ deUint32 result = 0;
+ for (int i = 0; i < numBits; i++)
+ result |= ((src >> i) & 1) << (numBits-1-i);
+
+ return result;
+}
+
+inline deUint32 bitReplicationScale (deUint32 src, int numSrcBits, int numDstBits)
+{
+ DE_ASSERT(numSrcBits <= numDstBits);
+ DE_ASSERT((src & ((1<<numSrcBits)-1)) == src);
+
+ deUint32 dst = 0;
+ for (int shift = numDstBits-numSrcBits; shift > -numSrcBits; shift -= numSrcBits)
+ dst |= (shift >= 0) ? (src << shift) : (src >> -shift);
+
+ return dst;
+}
+
+inline deInt32 signExtend (deInt32 src, int numSrcBits)
+{
+ DE_ASSERT(basisu_astc::inRange(numSrcBits, 2, 31));
+
+ const bool negative = (src & (1 << (numSrcBits-1))) != 0;
+ return src | (negative ? ~((1 << numSrcBits) - 1) : 0);
+}
+
+typedef uint16_t deFloat16;
+
+inline bool isFloat16InfOrNan (deFloat16 v)
+{
+ return getBits(v, 10, 14) == 31;
+}
+
+float deFloat16To32(deFloat16 val16)
+{
+ deUint32 sign;
+ deUint32 expotent;
+ deUint32 mantissa;
+
+ union
+ {
+ float f;
+ deUint32 u;
+ } x;
+
+ x.u = 0u;
+
+ sign = ((deUint32)val16 >> 15u) & 0x00000001u;
+ expotent = ((deUint32)val16 >> 10u) & 0x0000001fu;
+ mantissa = (deUint32)val16 & 0x000003ffu;
+
+ if (expotent == 0u)
+ {
+ if (mantissa == 0u)
+ {
+ /* +/- 0 */
+ x.u = sign << 31u;
+ return x.f;
+ }
+ else
+ {
+ /* Denormalized, normalize it. */
+
+ while (!(mantissa & 0x00000400u))
+ {
+ mantissa <<= 1u;
+ expotent -= 1u;
+ }
+
+ expotent += 1u;
+ mantissa &= ~0x00000400u;
+ }
+ }
+ else if (expotent == 31u)
+ {
+ if (mantissa == 0u)
+ {
+ /* +/- InF */
+ x.u = (sign << 31u) | 0x7f800000u;
+ return x.f;
+ }
+ else
+ {
+ /* +/- NaN */
+ x.u = (sign << 31u) | 0x7f800000u | (mantissa << 13u);
+ return x.f;
+ }
+ }
+
+ expotent = expotent + (127u - 15u);
+ mantissa = mantissa << 13u;
+
+ x.u = (sign << 31u) | (expotent << 23u) | mantissa;
+ return x.f;
+}
+
+enum ISEMode
+{
+ ISEMODE_TRIT = 0,
+ ISEMODE_QUINT,
+ ISEMODE_PLAIN_BIT,
+ ISEMODE_LAST
+};
+
+struct ISEParams
+{
+ ISEMode mode;
+ int numBits;
+ ISEParams (ISEMode mode_, int numBits_) : mode(mode_), numBits(numBits_) {}
+};
+
+inline int computeNumRequiredBits (const ISEParams& iseParams, int numValues)
+{
+ switch (iseParams.mode)
+ {
+ case ISEMODE_TRIT: return deDivRoundUp32(numValues*8, 5) + numValues*iseParams.numBits;
+ case ISEMODE_QUINT: return deDivRoundUp32(numValues*7, 3) + numValues*iseParams.numBits;
+ case ISEMODE_PLAIN_BIT: return numValues*iseParams.numBits;
+ default:
+ DE_ASSERT(false);
+ return -1;
+ }
+}
+
+ISEParams computeMaximumRangeISEParams (int numAvailableBits, int numValuesInSequence)
+{
+ int curBitsForTritMode = 6;
+ int curBitsForQuintMode = 5;
+ int curBitsForPlainBitMode = 8;
+
+ while (true)
+ {
+ DE_ASSERT(curBitsForTritMode > 0 || curBitsForQuintMode > 0 || curBitsForPlainBitMode > 0);
+ const int tritRange = (curBitsForTritMode > 0) ? (3 << curBitsForTritMode) - 1 : -1;
+ const int quintRange = (curBitsForQuintMode > 0) ? (5 << curBitsForQuintMode) - 1 : -1;
+ const int plainBitRange = (curBitsForPlainBitMode > 0) ? (1 << curBitsForPlainBitMode) - 1 : -1;
+ const int maxRange = basisu_astc::max(basisu_astc::max(tritRange, quintRange), plainBitRange);
+
+ if (maxRange == tritRange)
+ {
+ const ISEParams params(ISEMODE_TRIT, curBitsForTritMode);
+
+ if (computeNumRequiredBits(params, numValuesInSequence) <= numAvailableBits)
+ return ISEParams(ISEMODE_TRIT, curBitsForTritMode);
+
+ curBitsForTritMode--;
+ }
+ else if (maxRange == quintRange)
+ {
+ const ISEParams params(ISEMODE_QUINT, curBitsForQuintMode);
+
+ if (computeNumRequiredBits(params, numValuesInSequence) <= numAvailableBits)
+ return ISEParams(ISEMODE_QUINT, curBitsForQuintMode);
+
+ curBitsForQuintMode--;
+ }
+ else
+ {
+ const ISEParams params(ISEMODE_PLAIN_BIT, curBitsForPlainBitMode);
+ DE_ASSERT(maxRange == plainBitRange);
+
+ if (computeNumRequiredBits(params, numValuesInSequence) <= numAvailableBits)
+ return ISEParams(ISEMODE_PLAIN_BIT, curBitsForPlainBitMode);
+
+ curBitsForPlainBitMode--;
+ }
+ }
+}
+
+inline int computeNumColorEndpointValues (deUint32 endpointMode)
+{
+ DE_ASSERT(endpointMode < 16);
+ return (endpointMode/4 + 1) * 2;
+}
+
+// Decompression utilities
+enum DecompressResult
+{
+ DECOMPRESS_RESULT_VALID_BLOCK = 0, //!< Decompressed valid block
+ DECOMPRESS_RESULT_ERROR, //!< Encountered error while decompressing, error color written
+ DECOMPRESS_RESULT_LAST
+};
+
+// A helper for getting bits from a 128-bit block.
+class Block128
+{
+private:
+ typedef deUint64 Word;
+
+ enum
+ {
+ WORD_BYTES = sizeof(Word),
+ WORD_BITS = 8*WORD_BYTES,
+ NUM_WORDS = 128 / WORD_BITS
+ };
+ //DE_STATIC_ASSERT(128 % WORD_BITS == 0);
+
+public:
+ Block128 (const deUint8* src)
+ {
+ for (int wordNdx = 0; wordNdx < NUM_WORDS; wordNdx++)
+ {
+ m_words[wordNdx] = 0;
+ for (int byteNdx = 0; byteNdx < WORD_BYTES; byteNdx++)
+ m_words[wordNdx] |= (Word)src[wordNdx*WORD_BYTES + byteNdx] << (8*byteNdx);
+ }
+ }
+
+ deUint32 getBit (int ndx) const
+ {
+ DE_ASSERT(basisu_astc::inBounds(ndx, 0, 128));
+ return (m_words[ndx / WORD_BITS] >> (ndx % WORD_BITS)) & 1;
+ }
+
+ deUint32 getBits (int low, int high) const
+ {
+ DE_ASSERT(basisu_astc::inBounds(low, 0, 128));
+ DE_ASSERT(basisu_astc::inBounds(high, 0, 128));
+ DE_ASSERT(basisu_astc::inRange(high-low+1, 0, 32));
+
+ if (high-low+1 == 0)
+ return 0;
+
+ const int word0Ndx = low / WORD_BITS;
+ const int word1Ndx = high / WORD_BITS;
+ // \note "foo << bar << 1" done instead of "foo << (bar+1)" to avoid overflow, i.e. shift amount being too big.
+ if (word0Ndx == word1Ndx)
+ return (deUint32)((m_words[word0Ndx] & ((((Word)1 << high%WORD_BITS << 1) - 1))) >> ((Word)low % WORD_BITS));
+ else
+ {
+ DE_ASSERT(word1Ndx == word0Ndx + 1);
+ return (deUint32)(m_words[word0Ndx] >> (low%WORD_BITS)) |
+ (deUint32)((m_words[word1Ndx] & (((Word)1 << high%WORD_BITS << 1) - 1)) << (high-low - high%WORD_BITS));
+ }
+ }
+
+ bool isBitSet (int ndx) const
+ {
+ DE_ASSERT(basisu_astc::inBounds(ndx, 0, 128));
+ return getBit(ndx) != 0;
+ }
+
+private:
+ Word m_words[NUM_WORDS];
+};
+
+// A helper for sequential access into a Block128.
+class BitAccessStream
+{
+public:
+ BitAccessStream (const Block128& src, int startNdxInSrc, int length, bool forward)
+ : m_src (src)
+ , m_startNdxInSrc (startNdxInSrc)
+ , m_length (length)
+ , m_forward (forward)
+ , m_ndx (0)
+ {
+ }
+
+ // Get the next num bits. Bits at positions greater than or equal to m_length are zeros.
+ deUint32 getNext (int num)
+ {
+ if (num == 0 || m_ndx >= m_length)
+ return 0;
+ const int end = m_ndx + num;
+ const int numBitsFromSrc = basisu_astc::max(0, basisu_astc::min(m_length, end) - m_ndx);
+ const int low = m_ndx;
+ const int high = m_ndx + numBitsFromSrc - 1;
+
+ m_ndx += num;
+
+ return m_forward ? m_src.getBits(m_startNdxInSrc + low, m_startNdxInSrc + high)
+ : reverseBits(m_src.getBits(m_startNdxInSrc - high, m_startNdxInSrc - low), numBitsFromSrc);
+ }
+
+private:
+ const Block128& m_src;
+ const int m_startNdxInSrc;
+ const int m_length;
+ const bool m_forward;
+ int m_ndx;
+};
+
+struct ISEDecodedResult
+{
+ deUint32 m;
+ deUint32 tq; //!< Trit or quint value, depending on ISE mode.
+ deUint32 v;
+};
+
+// Data from an ASTC block's "block mode" part (i.e. bits [0,10]).
+struct ASTCBlockMode
+{
+ bool isError;
+ // \note Following fields only relevant if !isError.
+ bool isVoidExtent;
+ // \note Following fields only relevant if !isVoidExtent.
+ bool isDualPlane;
+ int weightGridWidth;
+ int weightGridHeight;
+ ISEParams weightISEParams;
+
+ ASTCBlockMode (void)
+ : isError (true)
+ , isVoidExtent (true)
+ , isDualPlane (true)
+ , weightGridWidth (-1)
+ , weightGridHeight (-1)
+ , weightISEParams (ISEMODE_LAST, -1)
+ {
+ }
+};
+
+inline int computeNumWeights (const ASTCBlockMode& mode)
+{
+ return mode.weightGridWidth * mode.weightGridHeight * (mode.isDualPlane ? 2 : 1);
+}
+
+struct ColorEndpointPair
+{
+ UVec4 e0;
+ UVec4 e1;
+};
+
+struct TexelWeightPair
+{
+ deUint32 w[2];
+};
+
+ASTCBlockMode getASTCBlockMode (deUint32 blockModeData)
+{
+ ASTCBlockMode blockMode;
+ blockMode.isError = true; // \note Set to false later, if not error.
+ blockMode.isVoidExtent = getBits(blockModeData, 0, 8) == 0x1fc;
+ if (!blockMode.isVoidExtent)
+ {
+ if ((getBits(blockModeData, 0, 1) == 0 && getBits(blockModeData, 6, 8) == 7) || getBits(blockModeData, 0, 3) == 0)
+ return blockMode; // Invalid ("reserved").
+
+ deUint32 r = (deUint32)-1; // \note Set in the following branches.
+
+ if (getBits(blockModeData, 0, 1) == 0)
+ {
+ const deUint32 r0 = getBit(blockModeData, 4);
+ const deUint32 r1 = getBit(blockModeData, 2);
+ const deUint32 r2 = getBit(blockModeData, 3);
+ const deUint32 i78 = getBits(blockModeData, 7, 8);
+
+ r = (r2 << 2) | (r1 << 1) | (r0 << 0);
+
+ if (i78 == 3)
+ {
+ const bool i5 = isBitSet(blockModeData, 5);
+ blockMode.weightGridWidth = i5 ? 10 : 6;
+ blockMode.weightGridHeight = i5 ? 6 : 10;
+ }
+ else
+ {
+ const deUint32 a = getBits(blockModeData, 5, 6);
+
+ switch (i78)
+ {
+ case 0: blockMode.weightGridWidth = 12; blockMode.weightGridHeight = a + 2; break;
+ case 1: blockMode.weightGridWidth = a + 2; blockMode.weightGridHeight = 12; break;
+ case 2: blockMode.weightGridWidth = a + 6; blockMode.weightGridHeight = getBits(blockModeData, 9, 10) + 6; break;
+ default: DE_ASSERT(false);
+ }
+ }
+ }
+ else
+ {
+ const deUint32 r0 = getBit(blockModeData, 4);
+ const deUint32 r1 = getBit(blockModeData, 0);
+ const deUint32 r2 = getBit(blockModeData, 1);
+ const deUint32 i23 = getBits(blockModeData, 2, 3);
+ const deUint32 a = getBits(blockModeData, 5, 6);
+
+ r = (r2 << 2) | (r1 << 1) | (r0 << 0);
+ if (i23 == 3)
+ {
+ const deUint32 b = getBit(blockModeData, 7);
+ const bool i8 = isBitSet(blockModeData, 8);
+ blockMode.weightGridWidth = i8 ? b+2 : a+2;
+ blockMode.weightGridHeight = i8 ? a+2 : b+6;
+ }
+ else
+ {
+ const deUint32 b = getBits(blockModeData, 7, 8);
+ switch (i23)
+ {
+ case 0: blockMode.weightGridWidth = b + 4; blockMode.weightGridHeight = a + 2; break;
+ case 1: blockMode.weightGridWidth = b + 8; blockMode.weightGridHeight = a + 2; break;
+ case 2: blockMode.weightGridWidth = a + 2; blockMode.weightGridHeight = b + 8; break;
+ default: DE_ASSERT(false);
+ }
+ }
+ }
+
+ const bool zeroDH = getBits(blockModeData, 0, 1) == 0 && getBits(blockModeData, 7, 8) == 2;
+ const bool h = zeroDH ? 0 : isBitSet(blockModeData, 9);
+ blockMode.isDualPlane = zeroDH ? 0 : isBitSet(blockModeData, 10);
+
+ {
+ ISEMode& m = blockMode.weightISEParams.mode;
+ int& b = blockMode.weightISEParams.numBits;
+ m = ISEMODE_PLAIN_BIT;
+ b = 0;
+ if (h)
+ {
+ switch (r)
+ {
+ case 2: m = ISEMODE_QUINT; b = 1; break;
+ case 3: m = ISEMODE_TRIT; b = 2; break;
+ case 4: b = 4; break;
+ case 5: m = ISEMODE_QUINT; b = 2; break;
+ case 6: m = ISEMODE_TRIT; b = 3; break;
+ case 7: b = 5; break;
+ default: DE_ASSERT(false);
+ }
+ }
+ else
+ {
+ switch (r)
+ {
+ case 2: b = 1; break;
+ case 3: m = ISEMODE_TRIT; break;
+ case 4: b = 2; break;
+ case 5: m = ISEMODE_QUINT; break;
+ case 6: m = ISEMODE_TRIT; b = 1; break;
+ case 7: b = 3; break;
+ default: DE_ASSERT(false);
+ }
+ }
+ }
+ }
+
+ blockMode.isError = false;
+ return blockMode;
+}
+
+inline void setASTCErrorColorBlock (void* dst, int blockWidth, int blockHeight, bool isSRGB)
+{
+ if (isSRGB)
+ {
+ deUint8* const dstU = (deUint8*)dst;
+ for (int i = 0; i < blockWidth*blockHeight; i++)
+ {
+ dstU[4*i + 0] = 0xff;
+ dstU[4*i + 1] = 0;
+ dstU[4*i + 2] = 0xff;
+ dstU[4*i + 3] = 0xff;
+ }
+ }
+ else
+ {
+ float* const dstF = (float*)dst;
+ for (int i = 0; i < blockWidth*blockHeight; i++)
+ {
+ dstF[4*i + 0] = 1.0f;
+ dstF[4*i + 1] = 0.0f;
+ dstF[4*i + 2] = 1.0f;
+ dstF[4*i + 3] = 1.0f;
+ }
+ }
+}
+
+DecompressResult decodeVoidExtentBlock (void* dst, const Block128& blockData, int blockWidth, int blockHeight, bool isSRGB, bool isLDRMode)
+{
+ const deUint32 minSExtent = blockData.getBits(12, 24);
+ const deUint32 maxSExtent = blockData.getBits(25, 37);
+ const deUint32 minTExtent = blockData.getBits(38, 50);
+ const deUint32 maxTExtent = blockData.getBits(51, 63);
+ const bool allExtentsAllOnes = (minSExtent == 0x1fff) && (maxSExtent == 0x1fff) && (minTExtent == 0x1fff) && (maxTExtent == 0x1fff);
+ const bool isHDRBlock = blockData.isBitSet(9);
+
+ if ((isLDRMode && isHDRBlock) || (!allExtentsAllOnes && (minSExtent >= maxSExtent || minTExtent >= maxTExtent)))
+ {
+ setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
+ return DECOMPRESS_RESULT_ERROR;
+ }
+
+ const deUint32 rgba[4] =
+ {
+ blockData.getBits(64, 79),
+ blockData.getBits(80, 95),
+ blockData.getBits(96, 111),
+ blockData.getBits(112, 127)
+ };
+
+ if (isSRGB)
+ {
+ deUint8* const dstU = (deUint8*)dst;
+ for (int i = 0; i < blockWidth * blockHeight; i++)
+ {
+ for (int c = 0; c < 4; c++)
+ dstU[i * 4 + c] = (deUint8)((rgba[c] & 0xff00) >> 8);
+ }
+ }
+ else
+ {
+ float* const dstF = (float*)dst;
+
+ if (isHDRBlock)
+ {
+ for (int c = 0; c < 4; c++)
+ {
+ if (isFloat16InfOrNan((deFloat16)rgba[c]))
+ {
+ //throw InternalError("Infinity or NaN color component in HDR void extent block in ASTC texture (behavior undefined by ASTC specification)");
+ setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
+ return DECOMPRESS_RESULT_ERROR;
+ }
+ }
+
+ for (int i = 0; i < blockWidth * blockHeight; i++)
+ {
+ for (int c = 0; c < 4; c++)
+ dstF[i * 4 + c] = deFloat16To32((deFloat16)rgba[c]);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < blockWidth * blockHeight; i++)
+ {
+ for (int c = 0; c < 4; c++)
+ dstF[i * 4 + c] = (rgba[c] == 65535) ? 1.0f : ((float)rgba[c] / 65536.0f);
+ }
+ }
+ }
+
+ return DECOMPRESS_RESULT_VALID_BLOCK;
+}
+
+void decodeColorEndpointModes (deUint32* endpointModesDst, const Block128& blockData, int numPartitions, int extraCemBitsStart)
+{
+ if (numPartitions == 1)
+ endpointModesDst[0] = blockData.getBits(13, 16);
+ else
+ {
+ const deUint32 highLevelSelector = blockData.getBits(23, 24);
+
+ if (highLevelSelector == 0)
+ {
+ const deUint32 mode = blockData.getBits(25, 28);
+
+ for (int i = 0; i < numPartitions; i++)
+ endpointModesDst[i] = mode;
+ }
+ else
+ {
+ for (int partNdx = 0; partNdx < numPartitions; partNdx++)
+ {
+ const deUint32 cemClass = highLevelSelector - (blockData.isBitSet(25 + partNdx) ? 0 : 1);
+ const deUint32 lowBit0Ndx = numPartitions + 2*partNdx;
+ const deUint32 lowBit1Ndx = numPartitions + 2*partNdx + 1;
+ const deUint32 lowBit0 = blockData.getBit(lowBit0Ndx < 4 ? 25+lowBit0Ndx : extraCemBitsStart+lowBit0Ndx-4);
+ const deUint32 lowBit1 = blockData.getBit(lowBit1Ndx < 4 ? 25+lowBit1Ndx : extraCemBitsStart+lowBit1Ndx-4);
+
+ endpointModesDst[partNdx] = (cemClass << 2) | (lowBit1 << 1) | lowBit0;
+ }
+ }
+ }
+}
+
+int computeNumColorEndpointValues (const deUint32* endpointModes, int numPartitions)
+{
+ int result = 0;
+
+ for (int i = 0; i < numPartitions; i++)
+ result += computeNumColorEndpointValues(endpointModes[i]);
+
+ return result;
+}
+
+void decodeISETritBlock (ISEDecodedResult* dst, int numValues, BitAccessStream& data, int numBits)
+{
+ DE_ASSERT(basisu_astc::inRange(numValues, 1, 5));
+
+ deUint32 m[5];
+ m[0] = data.getNext(numBits);
+ deUint32 T01 = data.getNext(2);
+ m[1] = data.getNext(numBits);
+ deUint32 T23 = data.getNext(2);
+ m[2] = data.getNext(numBits);
+ deUint32 T4 = data.getNext(1);
+ m[3] = data.getNext(numBits);
+ deUint32 T56 = data.getNext(2);
+ m[4] = data.getNext(numBits);
+ deUint32 T7 = data.getNext(1);
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough="
+#endif
+ switch (numValues)
+ {
+ // \note Fall-throughs.
+ case 1: T23 = 0;
+ case 2: T4 = 0;
+ case 3: T56 = 0;
+ case 4: T7 = 0;
+ case 5: break;
+ default:
+ DE_ASSERT(false);
+ }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ const deUint32 T = (T7 << 7) | (T56 << 5) | (T4 << 4) | (T23 << 2) | (T01 << 0);
+
+ static const deUint32 tritsFromT[256][5] =
+ {
+ { 0,0,0,0,0 }, { 1,0,0,0,0 }, { 2,0,0,0,0 }, { 0,0,2,0,0 }, { 0,1,0,0,0 }, { 1,1,0,0,0 }, { 2,1,0,0,0 }, { 1,0,2,0,0 }, { 0,2,0,0,0 }, { 1,2,0,0,0 }, { 2,2,0,0,0 }, { 2,0,2,0,0 }, { 0,2,2,0,0 }, { 1,2,2,0,0 }, { 2,2,2,0,0 }, { 2,0,2,0,0 },
+ { 0,0,1,0,0 }, { 1,0,1,0,0 }, { 2,0,1,0,0 }, { 0,1,2,0,0 }, { 0,1,1,0,0 }, { 1,1,1,0,0 }, { 2,1,1,0,0 }, { 1,1,2,0,0 }, { 0,2,1,0,0 }, { 1,2,1,0,0 }, { 2,2,1,0,0 }, { 2,1,2,0,0 }, { 0,0,0,2,2 }, { 1,0,0,2,2 }, { 2,0,0,2,2 }, { 0,0,2,2,2 },
+ { 0,0,0,1,0 }, { 1,0,0,1,0 }, { 2,0,0,1,0 }, { 0,0,2,1,0 }, { 0,1,0,1,0 }, { 1,1,0,1,0 }, { 2,1,0,1,0 }, { 1,0,2,1,0 }, { 0,2,0,1,0 }, { 1,2,0,1,0 }, { 2,2,0,1,0 }, { 2,0,2,1,0 }, { 0,2,2,1,0 }, { 1,2,2,1,0 }, { 2,2,2,1,0 }, { 2,0,2,1,0 },
+ { 0,0,1,1,0 }, { 1,0,1,1,0 }, { 2,0,1,1,0 }, { 0,1,2,1,0 }, { 0,1,1,1,0 }, { 1,1,1,1,0 }, { 2,1,1,1,0 }, { 1,1,2,1,0 }, { 0,2,1,1,0 }, { 1,2,1,1,0 }, { 2,2,1,1,0 }, { 2,1,2,1,0 }, { 0,1,0,2,2 }, { 1,1,0,2,2 }, { 2,1,0,2,2 }, { 1,0,2,2,2 },
+ { 0,0,0,2,0 }, { 1,0,0,2,0 }, { 2,0,0,2,0 }, { 0,0,2,2,0 }, { 0,1,0,2,0 }, { 1,1,0,2,0 }, { 2,1,0,2,0 }, { 1,0,2,2,0 }, { 0,2,0,2,0 }, { 1,2,0,2,0 }, { 2,2,0,2,0 }, { 2,0,2,2,0 }, { 0,2,2,2,0 }, { 1,2,2,2,0 }, { 2,2,2,2,0 }, { 2,0,2,2,0 },
+ { 0,0,1,2,0 }, { 1,0,1,2,0 }, { 2,0,1,2,0 }, { 0,1,2,2,0 }, { 0,1,1,2,0 }, { 1,1,1,2,0 }, { 2,1,1,2,0 }, { 1,1,2,2,0 }, { 0,2,1,2,0 }, { 1,2,1,2,0 }, { 2,2,1,2,0 }, { 2,1,2,2,0 }, { 0,2,0,2,2 }, { 1,2,0,2,2 }, { 2,2,0,2,2 }, { 2,0,2,2,2 },
+ { 0,0,0,0,2 }, { 1,0,0,0,2 }, { 2,0,0,0,2 }, { 0,0,2,0,2 }, { 0,1,0,0,2 }, { 1,1,0,0,2 }, { 2,1,0,0,2 }, { 1,0,2,0,2 }, { 0,2,0,0,2 }, { 1,2,0,0,2 }, { 2,2,0,0,2 }, { 2,0,2,0,2 }, { 0,2,2,0,2 }, { 1,2,2,0,2 }, { 2,2,2,0,2 }, { 2,0,2,0,2 },
+ { 0,0,1,0,2 }, { 1,0,1,0,2 }, { 2,0,1,0,2 }, { 0,1,2,0,2 }, { 0,1,1,0,2 }, { 1,1,1,0,2 }, { 2,1,1,0,2 }, { 1,1,2,0,2 }, { 0,2,1,0,2 }, { 1,2,1,0,2 }, { 2,2,1,0,2 }, { 2,1,2,0,2 }, { 0,2,2,2,2 }, { 1,2,2,2,2 }, { 2,2,2,2,2 }, { 2,0,2,2,2 },
+ { 0,0,0,0,1 }, { 1,0,0,0,1 }, { 2,0,0,0,1 }, { 0,0,2,0,1 }, { 0,1,0,0,1 }, { 1,1,0,0,1 }, { 2,1,0,0,1 }, { 1,0,2,0,1 }, { 0,2,0,0,1 }, { 1,2,0,0,1 }, { 2,2,0,0,1 }, { 2,0,2,0,1 }, { 0,2,2,0,1 }, { 1,2,2,0,1 }, { 2,2,2,0,1 }, { 2,0,2,0,1 },
+ { 0,0,1,0,1 }, { 1,0,1,0,1 }, { 2,0,1,0,1 }, { 0,1,2,0,1 }, { 0,1,1,0,1 }, { 1,1,1,0,1 }, { 2,1,1,0,1 }, { 1,1,2,0,1 }, { 0,2,1,0,1 }, { 1,2,1,0,1 }, { 2,2,1,0,1 }, { 2,1,2,0,1 }, { 0,0,1,2,2 }, { 1,0,1,2,2 }, { 2,0,1,2,2 }, { 0,1,2,2,2 },
+ { 0,0,0,1,1 }, { 1,0,0,1,1 }, { 2,0,0,1,1 }, { 0,0,2,1,1 }, { 0,1,0,1,1 }, { 1,1,0,1,1 }, { 2,1,0,1,1 }, { 1,0,2,1,1 }, { 0,2,0,1,1 }, { 1,2,0,1,1 }, { 2,2,0,1,1 }, { 2,0,2,1,1 }, { 0,2,2,1,1 }, { 1,2,2,1,1 }, { 2,2,2,1,1 }, { 2,0,2,1,1 },
+ { 0,0,1,1,1 }, { 1,0,1,1,1 }, { 2,0,1,1,1 }, { 0,1,2,1,1 }, { 0,1,1,1,1 }, { 1,1,1,1,1 }, { 2,1,1,1,1 }, { 1,1,2,1,1 }, { 0,2,1,1,1 }, { 1,2,1,1,1 }, { 2,2,1,1,1 }, { 2,1,2,1,1 }, { 0,1,1,2,2 }, { 1,1,1,2,2 }, { 2,1,1,2,2 }, { 1,1,2,2,2 },
+ { 0,0,0,2,1 }, { 1,0,0,2,1 }, { 2,0,0,2,1 }, { 0,0,2,2,1 }, { 0,1,0,2,1 }, { 1,1,0,2,1 }, { 2,1,0,2,1 }, { 1,0,2,2,1 }, { 0,2,0,2,1 }, { 1,2,0,2,1 }, { 2,2,0,2,1 }, { 2,0,2,2,1 }, { 0,2,2,2,1 }, { 1,2,2,2,1 }, { 2,2,2,2,1 }, { 2,0,2,2,1 },
+ { 0,0,1,2,1 }, { 1,0,1,2,1 }, { 2,0,1,2,1 }, { 0,1,2,2,1 }, { 0,1,1,2,1 }, { 1,1,1,2,1 }, { 2,1,1,2,1 }, { 1,1,2,2,1 }, { 0,2,1,2,1 }, { 1,2,1,2,1 }, { 2,2,1,2,1 }, { 2,1,2,2,1 }, { 0,2,1,2,2 }, { 1,2,1,2,2 }, { 2,2,1,2,2 }, { 2,1,2,2,2 },
+ { 0,0,0,1,2 }, { 1,0,0,1,2 }, { 2,0,0,1,2 }, { 0,0,2,1,2 }, { 0,1,0,1,2 }, { 1,1,0,1,2 }, { 2,1,0,1,2 }, { 1,0,2,1,2 }, { 0,2,0,1,2 }, { 1,2,0,1,2 }, { 2,2,0,1,2 }, { 2,0,2,1,2 }, { 0,2,2,1,2 }, { 1,2,2,1,2 }, { 2,2,2,1,2 }, { 2,0,2,1,2 },
+ { 0,0,1,1,2 }, { 1,0,1,1,2 }, { 2,0,1,1,2 }, { 0,1,2,1,2 }, { 0,1,1,1,2 }, { 1,1,1,1,2 }, { 2,1,1,1,2 }, { 1,1,2,1,2 }, { 0,2,1,1,2 }, { 1,2,1,1,2 }, { 2,2,1,1,2 }, { 2,1,2,1,2 }, { 0,2,2,2,2 }, { 1,2,2,2,2 }, { 2,2,2,2,2 }, { 2,1,2,2,2 }
+ };
+
+ const deUint32 (& trits)[5] = tritsFromT[T];
+ for (int i = 0; i < numValues; i++)
+ {
+ dst[i].m = m[i];
+ dst[i].tq = trits[i];
+ dst[i].v = (trits[i] << numBits) + m[i];
+ }
+}
+
+void decodeISEQuintBlock (ISEDecodedResult* dst, int numValues, BitAccessStream& data, int numBits)
+{
+ DE_ASSERT(basisu_astc::inRange(numValues, 1, 3));
+
+ deUint32 m[3];
+ m[0] = data.getNext(numBits);
+ deUint32 Q012 = data.getNext(3);
+ m[1] = data.getNext(numBits);
+ deUint32 Q34 = data.getNext(2);
+ m[2] = data.getNext(numBits);
+ deUint32 Q56 = data.getNext(2);
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough="
+#endif
+ switch (numValues)
+ {
+ // \note Fall-throughs.
+ case 1: Q34 = 0;
+ case 2: Q56 = 0;
+ case 3: break;
+ default:
+ DE_ASSERT(false);
+ }
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+ const deUint32 Q = (Q56 << 5) | (Q34 << 3) | (Q012 << 0);
+
+ static const deUint32 quintsFromQ[256][3] =
+ {
+ { 0,0,0 }, { 1,0,0 }, { 2,0,0 }, { 3,0,0 }, { 4,0,0 }, { 0,4,0 }, { 4,4,0 }, { 4,4,4 }, { 0,1,0 }, { 1,1,0 }, { 2,1,0 }, { 3,1,0 }, { 4,1,0 }, { 1,4,0 }, { 4,4,1 }, { 4,4,4 },
+ { 0,2,0 }, { 1,2,0 }, { 2,2,0 }, { 3,2,0 }, { 4,2,0 }, { 2,4,0 }, { 4,4,2 }, { 4,4,4 }, { 0,3,0 }, { 1,3,0 }, { 2,3,0 }, { 3,3,0 }, { 4,3,0 }, { 3,4,0 }, { 4,4,3 }, { 4,4,4 },
+ { 0,0,1 }, { 1,0,1 }, { 2,0,1 }, { 3,0,1 }, { 4,0,1 }, { 0,4,1 }, { 4,0,4 }, { 0,4,4 }, { 0,1,1 }, { 1,1,1 }, { 2,1,1 }, { 3,1,1 }, { 4,1,1 }, { 1,4,1 }, { 4,1,4 }, { 1,4,4 },
+ { 0,2,1 }, { 1,2,1 }, { 2,2,1 }, { 3,2,1 }, { 4,2,1 }, { 2,4,1 }, { 4,2,4 }, { 2,4,4 }, { 0,3,1 }, { 1,3,1 }, { 2,3,1 }, { 3,3,1 }, { 4,3,1 }, { 3,4,1 }, { 4,3,4 }, { 3,4,4 },
+ { 0,0,2 }, { 1,0,2 }, { 2,0,2 }, { 3,0,2 }, { 4,0,2 }, { 0,4,2 }, { 2,0,4 }, { 3,0,4 }, { 0,1,2 }, { 1,1,2 }, { 2,1,2 }, { 3,1,2 }, { 4,1,2 }, { 1,4,2 }, { 2,1,4 }, { 3,1,4 },
+ { 0,2,2 }, { 1,2,2 }, { 2,2,2 }, { 3,2,2 }, { 4,2,2 }, { 2,4,2 }, { 2,2,4 }, { 3,2,4 }, { 0,3,2 }, { 1,3,2 }, { 2,3,2 }, { 3,3,2 }, { 4,3,2 }, { 3,4,2 }, { 2,3,4 }, { 3,3,4 },
+ { 0,0,3 }, { 1,0,3 }, { 2,0,3 }, { 3,0,3 }, { 4,0,3 }, { 0,4,3 }, { 0,0,4 }, { 1,0,4 }, { 0,1,3 }, { 1,1,3 }, { 2,1,3 }, { 3,1,3 }, { 4,1,3 }, { 1,4,3 }, { 0,1,4 }, { 1,1,4 },
+ { 0,2,3 }, { 1,2,3 }, { 2,2,3 }, { 3,2,3 }, { 4,2,3 }, { 2,4,3 }, { 0,2,4 }, { 1,2,4 }, { 0,3,3 }, { 1,3,3 }, { 2,3,3 }, { 3,3,3 }, { 4,3,3 }, { 3,4,3 }, { 0,3,4 }, { 1,3,4 }
+ };
+
+ const deUint32 (& quints)[3] = quintsFromQ[Q];
+ for (int i = 0; i < numValues; i++)
+ {
+ dst[i].m = m[i];
+ dst[i].tq = quints[i];
+ dst[i].v = (quints[i] << numBits) + m[i];
+ }
+}
+
+inline void decodeISEBitBlock (ISEDecodedResult* dst, BitAccessStream& data, int numBits)
+{
+ dst[0].m = data.getNext(numBits);
+ dst[0].v = dst[0].m;
+}
+
+void decodeISE (ISEDecodedResult* dst, int numValues, BitAccessStream& data, const ISEParams& params)
+{
+ if (params.mode == ISEMODE_TRIT)
+ {
+ const int numBlocks = deDivRoundUp32(numValues, 5);
+ for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+ {
+ const int numValuesInBlock = blockNdx == numBlocks-1 ? numValues - 5*(numBlocks-1) : 5;
+ decodeISETritBlock(&dst[5*blockNdx], numValuesInBlock, data, params.numBits);
+ }
+ }
+ else if (params.mode == ISEMODE_QUINT)
+ {
+ const int numBlocks = deDivRoundUp32(numValues, 3);
+ for (int blockNdx = 0; blockNdx < numBlocks; blockNdx++)
+ {
+ const int numValuesInBlock = blockNdx == numBlocks-1 ? numValues - 3*(numBlocks-1) : 3;
+ decodeISEQuintBlock(&dst[3*blockNdx], numValuesInBlock, data, params.numBits);
+ }
+ }
+ else
+ {
+ DE_ASSERT(params.mode == ISEMODE_PLAIN_BIT);
+ for (int i = 0; i < numValues; i++)
+ decodeISEBitBlock(&dst[i], data, params.numBits);
+ }
+}
+
+void unquantizeColorEndpoints (deUint32* dst, const ISEDecodedResult* iseResults, int numEndpoints, const ISEParams& iseParams)
+{
+ if ((iseParams.mode == ISEMODE_TRIT) || (iseParams.mode == ISEMODE_QUINT))
+ {
+ const int rangeCase = iseParams.numBits*2 - (iseParams.mode == ISEMODE_TRIT ? 2 : 1);
+ DE_ASSERT(basisu_astc::inRange(rangeCase, 0, 10));
+
+ static const deUint32 Ca[11] = { 204, 113, 93, 54, 44, 26, 22, 13, 11, 6, 5 };
+ const deUint32 C = Ca[rangeCase];
+
+ for (int endpointNdx = 0; endpointNdx < numEndpoints; endpointNdx++)
+ {
+ const deUint32 a = getBit(iseResults[endpointNdx].m, 0);
+ const deUint32 b = getBit(iseResults[endpointNdx].m, 1);
+ const deUint32 c = getBit(iseResults[endpointNdx].m, 2);
+ const deUint32 d = getBit(iseResults[endpointNdx].m, 3);
+ const deUint32 e = getBit(iseResults[endpointNdx].m, 4);
+ const deUint32 f = getBit(iseResults[endpointNdx].m, 5);
+ const deUint32 A = (a == 0) ? 0 : (1<<9)-1;
+
+ const deUint32 B = (rangeCase == 0) ? 0
+ : (rangeCase == 1) ? 0
+ : (rangeCase == 2) ? ((b << 8) | (b << 4) | (b << 2) | (b << 1))
+ : (rangeCase == 3) ? ((b << 8) | (b << 3) | (b << 2))
+ : (rangeCase == 4) ? ((c << 8) | (b << 7) | (c << 3) | (b << 2) | (c << 1) | (b << 0))
+ : (rangeCase == 5) ? ((c << 8) | (b << 7) | (c << 2) | (b << 1) | (c << 0))
+ : (rangeCase == 6) ? ((d << 8) | (c << 7) | (b << 6) | (d << 2) | (c << 1) | (b << 0))
+ : (rangeCase == 7) ? ((d << 8) | (c << 7) | (b << 6) | (d << 1) | (c << 0))
+ : (rangeCase == 8) ? ((e << 8) | (d << 7) | (c << 6) | (b << 5) | (e << 1) | (d << 0))
+ : (rangeCase == 9) ? ((e << 8) | (d << 7) | (c << 6) | (b << 5) | (e << 0))
+ : (rangeCase == 10) ? ((f << 8) | (e << 7) | (d << 6) | (c << 5) | (b << 4) | (f << 0))
+ : (deUint32)-1;
+
+ DE_ASSERT(B != (deUint32)-1);
+ dst[endpointNdx] = (((iseResults[endpointNdx].tq*C + B) ^ A) >> 2) | (A & 0x80);
+ }
+ }
+ else
+ {
+ DE_ASSERT(iseParams.mode == ISEMODE_PLAIN_BIT);
+ for (int endpointNdx = 0; endpointNdx < numEndpoints; endpointNdx++)
+ dst[endpointNdx] = bitReplicationScale(iseResults[endpointNdx].v, iseParams.numBits, 8);
+ }
+}
+
+inline void bitTransferSigned (deInt32& a, deInt32& b)
+{
+ b >>= 1;
+ b |= a & 0x80;
+ a >>= 1;
+ a &= 0x3f;
+ if (isBitSet(a, 5))
+ a -= 0x40;
+}
+
+inline UVec4 clampedRGBA (const IVec4& rgba)
+{
+ return UVec4(basisu_astc::clamp(rgba.x(), 0, 0xff),
+ basisu_astc::clamp(rgba.y(), 0, 0xff),
+ basisu_astc::clamp(rgba.z(), 0, 0xff),
+ basisu_astc::clamp(rgba.w(), 0, 0xff));
+}
+
+inline IVec4 blueContract (int r, int g, int b, int a)
+{
+ return IVec4((r+b)>>1, (g+b)>>1, b, a);
+}
+
+inline bool isColorEndpointModeHDR (deUint32 mode)
+{
+ return (mode == 2) ||
+ (mode == 3) ||
+ (mode == 7) ||
+ (mode == 11) ||
+ (mode == 14) ||
+ (mode == 15);
+}
+
+void decodeHDREndpointMode7 (UVec4& e0, UVec4& e1, deUint32 v0, deUint32 v1, deUint32 v2, deUint32 v3)
+{
+ const deUint32 m10 = getBit(v1, 7) | (getBit(v2, 7) << 1);
+ const deUint32 m23 = getBits(v0, 6, 7);
+
+ const deUint32 majComp = (m10 != 3) ? m10
+ : (m23 != 3) ? m23
+ : 0;
+
+ const deUint32 mode = (m10 != 3) ? m23
+ : (m23 != 3) ? 4
+ : 5;
+
+ deInt32 red = (deInt32)getBits(v0, 0, 5);
+ deInt32 green = (deInt32)getBits(v1, 0, 4);
+ deInt32 blue = (deInt32)getBits(v2, 0, 4);
+ deInt32 scale = (deInt32)getBits(v3, 0, 4);
+
+ {
+#define SHOR(DST_VAR, SHIFT, BIT_VAR) (DST_VAR) |= (BIT_VAR) << (SHIFT)
+#define ASSIGN_X_BITS(V0,S0, V1,S1, V2,S2, V3,S3, V4,S4, V5,S5, V6,S6) do { SHOR(V0,S0,x0); SHOR(V1,S1,x1); SHOR(V2,S2,x2); SHOR(V3,S3,x3); SHOR(V4,S4,x4); SHOR(V5,S5,x5); SHOR(V6,S6,x6); } while (false)
+
+ const deUint32 x0 = getBit(v1, 6);
+ const deUint32 x1 = getBit(v1, 5);
+ const deUint32 x2 = getBit(v2, 6);
+ const deUint32 x3 = getBit(v2, 5);
+ const deUint32 x4 = getBit(v3, 7);
+ const deUint32 x5 = getBit(v3, 6);
+ const deUint32 x6 = getBit(v3, 5);
+
+ deInt32& R = red;
+ deInt32& G = green;
+ deInt32& B = blue;
+ deInt32& S = scale;
+
+ switch (mode)
+ {
+ case 0: ASSIGN_X_BITS(R,9, R,8, R,7, R,10, R,6, S,6, S,5); break;
+ case 1: ASSIGN_X_BITS(R,8, G,5, R,7, B,5, R,6, R,10, R,9); break;
+ case 2: ASSIGN_X_BITS(R,9, R,8, R,7, R,6, S,7, S,6, S,5); break;
+ case 3: ASSIGN_X_BITS(R,8, G,5, R,7, B,5, R,6, S,6, S,5); break;
+ case 4: ASSIGN_X_BITS(G,6, G,5, B,6, B,5, R,6, R,7, S,5); break;
+ case 5: ASSIGN_X_BITS(G,6, G,5, B,6, B,5, R,6, S,6, S,5); break;
+ default:
+ DE_ASSERT(false);
+ }
+#undef ASSIGN_X_BITS
+#undef SHOR
+ }
+
+ static const int shiftAmounts[] = { 1, 1, 2, 3, 4, 5 };
+ DE_ASSERT(mode < DE_LENGTH_OF_ARRAY(shiftAmounts));
+
+ red <<= shiftAmounts[mode];
+ green <<= shiftAmounts[mode];
+ blue <<= shiftAmounts[mode];
+ scale <<= shiftAmounts[mode];
+
+ if (mode != 5)
+ {
+ green = red - green;
+ blue = red - blue;
+ }
+
+ if (majComp == 1)
+ std::swap(red, green);
+ else if (majComp == 2)
+ std::swap(red, blue);
+
+ e0 = UVec4(basisu_astc::clamp(red - scale, 0, 0xfff),
+ basisu_astc::clamp(green - scale, 0, 0xfff),
+ basisu_astc::clamp(blue - scale, 0, 0xfff),
+ 0x780);
+
+ e1 = UVec4(basisu_astc::clamp(red, 0, 0xfff),
+ basisu_astc::clamp(green, 0, 0xfff),
+ basisu_astc::clamp(blue, 0, 0xfff),
+ 0x780);
+}
+
+void decodeHDREndpointMode11 (UVec4& e0, UVec4& e1, deUint32 v0, deUint32 v1, deUint32 v2, deUint32 v3, deUint32 v4, deUint32 v5)
+{
+ const deUint32 major = (getBit(v5, 7) << 1) | getBit(v4, 7);
+
+ if (major == 3)
+ {
+ e0 = UVec4(v0<<4, v2<<4, getBits(v4,0,6)<<5, 0x780);
+ e1 = UVec4(v1<<4, v3<<4, getBits(v5,0,6)<<5, 0x780);
+ }
+ else
+ {
+ const deUint32 mode = (getBit(v3, 7) << 2) | (getBit(v2, 7) << 1) | getBit(v1, 7);
+
+ deInt32 a = (deInt32)((getBit(v1, 6) << 8) | v0);
+ deInt32 c = (deInt32)(getBits(v1, 0, 5));
+ deInt32 b0 = (deInt32)(getBits(v2, 0, 5));
+ deInt32 b1 = (deInt32)(getBits(v3, 0, 5));
+ deInt32 d0 = (deInt32)(getBits(v4, 0, 4));
+ deInt32 d1 = (deInt32)(getBits(v5, 0, 4));
+
+ {
+#define SHOR(DST_VAR, SHIFT, BIT_VAR) (DST_VAR) |= (BIT_VAR) << (SHIFT)
+#define ASSIGN_X_BITS(V0,S0, V1,S1, V2,S2, V3,S3, V4,S4, V5,S5) do { SHOR(V0,S0,x0); SHOR(V1,S1,x1); SHOR(V2,S2,x2); SHOR(V3,S3,x3); SHOR(V4,S4,x4); SHOR(V5,S5,x5); } while (false)
+ const deUint32 x0 = getBit(v2, 6);
+ const deUint32 x1 = getBit(v3, 6);
+ const deUint32 x2 = getBit(v4, 6);
+ const deUint32 x3 = getBit(v5, 6);
+ const deUint32 x4 = getBit(v4, 5);
+ const deUint32 x5 = getBit(v5, 5);
+
+ switch (mode)
+ {
+ case 0: ASSIGN_X_BITS(b0,6, b1,6, d0,6, d1,6, d0,5, d1,5); break;
+ case 1: ASSIGN_X_BITS(b0,6, b1,6, b0,7, b1,7, d0,5, d1,5); break;
+ case 2: ASSIGN_X_BITS(a,9, c,6, d0,6, d1,6, d0,5, d1,5); break;
+ case 3: ASSIGN_X_BITS(b0,6, b1,6, a,9, c,6, d0,5, d1,5); break;
+ case 4: ASSIGN_X_BITS(b0,6, b1,6, b0,7, b1,7, a,9, a,10); break;
+ case 5: ASSIGN_X_BITS(a,9, a,10, c,7, c,6, d0,5, d1,5); break;
+ case 6: ASSIGN_X_BITS(b0,6, b1,6, a,11, c,6, a,9, a,10); break;
+ case 7: ASSIGN_X_BITS(a,9, a,10, a,11, c,6, d0,5, d1,5); break;
+ default:
+ DE_ASSERT(false);
+ }
+#undef ASSIGN_X_BITS
+#undef SHOR
+ }
+
+ static const int numDBits[] = { 7, 6, 7, 6, 5, 6, 5, 6 };
+ DE_ASSERT(mode < DE_LENGTH_OF_ARRAY(numDBits));
+ d0 = signExtend(d0, numDBits[mode]);
+ d1 = signExtend(d1, numDBits[mode]);
+
+ const int shiftAmount = (mode >> 1) ^ 3;
+ a = (uint32_t)a << shiftAmount;
+ c = (uint32_t)c << shiftAmount;
+ b0 = (uint32_t)b0 << shiftAmount;
+ b1 = (uint32_t)b1 << shiftAmount;
+ d0 = (uint32_t)d0 << shiftAmount;
+ d1 = (uint32_t)d1 << shiftAmount;
+
+ e0 = UVec4(basisu_astc::clamp(a-c, 0, 0xfff), basisu_astc::clamp(a-b0-c-d0, 0, 0xfff), basisu_astc::clamp(a-b1-c-d1, 0, 0xfff), 0x780);
+ e1 = UVec4(basisu_astc::clamp(a, 0, 0xfff), basisu_astc::clamp(a-b0, 0, 0xfff), basisu_astc::clamp(a-b1, 0, 0xfff), 0x780);
+
+ if (major == 1)
+ {
+ std::swap(e0.x(), e0.y());
+ std::swap(e1.x(), e1.y());
+ }
+ else if (major == 2)
+ {
+ std::swap(e0.x(), e0.z());
+ std::swap(e1.x(), e1.z());
+ }
+ }
+}
+
+void decodeHDREndpointMode15(UVec4& e0, UVec4& e1, deUint32 v0, deUint32 v1, deUint32 v2, deUint32 v3, deUint32 v4, deUint32 v5, deUint32 v6In, deUint32 v7In)
+{
+ decodeHDREndpointMode11(e0, e1, v0, v1, v2, v3, v4, v5);
+
+ const deUint32 mode = (getBit(v7In, 7) << 1) | getBit(v6In, 7);
+ deInt32 v6 = (deInt32)getBits(v6In, 0, 6);
+ deInt32 v7 = (deInt32)getBits(v7In, 0, 6);
+
+ if (mode == 3)
+ {
+ e0.w() = v6 << 5;
+ e1.w() = v7 << 5;
+ }
+ else
+ {
+ v6 |= (v7 << (mode+1)) & 0x780;
+ v7 &= (0x3f >> mode);
+ v7 ^= 0x20 >> mode;
+ v7 -= 0x20 >> mode;
+ v6 <<= 4-mode;
+ v7 <<= 4-mode;
+ v7 += v6;
+ v7 = basisu_astc::clamp(v7, 0, 0xfff);
+ e0.w() = v6;
+ e1.w() = v7;
+ }
+}
+
+void decodeColorEndpoints (ColorEndpointPair* dst, const deUint32* unquantizedEndpoints, const deUint32* endpointModes, int numPartitions)
+{
+ int unquantizedNdx = 0;
+
+ for (int partitionNdx = 0; partitionNdx < numPartitions; partitionNdx++)
+ {
+ const deUint32 endpointMode = endpointModes[partitionNdx];
+ const deUint32* v = &unquantizedEndpoints[unquantizedNdx];
+
+ UVec4& e0 = dst[partitionNdx].e0;
+ UVec4& e1 = dst[partitionNdx].e1;
+ unquantizedNdx += computeNumColorEndpointValues(endpointMode);
+
+ switch (endpointMode)
+ {
+ case 0:
+ {
+ e0 = UVec4(v[0], v[0], v[0], 0xff);
+ e1 = UVec4(v[1], v[1], v[1], 0xff);
+ break;
+ }
+ case 1:
+ {
+ const deUint32 L0 = (v[0] >> 2) | (getBits(v[1], 6, 7) << 6);
+ const deUint32 L1 = basisu_astc::min(0xffu, L0 + getBits(v[1], 0, 5));
+ e0 = UVec4(L0, L0, L0, 0xff);
+ e1 = UVec4(L1, L1, L1, 0xff);
+ break;
+ }
+ case 2:
+ {
+ const deUint32 v1Gr = v[1] >= v[0];
+ const deUint32 y0 = v1Gr ? v[0]<<4 : (v[1]<<4) + 8;
+ const deUint32 y1 = v1Gr ? v[1]<<4 : (v[0]<<4) - 8;
+ e0 = UVec4(y0, y0, y0, 0x780);
+ e1 = UVec4(y1, y1, y1, 0x780);
+ break;
+ }
+ case 3:
+ {
+ const bool m = isBitSet(v[0], 7);
+ const deUint32 y0 = m ? (getBits(v[1], 5, 7) << 9) | (getBits(v[0], 0, 6) << 2)
+ : (getBits(v[1], 4, 7) << 8) | (getBits(v[0], 0, 6) << 1);
+ const deUint32 d = m ? getBits(v[1], 0, 4) << 2
+ : getBits(v[1], 0, 3) << 1;
+ const deUint32 y1 = basisu_astc::min(0xfffu, y0+d);
+ e0 = UVec4(y0, y0, y0, 0x780);
+ e1 = UVec4(y1, y1, y1, 0x780);
+ break;
+ }
+ case 4:
+ {
+ e0 = UVec4(v[0], v[0], v[0], v[2]);
+ e1 = UVec4(v[1], v[1], v[1], v[3]);
+ break;
+ }
+ case 5:
+ {
+ deInt32 v0 = (deInt32)v[0];
+ deInt32 v1 = (deInt32)v[1];
+ deInt32 v2 = (deInt32)v[2];
+ deInt32 v3 = (deInt32)v[3];
+ bitTransferSigned(v1, v0);
+ bitTransferSigned(v3, v2);
+ e0 = clampedRGBA(IVec4(v0, v0, v0, v2));
+ e1 = clampedRGBA(IVec4(v0+v1, v0+v1, v0+v1, v2+v3));
+ break;
+ }
+ case 6:
+ e0 = UVec4((v[0]*v[3]) >> 8, (v[1]*v[3]) >> 8, (v[2]*v[3]) >> 8, 0xff);
+ e1 = UVec4(v[0], v[1], v[2], 0xff);
+ break;
+ case 7:
+ decodeHDREndpointMode7(e0, e1, v[0], v[1], v[2], v[3]);
+ break;
+ case 8:
+ {
+ if (v[1]+v[3]+v[5] >= v[0]+v[2]+v[4])
+ {
+ e0 = UVec4(v[0], v[2], v[4], 0xff);
+ e1 = UVec4(v[1], v[3], v[5], 0xff);
+ }
+ else
+ {
+ e0 = blueContract(v[1], v[3], v[5], 0xff).asUint();
+ e1 = blueContract(v[0], v[2], v[4], 0xff).asUint();
+ }
+ break;
+ }
+ case 9:
+ {
+ deInt32 v0 = (deInt32)v[0];
+ deInt32 v1 = (deInt32)v[1];
+ deInt32 v2 = (deInt32)v[2];
+ deInt32 v3 = (deInt32)v[3];
+ deInt32 v4 = (deInt32)v[4];
+ deInt32 v5 = (deInt32)v[5];
+ bitTransferSigned(v1, v0);
+ bitTransferSigned(v3, v2);
+ bitTransferSigned(v5, v4);
+ if (v1+v3+v5 >= 0)
+ {
+ e0 = clampedRGBA(IVec4(v0, v2, v4, 0xff));
+ e1 = clampedRGBA(IVec4(v0+v1, v2+v3, v4+v5, 0xff));
+ }
+ else
+ {
+ e0 = clampedRGBA(blueContract(v0+v1, v2+v3, v4+v5, 0xff));
+ e1 = clampedRGBA(blueContract(v0, v2, v4, 0xff));
+ }
+ break;
+ }
+ case 10:
+ {
+ e0 = UVec4((v[0]*v[3]) >> 8, (v[1]*v[3]) >> 8, (v[2]*v[3]) >> 8, v[4]);
+ e1 = UVec4(v[0], v[1], v[2], v[5]);
+ break;
+ }
+ case 11:
+ {
+ decodeHDREndpointMode11(e0, e1, v[0], v[1], v[2], v[3], v[4], v[5]);
+ break;
+ }
+ case 12:
+ {
+ if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4])
+ {
+ e0 = UVec4(v[0], v[2], v[4], v[6]);
+ e1 = UVec4(v[1], v[3], v[5], v[7]);
+ }
+ else
+ {
+ e0 = clampedRGBA(blueContract(v[1], v[3], v[5], v[7]));
+ e1 = clampedRGBA(blueContract(v[0], v[2], v[4], v[6]));
+ }
+ break;
+ }
+ case 13:
+ {
+ deInt32 v0 = (deInt32)v[0];
+ deInt32 v1 = (deInt32)v[1];
+ deInt32 v2 = (deInt32)v[2];
+ deInt32 v3 = (deInt32)v[3];
+ deInt32 v4 = (deInt32)v[4];
+ deInt32 v5 = (deInt32)v[5];
+ deInt32 v6 = (deInt32)v[6];
+ deInt32 v7 = (deInt32)v[7];
+ bitTransferSigned(v1, v0);
+ bitTransferSigned(v3, v2);
+ bitTransferSigned(v5, v4);
+ bitTransferSigned(v7, v6);
+ if (v1+v3+v5 >= 0)
+ {
+ e0 = clampedRGBA(IVec4(v0, v2, v4, v6));
+ e1 = clampedRGBA(IVec4(v0+v1, v2+v3, v4+v5, v6+v7));
+ }
+ else
+ {
+ e0 = clampedRGBA(blueContract(v0+v1, v2+v3, v4+v5, v6+v7));
+ e1 = clampedRGBA(blueContract(v0, v2, v4, v6));
+ }
+ break;
+ }
+ case 14:
+ decodeHDREndpointMode11(e0, e1, v[0], v[1], v[2], v[3], v[4], v[5]);
+ e0.w() = v[6];
+ e1.w() = v[7];
+ break;
+ case 15:
+ {
+ decodeHDREndpointMode15(e0, e1, v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
+ break;
+ }
+ default:
+ DE_ASSERT(false);
+ }
+ }
+}
+
+void computeColorEndpoints (ColorEndpointPair* dst, const Block128& blockData, const deUint32* endpointModes, int numPartitions, int numColorEndpointValues, const ISEParams& iseParams, int numBitsAvailable)
+{
+ const int colorEndpointDataStart = (numPartitions == 1) ? 17 : 29;
+ ISEDecodedResult colorEndpointData[18];
+
+ {
+ BitAccessStream dataStream(blockData, colorEndpointDataStart, numBitsAvailable, true);
+ decodeISE(&colorEndpointData[0], numColorEndpointValues, dataStream, iseParams);
+ }
+
+ {
+ deUint32 unquantizedEndpoints[18];
+ unquantizeColorEndpoints(&unquantizedEndpoints[0], &colorEndpointData[0], numColorEndpointValues, iseParams);
+ decodeColorEndpoints(dst, &unquantizedEndpoints[0], &endpointModes[0], numPartitions);
+ }
+}
+
+void unquantizeWeights (deUint32 dst[64], const ISEDecodedResult* weightGrid, const ASTCBlockMode& blockMode)
+{
+ const int numWeights = computeNumWeights(blockMode);
+ const ISEParams& iseParams = blockMode.weightISEParams;
+
+ if ((iseParams.mode == ISEMODE_TRIT) || (iseParams.mode == ISEMODE_QUINT))
+ {
+ const int rangeCase = iseParams.numBits*2 + (iseParams.mode == ISEMODE_QUINT ? 1 : 0);
+
+ if ((rangeCase == 0) || (rangeCase == 1))
+ {
+ static const deUint32 map0[3] = { 0, 32, 63 };
+ static const deUint32 map1[5] = { 0, 16, 32, 47, 63 };
+ const deUint32* const map = (rangeCase == 0) ? &map0[0] : &map1[0];
+
+ for (int i = 0; i < numWeights; i++)
+ {
+ DE_ASSERT(weightGrid[i].v < (rangeCase == 0 ? 3u : 5u));
+ dst[i] = map[weightGrid[i].v];
+ }
+ }
+ else
+ {
+ DE_ASSERT(rangeCase <= 6);
+ static const deUint32 Ca[5] = { 50, 28, 23, 13, 11 };
+ const deUint32 C = Ca[rangeCase-2];
+
+ for (int weightNdx = 0; weightNdx < numWeights; weightNdx++)
+ {
+ const deUint32 a = getBit(weightGrid[weightNdx].m, 0);
+ const deUint32 b = getBit(weightGrid[weightNdx].m, 1);
+ const deUint32 c = getBit(weightGrid[weightNdx].m, 2);
+
+ const deUint32 A = (a == 0) ? 0 : (1<<7)-1;
+ const deUint32 B = (rangeCase == 2) ? 0
+ : (rangeCase == 3) ? 0
+ : (rangeCase == 4) ? (b << 6) | (b << 2) | (b << 0)
+ : (rangeCase == 5) ? (b << 6) | (b << 1)
+ : (rangeCase == 6) ? (c << 6) | (b << 5) | (c << 1) | (b << 0)
+ : (deUint32)-1;
+
+ dst[weightNdx] = (((weightGrid[weightNdx].tq*C + B) ^ A) >> 2) | (A & 0x20);
+ }
+ }
+ }
+ else
+ {
+ DE_ASSERT(iseParams.mode == ISEMODE_PLAIN_BIT);
+ for (int weightNdx = 0; weightNdx < numWeights; weightNdx++)
+ dst[weightNdx] = bitReplicationScale(weightGrid[weightNdx].v, iseParams.numBits, 6);
+ }
+
+ for (int weightNdx = 0; weightNdx < numWeights; weightNdx++)
+ dst[weightNdx] += dst[weightNdx] > 32 ? 1 : 0;
+
+ // Initialize nonexistent weights to poison values
+ for (int weightNdx = numWeights; weightNdx < 64; weightNdx++)
+ dst[weightNdx] = ~0u;
+}
+
+void interpolateWeights (TexelWeightPair* dst, const deUint32 (&unquantizedWeights) [64], int blockWidth, int blockHeight, const ASTCBlockMode& blockMode)
+{
+ const int numWeightsPerTexel = blockMode.isDualPlane ? 2 : 1;
+ const deUint32 scaleX = (1024 + blockWidth/2) / (blockWidth-1);
+ const deUint32 scaleY = (1024 + blockHeight/2) / (blockHeight-1);
+ DE_ASSERT(blockMode.weightGridWidth*blockMode.weightGridHeight*numWeightsPerTexel <= (int)DE_LENGTH_OF_ARRAY(unquantizedWeights));
+
+ for (int texelY = 0; texelY < blockHeight; texelY++)
+ {
+ for (int texelX = 0; texelX < blockWidth; texelX++)
+ {
+ const deUint32 gX = (scaleX*texelX*(blockMode.weightGridWidth-1) + 32) >> 6;
+ const deUint32 gY = (scaleY*texelY*(blockMode.weightGridHeight-1) + 32) >> 6;
+ const deUint32 jX = gX >> 4;
+ const deUint32 jY = gY >> 4;
+ const deUint32 fX = gX & 0xf;
+ const deUint32 fY = gY & 0xf;
+ const deUint32 w11 = (fX*fY + 8) >> 4;
+ const deUint32 w10 = fY - w11;
+ const deUint32 w01 = fX - w11;
+ const deUint32 w00 = 16 - fX - fY + w11;
+ const deUint32 i00 = jY*blockMode.weightGridWidth + jX;
+ const deUint32 i01 = i00 + 1;
+ const deUint32 i10 = i00 + blockMode.weightGridWidth;
+ const deUint32 i11 = i00 + blockMode.weightGridWidth + 1;
+
+ // These addresses can be out of bounds, but respective weights will be 0 then.
+ DE_ASSERT(deInBounds32(i00, 0, blockMode.weightGridWidth*blockMode.weightGridHeight) || w00 == 0);
+ DE_ASSERT(deInBounds32(i01, 0, blockMode.weightGridWidth*blockMode.weightGridHeight) || w01 == 0);
+ DE_ASSERT(deInBounds32(i10, 0, blockMode.weightGridWidth*blockMode.weightGridHeight) || w10 == 0);
+ DE_ASSERT(deInBounds32(i11, 0, blockMode.weightGridWidth*blockMode.weightGridHeight) || w11 == 0);
+
+ for (int texelWeightNdx = 0; texelWeightNdx < numWeightsPerTexel; texelWeightNdx++)
+ {
+ // & 0x3f clamps address to bounds of unquantizedWeights
+ const deUint32 p00 = unquantizedWeights[(i00 * numWeightsPerTexel + texelWeightNdx) & 0x3f];
+ const deUint32 p01 = unquantizedWeights[(i01 * numWeightsPerTexel + texelWeightNdx) & 0x3f];
+ const deUint32 p10 = unquantizedWeights[(i10 * numWeightsPerTexel + texelWeightNdx) & 0x3f];
+ const deUint32 p11 = unquantizedWeights[(i11 * numWeightsPerTexel + texelWeightNdx) & 0x3f];
+
+ dst[texelY*blockWidth + texelX].w[texelWeightNdx] = (p00*w00 + p01*w01 + p10*w10 + p11*w11 + 8) >> 4;
+ }
+ }
+ }
+}
+
+void computeTexelWeights (TexelWeightPair* dst, const Block128& blockData, int blockWidth, int blockHeight, const ASTCBlockMode& blockMode)
+{
+ ISEDecodedResult weightGrid[64];
+
+ {
+ BitAccessStream dataStream(blockData, 127, computeNumRequiredBits(blockMode.weightISEParams, computeNumWeights(blockMode)), false);
+ decodeISE(&weightGrid[0], computeNumWeights(blockMode), dataStream, blockMode.weightISEParams);
+ }
+
+ {
+ deUint32 unquantizedWeights[64];
+ unquantizeWeights(&unquantizedWeights[0], &weightGrid[0], blockMode);
+
+ interpolateWeights(dst, unquantizedWeights, blockWidth, blockHeight, blockMode);
+ }
+}
+
+inline deUint32 hash52 (deUint32 v)
+{
+ deUint32 p = v;
+ p ^= p >> 15; p -= p << 17; p += p << 7; p += p << 4;
+ p ^= p >> 5; p += p << 16; p ^= p >> 7; p ^= p >> 3;
+ p ^= p << 6; p ^= p >> 17;
+ return p;
+}
+
+int computeTexelPartition (deUint32 seedIn, deUint32 xIn, deUint32 yIn, deUint32 zIn, int numPartitions, bool smallBlock)
+{
+ DE_ASSERT(zIn == 0);
+
+ const deUint32 x = smallBlock ? xIn << 1 : xIn;
+ const deUint32 y = smallBlock ? yIn << 1 : yIn;
+ const deUint32 z = smallBlock ? zIn << 1 : zIn;
+ const deUint32 seed = seedIn + 1024*(numPartitions-1);
+ const deUint32 rnum = hash52(seed);
+
+ deUint8 seed1 = (deUint8)( rnum & 0xf);
+ deUint8 seed2 = (deUint8)((rnum >> 4) & 0xf);
+ deUint8 seed3 = (deUint8)((rnum >> 8) & 0xf);
+ deUint8 seed4 = (deUint8)((rnum >> 12) & 0xf);
+ deUint8 seed5 = (deUint8)((rnum >> 16) & 0xf);
+ deUint8 seed6 = (deUint8)((rnum >> 20) & 0xf);
+ deUint8 seed7 = (deUint8)((rnum >> 24) & 0xf);
+ deUint8 seed8 = (deUint8)((rnum >> 28) & 0xf);
+ deUint8 seed9 = (deUint8)((rnum >> 18) & 0xf);
+ deUint8 seed10 = (deUint8)((rnum >> 22) & 0xf);
+ deUint8 seed11 = (deUint8)((rnum >> 26) & 0xf);
+ deUint8 seed12 = (deUint8)(((rnum >> 30) | (rnum << 2)) & 0xf);
+
+ seed1 = (deUint8)(seed1 * seed1 );
+ seed2 = (deUint8)(seed2 * seed2 );
+ seed3 = (deUint8)(seed3 * seed3 );
+ seed4 = (deUint8)(seed4 * seed4 );
+ seed5 = (deUint8)(seed5 * seed5 );
+ seed6 = (deUint8)(seed6 * seed6 );
+ seed7 = (deUint8)(seed7 * seed7 );
+ seed8 = (deUint8)(seed8 * seed8 );
+ seed9 = (deUint8)(seed9 * seed9 );
+ seed10 = (deUint8)(seed10 * seed10);
+ seed11 = (deUint8)(seed11 * seed11);
+ seed12 = (deUint8)(seed12 * seed12);
+
+ const int shA = (seed & 2) != 0 ? 4 : 5;
+ const int shB = numPartitions == 3 ? 6 : 5;
+ const int sh1 = (seed & 1) != 0 ? shA : shB;
+ const int sh2 = (seed & 1) != 0 ? shB : shA;
+ const int sh3 = (seed & 0x10) != 0 ? sh1 : sh2;
+
+ seed1 = (deUint8)(seed1 >> sh1);
+ seed2 = (deUint8)(seed2 >> sh2);
+ seed3 = (deUint8)(seed3 >> sh1);
+ seed4 = (deUint8)(seed4 >> sh2);
+ seed5 = (deUint8)(seed5 >> sh1);
+ seed6 = (deUint8)(seed6 >> sh2);
+ seed7 = (deUint8)(seed7 >> sh1);
+ seed8 = (deUint8)(seed8 >> sh2);
+ seed9 = (deUint8)(seed9 >> sh3);
+ seed10 = (deUint8)(seed10 >> sh3);
+ seed11 = (deUint8)(seed11 >> sh3);
+ seed12 = (deUint8)(seed12 >> sh3);
+
+ const int a = 0x3f & (seed1*x + seed2*y + seed11*z + (rnum >> 14));
+ const int b = 0x3f & (seed3*x + seed4*y + seed12*z + (rnum >> 10));
+ const int c = (numPartitions >= 3) ? 0x3f & (seed5*x + seed6*y + seed9*z + (rnum >> 6)) : 0;
+ const int d = (numPartitions >= 4) ? 0x3f & (seed7*x + seed8*y + seed10*z + (rnum >> 2)) : 0;
+
+ return (a >= b && a >= c && a >= d) ? 0
+ : (b >= c && b >= d) ? 1
+ : (c >= d) ? 2
+ : 3;
+}
+
+DecompressResult setTexelColors (void* dst, ColorEndpointPair* colorEndpoints, TexelWeightPair* texelWeights, int ccs, deUint32 partitionIndexSeed,
+ int numPartitions, int blockWidth, int blockHeight, bool isSRGB, bool isLDRMode, const deUint32* colorEndpointModes)
+{
+ const bool smallBlock = blockWidth*blockHeight < 31;
+ DecompressResult result = DECOMPRESS_RESULT_VALID_BLOCK;
+ bool isHDREndpoint[4];
+
+ for (int i = 0; i < numPartitions; i++)
+ {
+ isHDREndpoint[i] = isColorEndpointModeHDR(colorEndpointModes[i]);
+ }
+
+ for (int texelY = 0; texelY < blockHeight; texelY++)
+ {
+ for (int texelX = 0; texelX < blockWidth; texelX++)
+ {
+ const int texelNdx = texelY * blockWidth + texelX;
+ const int colorEndpointNdx = (numPartitions == 1) ? 0 : computeTexelPartition(partitionIndexSeed, texelX, texelY, 0, numPartitions, smallBlock);
+
+ DE_ASSERT(colorEndpointNdx < numPartitions);
+ const UVec4& e0 = colorEndpoints[colorEndpointNdx].e0;
+ const UVec4& e1 = colorEndpoints[colorEndpointNdx].e1;
+ const TexelWeightPair& weight = texelWeights[texelNdx];
+
+ if (isLDRMode && isHDREndpoint[colorEndpointNdx])
+ {
+ if (isSRGB)
+ {
+ ((deUint8*)dst)[texelNdx * 4 + 0] = 0xff;
+ ((deUint8*)dst)[texelNdx * 4 + 1] = 0;
+ ((deUint8*)dst)[texelNdx * 4 + 2] = 0xff;
+ ((deUint8*)dst)[texelNdx * 4 + 3] = 0xff;
+ }
+ else
+ {
+ ((float*)dst)[texelNdx * 4 + 0] = 1.0f;
+ ((float*)dst)[texelNdx * 4 + 1] = 0;
+ ((float*)dst)[texelNdx * 4 + 2] = 1.0f;
+ ((float*)dst)[texelNdx * 4 + 3] = 1.0f;
+ }
+ result = DECOMPRESS_RESULT_ERROR;
+ }
+ else
+ {
+ for (int channelNdx = 0; channelNdx < 4; channelNdx++)
+ {
+ if (!isHDREndpoint[colorEndpointNdx] || (channelNdx == 3 && colorEndpointModes[colorEndpointNdx] == 14)) // \note Alpha for mode 14 is treated the same as LDR.
+ {
+ const deUint32 c0 = (e0[channelNdx] << 8) | (isSRGB ? 0x80 : e0[channelNdx]);
+ const deUint32 c1 = (e1[channelNdx] << 8) | (isSRGB ? 0x80 : e1[channelNdx]);
+ const deUint32 w = weight.w[ccs == channelNdx ? 1 : 0];
+ const deUint32 c = (c0 * (64 - w) + c1 * w + 32) / 64;
+
+ if (isSRGB)
+ ((deUint8*)dst)[texelNdx * 4 + channelNdx] = (deUint8)((c & 0xff00) >> 8);
+ else
+ ((float*)dst)[texelNdx * 4 + channelNdx] = (c == 65535) ? 1.0f : (float)c / 65536.0f;
+ }
+ else
+ {
+ DE_ASSERT(!isSRGB);
+ //DE_STATIC_ASSERT((basisu_astc::meta::TypesSame<deFloat16, deUint16>::Value));
+
+ const deUint32 c0 = e0[channelNdx] << 4;
+ const deUint32 c1 = e1[channelNdx] << 4;
+ const deUint32 w = weight.w[(ccs == channelNdx) ? 1 : 0];
+ const deUint32 c = (c0 * (64 - w) + c1 * w + 32) / 64;
+ const deUint32 e = getBits(c, 11, 15);
+ const deUint32 m = getBits(c, 0, 10);
+ const deUint32 mt = (m < 512) ? (3 * m)
+ : (m >= 1536) ? (5 * m - 2048)
+ : (4 * m - 512);
+
+ const deFloat16 cf = (deFloat16)((e << 10) + (mt >> 3));
+
+ ((float*)dst)[texelNdx * 4 + channelNdx] = deFloat16To32(isFloat16InfOrNan(cf) ? 0x7bff : cf);
+ }
+
+ } // channelNdx
+ }
+ } // texelX
+ } // texelY
+
+ return result;
+}
+
+DecompressResult decompressBlock (void* dst, const Block128& blockData, int blockWidth, int blockHeight, bool isSRGB, bool isLDR)
+{
+ DE_ASSERT(isLDR || !isSRGB);
+
+ // Decode block mode.
+ const ASTCBlockMode blockMode = getASTCBlockMode(blockData.getBits(0, 10));
+
+ // Check for block mode errors.
+ if (blockMode.isError)
+ {
+ setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
+ return DECOMPRESS_RESULT_ERROR;
+ }
+
+ // Separate path for void-extent.
+ if (blockMode.isVoidExtent)
+ return decodeVoidExtentBlock(dst, blockData, blockWidth, blockHeight, isSRGB, isLDR);
+
+ // Compute weight grid values.
+ const int numWeights = computeNumWeights(blockMode);
+ const int numWeightDataBits = computeNumRequiredBits(blockMode.weightISEParams, numWeights);
+ const int numPartitions = (int)blockData.getBits(11, 12) + 1;
+
+ // Check for errors in weight grid, partition and dual-plane parameters.
+ if ((numWeights > 64) ||
+ (numWeightDataBits > 96) ||
+ (numWeightDataBits < 24) ||
+ (blockMode.weightGridWidth > blockWidth) ||
+ (blockMode.weightGridHeight > blockHeight) ||
+ ((numPartitions == 4) && blockMode.isDualPlane))
+ {
+ setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
+ return DECOMPRESS_RESULT_ERROR;
+ }
+
+ // Compute number of bits available for color endpoint data.
+ const bool isSingleUniqueCem = (numPartitions == 1) || (blockData.getBits(23, 24) == 0);
+
+ const int numConfigDataBits = ((numPartitions == 1) ? 17 : isSingleUniqueCem ? 29 : 25 + 3*numPartitions) +
+ (blockMode.isDualPlane ? 2 : 0);
+
+ const int numBitsForColorEndpoints = 128 - numWeightDataBits - numConfigDataBits;
+
+ const int extraCemBitsStart = 127 - numWeightDataBits - (isSingleUniqueCem ? -1
+ : (numPartitions == 4) ? 7
+ : (numPartitions == 3) ? 4
+ : (numPartitions == 2) ? 1
+ : 0);
+
+ // Decode color endpoint modes.
+ deUint32 colorEndpointModes[4];
+ decodeColorEndpointModes(&colorEndpointModes[0], blockData, numPartitions, extraCemBitsStart);
+ const int numColorEndpointValues = computeNumColorEndpointValues(colorEndpointModes, numPartitions);
+
+ // Check for errors in color endpoint value count.
+ if ((numColorEndpointValues > 18) || (numBitsForColorEndpoints < (int)deDivRoundUp32(13*numColorEndpointValues, 5)))
+ {
+ setASTCErrorColorBlock(dst, blockWidth, blockHeight, isSRGB);
+ return DECOMPRESS_RESULT_ERROR;
+ }
+
+ // Compute color endpoints.
+ ColorEndpointPair colorEndpoints[4];
+ computeColorEndpoints(&colorEndpoints[0], blockData, &colorEndpointModes[0], numPartitions, numColorEndpointValues,
+ computeMaximumRangeISEParams(numBitsForColorEndpoints, numColorEndpointValues), numBitsForColorEndpoints);
+
+ // Compute texel weights.
+ TexelWeightPair texelWeights[MAX_BLOCK_WIDTH*MAX_BLOCK_HEIGHT];
+ computeTexelWeights(&texelWeights[0], blockData, blockWidth, blockHeight, blockMode);
+
+ // Set texel colors.
+ const int ccs = blockMode.isDualPlane ? (int)blockData.getBits(extraCemBitsStart-2, extraCemBitsStart-1) : -1;
+ const deUint32 partitionIndexSeed = (numPartitions > 1) ? blockData.getBits(13, 22) : (deUint32)-1;
+
+ return setTexelColors(dst, &colorEndpoints[0], &texelWeights[0], ccs, partitionIndexSeed, numPartitions, blockWidth, blockHeight, isSRGB, isLDR, &colorEndpointModes[0]);
+}
+
+// Returns -1 on error, 0 if LDR, 1 if HDR
+int isHDR(const Block128& blockData, int blockWidth, int blockHeight)
+{
+ // Decode block mode.
+ const ASTCBlockMode blockMode = getASTCBlockMode(blockData.getBits(0, 10));
+
+ // Check for block mode errors.
+ if (blockMode.isError)
+ return -1;
+
+ // Separate path for void-extent.
+ if (blockMode.isVoidExtent)
+ {
+ const bool isHDRBlock = blockData.isBitSet(9);
+ return isHDRBlock ? 1 : 0;
+ }
+
+ // Compute weight grid values.
+ const int numWeights = computeNumWeights(blockMode);
+ const int numWeightDataBits = computeNumRequiredBits(blockMode.weightISEParams, numWeights);
+ const int numPartitions = (int)blockData.getBits(11, 12) + 1;
+
+ // Check for errors in weight grid, partition and dual-plane parameters.
+ if ((numWeights > 64) ||
+ (numWeightDataBits > 96) ||
+ (numWeightDataBits < 24) ||
+ (blockMode.weightGridWidth > blockWidth) ||
+ (blockMode.weightGridHeight > blockHeight) ||
+ ((numPartitions == 4) && blockMode.isDualPlane))
+ {
+ return -1;
+ }
+
+ // Compute number of bits available for color endpoint data.
+ const bool isSingleUniqueCem = (numPartitions == 1) || (blockData.getBits(23, 24) == 0);
+
+ const int extraCemBitsStart = 127 - numWeightDataBits - (isSingleUniqueCem ? -1
+ : (numPartitions == 4) ? 7
+ : (numPartitions == 3) ? 4
+ : (numPartitions == 2) ? 1
+ : 0);
+
+ // Decode color endpoint modes.
+ deUint32 colorEndpointModes[4];
+ decodeColorEndpointModes(&colorEndpointModes[0], blockData, numPartitions, extraCemBitsStart);
+
+ for (int i = 0; i < numPartitions; i++)
+ {
+ if (isColorEndpointModeHDR(colorEndpointModes[i]))
+ return 1;
+ }
+
+ return 0;
+}
+
+typedef uint16_t half_float;
+
+half_float float_to_half(float val, bool toward_zero)
+{
+ union { float f; int32_t i; uint32_t u; } fi = { val };
+ const int flt_m = fi.i & 0x7FFFFF, flt_e = (fi.i >> 23) & 0xFF, flt_s = (fi.i >> 31) & 0x1;
+ int s = flt_s, e = 0, m = 0;
+
+ // inf/NaN
+ if (flt_e == 0xff)
+ {
+ e = 31;
+ if (flt_m != 0) // NaN
+ m = 1;
+ }
+ // not zero or denormal
+ else if (flt_e != 0)
+ {
+ int new_exp = flt_e - 127;
+ if (new_exp > 15)
+ e = 31;
+ else if (new_exp < -14)
+ {
+ if (toward_zero)
+ m = (int)truncf((1 << 24) * fabsf(fi.f));
+ else
+ m = lrintf((1 << 24) * fabsf(fi.f));
+ }
+ else
+ {
+ e = new_exp + 15;
+ if (toward_zero)
+ m = (int)truncf((float)flt_m * (1.0f / (float)(1 << 13)));
+ else
+ m = lrintf((float)flt_m * (1.0f / (float)(1 << 13)));
+ }
+ }
+
+ assert((0 <= m) && (m <= 1024));
+ if (m == 1024)
+ {
+ e++;
+ m = 0;
+ }
+
+ assert((s >= 0) && (s <= 1));
+ assert((e >= 0) && (e <= 31));
+ assert((m >= 0) && (m <= 1023));
+
+ half_float result = (half_float)((s << 15) | (e << 10) | m);
+ return result;
+}
+
+float half_to_float(half_float hval)
+{
+ union { float f; uint32_t u; } x = { 0 };
+
+ uint32_t s = ((uint32_t)hval >> 15) & 1;
+ uint32_t e = ((uint32_t)hval >> 10) & 0x1F;
+ uint32_t m = (uint32_t)hval & 0x3FF;
+
+ if (!e)
+ {
+ if (!m)
+ {
+ // +- 0
+ x.u = s << 31;
+ return x.f;
+ }
+ else
+ {
+ // denormalized
+ while (!(m & 0x00000400))
+ {
+ m <<= 1;
+ --e;
+ }
+
+ ++e;
+ m &= ~0x00000400;
+ }
+ }
+ else if (e == 31)
+ {
+ if (m == 0)
+ {
+ // +/- INF
+ x.u = (s << 31) | 0x7f800000;
+ return x.f;
+ }
+ else
+ {
+ // +/- NaN
+ x.u = (s << 31) | 0x7f800000 | (m << 13);
+ return x.f;
+ }
+ }
+
+ e = e + (127 - 15);
+ m = m << 13;
+
+ assert(s <= 1);
+ assert(m <= 0x7FFFFF);
+ assert(e <= 255);
+
+ x.u = m | (e << 23) | (s << 31);
+ return x.f;
+}
+
+} // anonymous
+
+// See https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#_hdr_endpoint_decoding
+static void convert_to_half_prec(uint32_t n, float* pVals)
+{
+#if 0
+ const int prev_dir = fesetround(FE_TOWARDZERO);
+
+ for (uint32_t i = 0; i < n; i++)
+ pVals[i] = half_to_float(float_to_half(pVals[i]));
+
+ fesetround(prev_dir);
+
+ for (uint32_t i = 0; i < n; i++)
+ {
+ assert(pVals[i] == half_to_float(float_to_half(pVals[i], true)));
+ }
+#else
+ // This ensures the values are rounded towards zero as half floats.
+ for (uint32_t i = 0; i < n; i++)
+ {
+ pVals[i] = half_to_float(float_to_half(pVals[i], true));
+ }
+#endif
+}
+
+bool decompress_ldr(uint8_t *pDst, const uint8_t * data, bool isSRGB, int blockWidth, int blockHeight)
+{
+ float linear[MAX_BLOCK_WIDTH * MAX_BLOCK_HEIGHT * 4];
+
+ const Block128 blockData(data);
+
+ // isSRGB is true, this writes uint8_t's. Otherwise it writes floats.
+ if (decompressBlock(isSRGB ? (void*)pDst : (void*)&linear[0], blockData, blockWidth, blockHeight, isSRGB, true) != DECOMPRESS_RESULT_VALID_BLOCK)
+ {
+ return false;
+ }
+
+ if (!isSRGB)
+ {
+ // Convert the floats to 8-bits with rounding.
+ int pix = 0;
+ for (int i = 0; i < blockHeight; i++)
+ {
+ for (int j = 0; j < blockWidth; j++, pix++)
+ {
+ pDst[4 * pix + 0] = (uint8_t)(basisu_astc::clamp<int>((int)(linear[pix * 4 + 0] * 65536.0f + .5f), 0, 65535) >> 8);
+ pDst[4 * pix + 1] = (uint8_t)(basisu_astc::clamp<int>((int)(linear[pix * 4 + 1] * 65536.0f + .5f), 0, 65535) >> 8);
+ pDst[4 * pix + 2] = (uint8_t)(basisu_astc::clamp<int>((int)(linear[pix * 4 + 2] * 65536.0f + .5f), 0, 65535) >> 8);
+ pDst[4 * pix + 3] = (uint8_t)(basisu_astc::clamp<int>((int)(linear[pix * 4 + 3] * 65536.0f + .5f), 0, 65535) >> 8);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool decompress_hdr(float* pDstRGBA, const uint8_t* data, int blockWidth, int blockHeight)
+{
+ const Block128 blockData(data);
+
+ if (decompressBlock(pDstRGBA, blockData, blockWidth, blockHeight, false, false) != DECOMPRESS_RESULT_VALID_BLOCK)
+ {
+ return false;
+ }
+
+ convert_to_half_prec(blockWidth * blockHeight * 4, pDstRGBA);
+
+ return true;
+}
+
+bool is_hdr(const uint8_t* data, int blockWidth, int blockHeight, bool &is_hdr)
+{
+ is_hdr = false;
+
+ const Block128 blockData(data);
+
+ int status = isHDR(blockData, blockWidth, blockHeight);
+ if (status < 0)
+ {
+ return false;
+ }
+
+ is_hdr = (status == 1);
+
+ return true;
+}
+
+} // astc
+
+} // basisu_astc
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
diff --git a/thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.h b/thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.h
new file mode 100644
index 0000000000..ad13093a6c
--- /dev/null
+++ b/thirdparty/basis_universal/encoder/3rdparty/android_astc_decomp.h
@@ -0,0 +1,45 @@
+// File: android_astc_decomp.h
+#ifndef _TCUASTCUTIL_HPP
+#define _TCUASTCUTIL_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Tester Core
+ * ----------------------------------------
+ *
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *//*!
+ * \file
+ * \brief ASTC Utilities.
+ *//*--------------------------------------------------------------------*/
+
+#include <vector>
+#include <stdint.h>
+
+namespace basisu_astc
+{
+namespace astc
+{
+
+// Unpacks a single ASTC block to pDst
+// If isSRGB is true, the spec requires the decoder to scale the LDR 8-bit endpoints to 16-bit before interpolation slightly differently,
+// which will lead to different outputs. So be sure to set it correctly (ideally it should match whatever the encoder did).
+bool decompress_ldr(uint8_t* pDst, const uint8_t* data, bool isSRGB, int blockWidth, int blockHeight);
+bool decompress_hdr(float* pDstRGBA, const uint8_t* data, int blockWidth, int blockHeight);
+bool is_hdr(const uint8_t* data, int blockWidth, int blockHeight, bool& is_hdr);
+
+} // astc
+} // basisu
+
+#endif
diff --git a/thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.cpp b/thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.cpp
new file mode 100644
index 0000000000..d698a7ff87
--- /dev/null
+++ b/thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.cpp
@@ -0,0 +1,3310 @@
+// basisu_astc_hdr_enc.cpp
+#include "basisu_astc_hdr_enc.h"
+#include "../transcoder/basisu_transcoder.h"
+
+using namespace basist;
+
+namespace basisu
+{
+
+const float DEF_R_ERROR_SCALE = 2.0f;
+const float DEF_G_ERROR_SCALE = 3.0f;
+
+static inline uint32_t get_max_qlog(uint32_t bits)
+{
+ switch (bits)
+ {
+ case 7: return MAX_QLOG7;
+ case 8: return MAX_QLOG8;
+ case 9: return MAX_QLOG9;
+ case 10: return MAX_QLOG10;
+ case 11: return MAX_QLOG11;
+ case 12: return MAX_QLOG12;
+ case 16: return MAX_QLOG16;
+ default: assert(0); break;
+ }
+ return 0;
+}
+
+#if 0
+static inline float get_max_qlog_val(uint32_t bits)
+{
+ switch (bits)
+ {
+ case 7: return MAX_QLOG7_VAL;
+ case 8: return MAX_QLOG8_VAL;
+ case 9: return MAX_QLOG9_VAL;
+ case 10: return MAX_QLOG10_VAL;
+ case 11: return MAX_QLOG11_VAL;
+ case 12: return MAX_QLOG12_VAL;
+ case 16: return MAX_QLOG16_VAL;
+ default: assert(0); break;
+ }
+ return 0;
+}
+#endif
+
+static inline int get_bit(
+ int src_val, int src_bit)
+{
+ assert(src_bit >= 0 && src_bit <= 31);
+ int bit = (src_val >> src_bit) & 1;
+ return bit;
+}
+
+static inline void pack_bit(
+ int& dst, int dst_bit,
+ int src_val, int src_bit = 0)
+{
+ assert(dst_bit >= 0 && dst_bit <= 31);
+ int bit = get_bit(src_val, src_bit);
+ dst |= (bit << dst_bit);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+astc_hdr_codec_options::astc_hdr_codec_options()
+{
+ init();
+}
+
+void astc_hdr_codec_options::init()
+{
+ m_bc6h_err_weight = .85f;
+ m_r_err_scale = DEF_R_ERROR_SCALE;
+ m_g_err_scale = DEF_G_ERROR_SCALE;
+
+ // Disabling by default to avoid transcoding outliers (try kodim26). The quality lost is very low. TODO: Could include the uber result in the output.
+ m_allow_uber_mode = false;
+
+ // Must set best quality level first to set defaults.
+ set_quality_best();
+
+ set_quality_level(cDefaultLevel);
+}
+
+void astc_hdr_codec_options::set_quality_best()
+{
+ m_mode11_direct_only = false;
+
+ // highest achievable quality
+ m_use_solid = true;
+
+ m_use_mode11 = true;
+ m_mode11_uber_mode = true;
+ m_first_mode11_weight_ise_range = MODE11_FIRST_ISE_RANGE;
+ m_last_mode11_weight_ise_range = MODE11_LAST_ISE_RANGE;
+ m_first_mode11_submode = -1;
+ m_last_mode11_submode = 7;
+
+ m_use_mode7_part1 = true;
+ m_first_mode7_part1_weight_ise_range = MODE7_PART1_FIRST_ISE_RANGE;
+ m_last_mode7_part1_weight_ise_range = MODE7_PART1_LAST_ISE_RANGE;
+
+ m_use_mode7_part2 = true;
+ m_mode7_part2_part_masks = UINT32_MAX;
+ m_first_mode7_part2_weight_ise_range = MODE7_PART2_FIRST_ISE_RANGE;
+ m_last_mode7_part2_weight_ise_range = MODE7_PART2_LAST_ISE_RANGE;
+
+ m_use_mode11_part2 = true;
+ m_mode11_part2_part_masks = UINT32_MAX;
+ m_first_mode11_part2_weight_ise_range = MODE11_PART2_FIRST_ISE_RANGE;
+ m_last_mode11_part2_weight_ise_range = MODE11_PART2_LAST_ISE_RANGE;
+
+ m_refine_weights = true;
+
+ m_use_estimated_partitions = false;
+ m_max_estimated_partitions = 0;
+}
+
+void astc_hdr_codec_options::set_quality_normal()
+{
+ m_use_solid = true;
+
+ // We'll allow uber mode in normal if the user allows it.
+ m_use_mode11 = true;
+ m_mode11_uber_mode = true;
+ m_first_mode11_weight_ise_range = 6;
+ m_last_mode11_weight_ise_range = MODE11_LAST_ISE_RANGE;
+
+ m_use_mode7_part1 = true;
+ m_first_mode7_part1_weight_ise_range = MODE7_PART1_LAST_ISE_RANGE;
+ m_last_mode7_part1_weight_ise_range = MODE7_PART1_LAST_ISE_RANGE;
+
+ m_use_mode7_part2 = true;
+ m_mode7_part2_part_masks = UINT32_MAX;
+ m_first_mode7_part2_weight_ise_range = MODE7_PART2_LAST_ISE_RANGE;
+ m_last_mode7_part2_weight_ise_range = MODE7_PART2_LAST_ISE_RANGE;
+
+ m_use_mode11_part2 = true;
+ m_mode11_part2_part_masks = UINT32_MAX;
+ m_first_mode11_part2_weight_ise_range = MODE11_PART2_LAST_ISE_RANGE;
+ m_last_mode11_part2_weight_ise_range = MODE11_PART2_LAST_ISE_RANGE;
+
+ m_refine_weights = true;
+}
+
+void astc_hdr_codec_options::set_quality_fastest()
+{
+ m_use_solid = true;
+
+ m_use_mode11 = true;
+ m_mode11_uber_mode = false;
+ m_first_mode11_weight_ise_range = MODE11_LAST_ISE_RANGE;
+ m_last_mode11_weight_ise_range = MODE11_LAST_ISE_RANGE;
+
+ m_use_mode7_part1 = false;
+ m_use_mode7_part2 = false;
+ m_use_mode11_part2 = false;
+
+ m_refine_weights = false;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+void astc_hdr_codec_options::set_quality_level(int level)
+{
+ level = clamp(level, cMinLevel, cMaxLevel);
+
+ m_level = level;
+
+ switch (level)
+ {
+ case 0:
+ {
+ set_quality_fastest();
+ break;
+ }
+ case 1:
+ {
+ set_quality_normal();
+
+ m_first_mode11_weight_ise_range = MODE11_LAST_ISE_RANGE - 1;
+ m_last_mode11_weight_ise_range = MODE11_LAST_ISE_RANGE;
+
+ m_use_mode7_part1 = false;
+ m_use_mode7_part2 = false;
+
+ m_use_estimated_partitions = true;
+ m_max_estimated_partitions = 1;
+
+ m_mode11_part2_part_masks = 1 | 2;
+ m_mode7_part2_part_masks = 1 | 2;
+ break;
+ }
+ case 2:
+ {
+ set_quality_normal();
+
+ m_use_estimated_partitions = true;
+ m_max_estimated_partitions = 2;
+
+ m_mode11_part2_part_masks = 1 | 2;
+ m_mode7_part2_part_masks = 1 | 2;
+
+ break;
+ }
+ case 3:
+ {
+ set_quality_best();
+
+ m_use_estimated_partitions = true;
+ m_max_estimated_partitions = 2;
+
+ m_mode11_part2_part_masks = 1 | 2 | 4 | 8;
+ m_mode7_part2_part_masks = 1 | 2 | 4 | 8;
+
+ break;
+ }
+ case 4:
+ {
+ set_quality_best();
+
+ break;
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+#if 0
+static inline half_float qlog12_to_half_slow(uint32_t qlog12)
+{
+ return qlog_to_half_slow(qlog12, 12);
+}
+#endif
+
+// max usable qlog8 value is 247, 248=inf, >=249 is nan
+// max usable qlog7 value is 123, 124=inf, >=125 is nan
+
+// To go from a smaller qlog to an larger one, shift left by X bits.
+
+//const uint32_t TOTAL_USABLE_QLOG8 = 248; // 0-247 are usable, 0=0, 247=60416.0, 246=55296.0
+
+// for qlog7's shift left by 1
+//half_float g_qlog8_to_half[256];
+//float g_qlog8_to_float[256];
+
+//half_float g_qlog12_to_half[4096];
+//float g_qlog12_to_float[4096];
+
+static half_float g_qlog16_to_half[65536];
+
+inline half_float qlog_to_half(uint32_t val, uint32_t bits)
+{
+ assert((bits >= 5) && (bits <= 16));
+ assert(val < (1U << bits));
+ return g_qlog16_to_half[val << (16 - bits)];
+}
+
+// nearest values given a positive half float value (only)
+static uint16_t g_half_to_qlog7[32768], g_half_to_qlog8[32768], g_half_to_qlog9[32768], g_half_to_qlog10[32768], g_half_to_qlog11[32768], g_half_to_qlog12[32768];
+
+const uint32_t HALF_TO_QLOG_TABS_BASE = 7;
+static uint16_t* g_pHalf_to_qlog_tabs[8] =
+{
+ g_half_to_qlog7,
+ g_half_to_qlog8,
+
+ g_half_to_qlog9,
+ g_half_to_qlog10,
+
+ g_half_to_qlog11,
+ g_half_to_qlog12
+};
+
+static inline uint32_t half_to_qlog7_12(half_float h, uint32_t bits)
+{
+ assert((bits >= HALF_TO_QLOG_TABS_BASE) && (bits <= 12));
+ assert(h < 32768);
+
+ return g_pHalf_to_qlog_tabs[bits - HALF_TO_QLOG_TABS_BASE][h];
+}
+
+#if 0
+// Input is the low 11 bits of the qlog
+// Returns the 10-bit mantissa of the half float value
+static int qlog11_to_half_float_mantissa(int M)
+{
+ assert(M <= 0x7FF);
+ int Mt;
+ if (M < 512)
+ Mt = 3 * M;
+ else if (M >= 1536)
+ Mt = 5 * M - 2048;
+ else
+ Mt = 4 * M - 512;
+ return (Mt >> 3);
+}
+#endif
+
+// Input is the 10-bit mantissa of the half float value
+// Output is the 11-bit qlog value
+// Inverse of qlog11_to_half_float_mantissa()
+static inline int half_float_mantissa_to_qlog11(int hf)
+{
+ int q0 = (hf * 8 + 2) / 3;
+ int q1 = (hf * 8 + 2048 + 4) / 5;
+
+ if (q0 < 512)
+ return q0;
+ else if (q1 >= 1536)
+ return q1;
+
+ int q2 = (hf * 8 + 512 + 2) / 4;
+ return q2;
+}
+
+static inline int half_to_qlog16(int hf)
+{
+ // extract 5 bits exponent, which is carried through to qlog16 unchanged
+ const int exp = (hf >> 10) & 0x1F;
+
+ // extract and invert the 10 bit mantissa to nearest qlog11 (should be lossless)
+ const int mantissa = half_float_mantissa_to_qlog11(hf & 0x3FF);
+ assert(mantissa <= 0x7FF);
+
+ // Now combine to qlog16, which is what ASTC HDR interpolates using the [0-64] weights.
+ uint32_t qlog16 = (exp << 11) | mantissa;
+
+ // should be a lossless operation
+ assert(qlog16_to_half_slow(qlog16) == hf);
+
+ return qlog16;
+}
+
+static inline uint32_t quant_qlog16(uint32_t q16, uint32_t desired_bits)
+{
+ assert((desired_bits >= 7) && (desired_bits <= 12));
+ assert(q16 <= 65535);
+
+ const uint32_t shift = 16 - desired_bits;
+ uint32_t e = (q16 + (1U << (shift - 1U)) - 1U) >> shift;
+
+ uint32_t max_val = (1U << desired_bits) - 1U;
+ e = minimum<uint32_t>(e, max_val);
+
+ return e;
+}
+
+static void compute_half_to_qlog_table(uint32_t bits, uint16_t* pTable, const basisu::vector<float> &qlog16_to_float)
+{
+ assert(bits >= 5 && bits <= 12);
+ const uint32_t max_val = (1 << bits) - 1;
+
+ // For all positive half-floats
+ for (uint32_t h = 0; h < 32768; h++)
+ {
+ // Skip invalid values
+ if (is_half_inf_or_nan((half_float)h))
+ continue;
+ const float desired_val = half_to_float((half_float)h);
+
+ float best_err = 1e+30f;
+ uint32_t best_qlog = 0;
+
+ // For all possible qlog's
+ for (uint32_t i = 0; i <= max_val; i++)
+ {
+ // Skip invalid values
+ float v = qlog16_to_float[i << (16 - bits)];
+ if (std::isnan(v))
+ continue;
+
+ // Compute error
+ float err = fabs(v - desired_val);
+
+ // Find best
+ if (err < best_err)
+ {
+ best_err = err;
+ best_qlog = i;
+ }
+ }
+
+ pTable[h] = (uint16_t)best_qlog;
+ }
+
+#if 0
+ uint32_t t = 0;
+
+ const uint32_t nb = 12;
+ int nb_shift = 16 - nb;
+
+ for (uint32_t q16 = 0; q16 < 65536; q16++)
+ {
+ half_float h = qlog16_to_half_slow(q16);
+ if (is_half_inf_or_nan(h))
+ continue;
+
+ int q7 = half_to_qlog7_12(h, nb);
+
+ uint32_t best_err = UINT32_MAX, best_l = 0;
+ for (int l = 0; l < (1 << nb); l++)
+ {
+ int dec_q16 = l << nb_shift;
+ int err = iabs(dec_q16 - q16);
+ if (err < best_err)
+ {
+ best_err = err;
+ best_l = l;
+ }
+ }
+
+ //int e = (q16 + 253) >> 9; // 345
+
+ int e = (q16 + (1 << (nb_shift - 1)) - 1) >> nb_shift; // 285
+ if (best_l != e)
+ //if (q7 != best_l)
+ {
+ printf("q16=%u, h=%u, q7=%u, e=%u, best_l=%u\n", q16, h, q7, e, best_l);
+ t++;
+ }
+ }
+
+ printf("Mismatches: %u\n", t);
+ exit(0);
+#endif
+}
+
+static void init_qlog_tables()
+{
+ basisu::vector<float> qlog16_to_float(65536);
+
+ // for all possible qlog16, compute the corresponding half float
+ for (uint32_t i = 0; i <= 65535; i++)
+ {
+ half_float h = qlog16_to_half_slow(i);
+ g_qlog16_to_half[i] = h;
+
+ qlog16_to_float[i] = half_to_float(h);
+ }
+
+ // for all possible half floats, find the nearest qlog5-12 float
+ for (uint32_t bits = HALF_TO_QLOG_TABS_BASE; bits <= 12; bits++)
+ {
+ compute_half_to_qlog_table(bits, g_pHalf_to_qlog_tabs[bits - HALF_TO_QLOG_TABS_BASE], qlog16_to_float);
+ }
+}
+
+// [ise_range][0] = # levels
+// [ise_range][1...] = lerp value [0,64]
+// in ASTC order
+// Supported ISE weight ranges: 0 to 10, 11 total
+const uint32_t MIN_SUPPORTED_ISE_WEIGHT_INDEX = 1; // ISE 1=3 levels
+const uint32_t MAX_SUPPORTED_ISE_WEIGHT_INDEX = 10; // ISE 10=24 levels
+
+static const uint8_t g_ise_weight_lerps[MAX_SUPPORTED_ISE_WEIGHT_INDEX + 1][32] =
+{
+ { 0 }, // ise range=0 is invalid for 4x4 block sizes (<24 weight bits in the block)
+ { 3, 0, 32, 64 }, // 1
+ { 4, 0, 21, 43, 64 }, // 2
+ { 5, 0, 16, 32, 48, 64 }, // 3
+ { 6, 0, 64, 12, 52, 25, 39 }, // 4
+ { 8, 0, 9, 18, 27, 37, 46, 55, 64 }, // 5
+ { 10, 0, 64, 7, 57, 14, 50, 21, 43, 28, 36 }, // 6
+ { 12, 0, 64, 17, 47, 5, 59, 23, 41, 11, 53, 28, 36 }, // 7
+ { 16, 0, 4, 8, 12, 17, 21, 25, 29, 35, 39, 43, 47, 52, 56, 60, 64 }, // 8
+ { 20, 0, 64, 16, 48, 3, 61, 19, 45, 6, 58, 23, 41, 9, 55, 26, 38, 13, 51, 29, 35 }, // 9
+ { 24, 0, 64, 8, 56, 16, 48, 24, 40, 2, 62, 11, 53, 19, 45, 27, 37, 5, 59, 13, 51, 22, 42, 30, 34 } // 10
+};
+
+//{ 12, 0, 64, 17, 47, 5, 59, 23, 41, 11, 53, 28, 36 }, // 7
+//static const uint8_t g_weight_order_7[12] = { 0, 4, 8, 2, 6, 10, 11, 7, 3, 9, 5, 1 };
+
+static vec3F calc_mean(uint32_t num_pixels, const vec4F* pPixels)
+{
+ vec3F mean(0.0f);
+
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ const vec4F& p = pPixels[i];
+
+ mean[0] += p[0];
+ mean[1] += p[1];
+ mean[2] += p[2];
+ }
+
+ return mean / static_cast<float>(num_pixels);
+}
+
+static vec3F calc_rgb_pca(uint32_t num_pixels, const vec4F* pPixels, const vec3F& mean_color)
+{
+ float cov[6] = { 0, 0, 0, 0, 0, 0 };
+
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ const vec4F& v = pPixels[i];
+
+ float r = v[0] - mean_color[0];
+ float g = v[1] - mean_color[1];
+ float b = v[2] - mean_color[2];
+
+ cov[0] += r * r;
+ cov[1] += r * g;
+ cov[2] += r * b;
+ cov[3] += g * g;
+ cov[4] += g * b;
+ cov[5] += b * b;
+ }
+
+ float xr = .9f, xg = 1.0f, xb = .7f;
+ for (uint32_t iter = 0; iter < 3; iter++)
+ {
+ float r = xr * cov[0] + xg * cov[1] + xb * cov[2];
+ float g = xr * cov[1] + xg * cov[3] + xb * cov[4];
+ float b = xr * cov[2] + xg * cov[4] + xb * cov[5];
+
+ float m = maximumf(maximumf(fabsf(r), fabsf(g)), fabsf(b));
+
+ if (m > 1e-10f)
+ {
+ m = 1.0f / m;
+
+ r *= m;
+ g *= m;
+ b *= m;
+ }
+
+ xr = r;
+ xg = g;
+ xb = b;
+ }
+
+ float len = xr * xr + xg * xg + xb * xb;
+
+ vec3F axis;
+ if (len < 1e-10f)
+ axis.set(0.0f);
+ else
+ {
+ len = 1.0f / sqrtf(len);
+
+ xr *= len;
+ xg *= len;
+ xb *= len;
+
+ axis.set(xr, xg, xb, 0);
+ }
+
+ if (axis.dot(axis) < .5f)
+ {
+ axis.set(1.0f, 1.0f, 1.0f, 0.0f);
+ axis.normalize_in_place();
+ }
+
+ return axis;
+}
+
+static vec3F interp_color(const vec3F& mean, const vec3F& dir, float df, const aabb3F& colorspace_box, const aabb3F& input_box, bool* pInside = nullptr)
+{
+#if 0
+ assert(mean[0] >= input_box[0][0]);
+ assert(mean[1] >= input_box[0][1]);
+ assert(mean[2] >= input_box[0][2]);
+ assert(mean[0] <= input_box[1][0]);
+ assert(mean[1] <= input_box[1][1]);
+ assert(mean[2] <= input_box[1][2]);
+#endif
+
+ if (pInside)
+ *pInside = false;
+
+ vec3F k(mean + dir * df);
+ if (colorspace_box.contains(k))
+ {
+ if (pInside)
+ *pInside = true;
+
+ return k;
+ }
+
+ // starts inside
+ vec3F s(mean);
+
+ // ends outside
+ vec3F e(mean + dir * df);
+
+ // a ray guaranteed to go from the outside to inside
+ ray3F r(e, (s - e).normalize_in_place());
+ vec3F c;
+ float t = 0.0f;
+
+ intersection::result res = intersection::ray_aabb(c, t, r, input_box);
+ if (res != intersection::cSuccess)
+ c = k;
+
+ return c;
+}
+
+// all in Q16 space, 0-65535
+static bool compute_least_squares_endpoints_rgb(
+ uint32_t N, const uint8_t* pSelectors, const vec4F* pSelector_weights,
+ vec3F* pXl, vec3F* pXh, const vec4F* pColors, const aabb3F& input_box)
+{
+ // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf
+ // https://web.archive.org/web/20150319232457/http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf
+ // I did this in matrix form first, expanded out all the ops, then optimized it a bit.
+ float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f;
+ float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f;
+ float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f;
+ float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f;
+
+ for (uint32_t i = 0; i < N; i++)
+ {
+ const uint32_t sel = pSelectors[i];
+ z00 += pSelector_weights[sel][0];
+ z10 += pSelector_weights[sel][1];
+ z11 += pSelector_weights[sel][2];
+
+ float w = pSelector_weights[sel][3];
+ q00_r += w * pColors[i][0];
+ t_r += pColors[i][0];
+
+ q00_g += w * pColors[i][1];
+ t_g += pColors[i][1];
+
+ q00_b += w * pColors[i][2];
+ t_b += pColors[i][2];
+ }
+
+ q10_r = t_r - q00_r;
+ q10_g = t_g - q00_g;
+ q10_b = t_b - q00_b;
+
+ z01 = z10;
+
+ float det = z00 * z11 - z01 * z10;
+ if (det == 0.0f)
+ return false;
+
+ det = 1.0f / det;
+
+ float iz00, iz01, iz10, iz11;
+ iz00 = z11 * det;
+ iz01 = -z01 * det;
+ iz10 = -z10 * det;
+ iz11 = z00 * det;
+
+ (*pXl)[0] = (float)(iz00 * q00_r + iz01 * q10_r);
+ (*pXh)[0] = (float)(iz10 * q00_r + iz11 * q10_r);
+
+ (*pXl)[1] = (float)(iz00 * q00_g + iz01 * q10_g);
+ (*pXh)[1] = (float)(iz10 * q00_g + iz11 * q10_g);
+
+ (*pXl)[2] = (float)(iz00 * q00_b + iz01 * q10_b);
+ (*pXh)[2] = (float)(iz10 * q00_b + iz11 * q10_b);
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ float l = (*pXl)[c], h = (*pXh)[c];
+
+ if (input_box.get_dim(c) < .0000125f)
+ {
+ l = input_box[0][c];
+ h = input_box[1][c];
+ }
+
+ (*pXl)[c] = l;
+ (*pXh)[c] = h;
+ }
+
+ vec3F mean((*pXl + *pXh) * .5f);
+ vec3F dir(*pXh - *pXl);
+
+ float ln = dir.length();
+ if (ln)
+ {
+ dir /= ln;
+
+ float ld = (*pXl - mean).dot(dir);
+ float hd = (*pXh - mean).dot(dir);
+
+ aabb3F colorspace_box(vec3F(0.0f), vec3F(MAX_QLOG16_VAL));
+
+ bool was_inside1 = false;
+
+ vec3F l = interp_color(mean, dir, ld, colorspace_box, input_box, &was_inside1);
+ if (!was_inside1)
+ *pXl = l;
+
+ bool was_inside2 = false;
+ vec3F h = interp_color(mean, dir, hd, colorspace_box, input_box, &was_inside2);
+ if (!was_inside2)
+ *pXh = h;
+ }
+
+ pXl->clamp(0.0f, MAX_QLOG16_VAL);
+ pXh->clamp(0.0f, MAX_QLOG16_VAL);
+
+ return true;
+}
+
+static vec4F g_astc_ls_weights_ise[MAX_SUPPORTED_ISE_WEIGHT_INDEX + 1][24];
+
+static uint8_t g_map_astc_to_linear_order[MAX_SUPPORTED_ISE_WEIGHT_INDEX + 1][24]; // [ise_range][astc_index] -> linear index
+static uint8_t g_map_linear_to_astc_order[MAX_SUPPORTED_ISE_WEIGHT_INDEX + 1][24]; // [ise_range][linear_index] -> astc_index
+
+static void encode_astc_hdr_init()
+{
+ // Precomputed weight constants used during least fit determination. For each entry: w * w, (1.0f - w) * w, (1.0f - w) * (1.0f - w), w
+ for (uint32_t range = MIN_SUPPORTED_ISE_WEIGHT_INDEX; range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX; range++)
+ {
+ const uint32_t num_levels = g_ise_weight_lerps[range][0];
+ assert((num_levels >= 3) && (num_levels <= 24));
+
+ for (uint32_t i = 0; i < num_levels; i++)
+ {
+ float w = g_ise_weight_lerps[range][1 + i] * (1.0f / 64.0f);
+
+ g_astc_ls_weights_ise[range][i].set(w * w, (1.0f - w) * w, (1.0f - w) * (1.0f - w), w);
+ }
+ }
+
+ for (uint32_t ise_range = MIN_SUPPORTED_ISE_WEIGHT_INDEX; ise_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX; ise_range++)
+ {
+ const uint32_t num_levels = g_ise_weight_lerps[ise_range][0];
+ assert((num_levels >= 3) && (num_levels <= 24));
+
+ uint32_t s[32];
+ for (uint32_t i = 0; i < num_levels; i++)
+ s[i] = (g_ise_weight_lerps[ise_range][1 + i] << 8) + i;
+
+ std::sort(s, s + num_levels);
+
+ for (uint32_t i = 0; i < num_levels; i++)
+ g_map_linear_to_astc_order[ise_range][i] = (uint8_t)(s[i] & 0xFF);
+
+ for (uint32_t i = 0; i < num_levels; i++)
+ g_map_astc_to_linear_order[ise_range][g_map_linear_to_astc_order[ise_range][i]] = (uint8_t)i;
+ }
+}
+
+void interpolate_qlog12_colors(
+ const int e[2][3],
+ half_float* pDecoded_half,
+ vec3F* pDecoded_float,
+ uint32_t n, uint32_t ise_weight_range)
+{
+ assert((ise_weight_range >= MIN_SUPPORTED_ISE_WEIGHT_INDEX) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ for (uint32_t j = 0; j < 3; j++)
+ {
+ assert(in_range(e[i][j], 0, 0xFFF));
+ }
+ }
+
+ for (uint32_t i = 0; i < n; i++)
+ {
+ const int c = g_ise_weight_lerps[ise_weight_range][1 + i];
+ assert(c == (int)astc_helpers::dequant_bise_weight(i, ise_weight_range));
+
+ half_float rf, gf, bf;
+
+ {
+ uint32_t r0 = e[0][0] << 4;
+ uint32_t r1 = e[1][0] << 4;
+ int ri = (r0 * (64 - c) + r1 * c + 32) / 64;
+ rf = qlog16_to_half_slow(ri);
+ }
+
+ {
+ uint32_t g0 = e[0][1] << 4;
+ uint32_t g1 = e[1][1] << 4;
+ int gi = (g0 * (64 - c) + g1 * c + 32) / 64;
+ gf = qlog16_to_half_slow(gi);
+ }
+
+ {
+ uint32_t b0 = e[0][2] << 4;
+ uint32_t b1 = e[1][2] << 4;
+ int bi = (b0 * (64 - c) + b1 * c + 32) / 64;
+ bf = qlog16_to_half_slow(bi);
+ }
+
+ if (pDecoded_half)
+ {
+ pDecoded_half[i * 3 + 0] = rf;
+ pDecoded_half[i * 3 + 1] = gf;
+ pDecoded_half[i * 3 + 2] = bf;
+ }
+
+ if (pDecoded_float)
+ {
+ pDecoded_float[i][0] = half_to_float(rf);
+ pDecoded_float[i][1] = half_to_float(gf);
+ pDecoded_float[i][2] = half_to_float(bf);
+ }
+ }
+}
+
+// decoded in ASTC order, not linear order
+// return false if the ISE endpoint quantization leads to non-valid endpoints being decoded
+bool get_astc_hdr_mode_11_block_colors(
+ const uint8_t* pEndpoints,
+ half_float* pDecoded_half,
+ vec3F* pDecoded_float,
+ uint32_t n, uint32_t ise_weight_range, uint32_t ise_endpoint_range)
+{
+ assert((ise_weight_range >= 1) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+
+ int e[2][3];
+ if (!decode_mode11_to_qlog12(pEndpoints, e, ise_endpoint_range))
+ return false;
+
+ interpolate_qlog12_colors(e, pDecoded_half, pDecoded_float, n, ise_weight_range);
+
+ return true;
+}
+
+// decoded in ASTC order, not linear order
+// return false if the ISE endpoint quantization leads to non-valid endpoints being decoded
+bool get_astc_hdr_mode_7_block_colors(
+ const uint8_t* pEndpoints,
+ half_float* pDecoded_half,
+ vec3F* pDecoded_float,
+ uint32_t n, uint32_t ise_weight_range, uint32_t ise_endpoint_range)
+{
+ assert((ise_weight_range >= 1) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+
+ int e[2][3];
+ if (!decode_mode7_to_qlog12(pEndpoints, e, nullptr, ise_endpoint_range))
+ return false;
+
+ interpolate_qlog12_colors(e, pDecoded_half, pDecoded_float, n, ise_weight_range);
+
+ return true;
+}
+
+// Fast high precision piecewise linear approximation of log2(bias+x).
+// Half may be zero, positive or denormal. No NaN/Inf/negative.
+static inline double q(half_float x)
+{
+ union { float f; int32_t i; uint32_t u; } fi;
+
+ fi.f = fast_half_to_float_pos_not_inf_or_nan(x);
+
+ assert(fi.f >= 0.0f);
+
+ fi.f += .125f;
+
+ return (double)fi.u; // approx log2f(fi.f), need to return double for the precision
+}
+
+double eval_selectors(
+ uint32_t num_pixels,
+ uint8_t* pWeights,
+ const half_float* pBlock_pixels_half,
+ uint32_t num_weight_levels,
+ const half_float* pDecoded_half,
+ const astc_hdr_codec_options& coptions,
+ uint32_t usable_selector_bitmask)
+{
+ assert((num_pixels >= 1) && (num_pixels <= 16));
+ assert(usable_selector_bitmask);
+
+ const float R_WEIGHT = coptions.m_r_err_scale;
+ const float G_WEIGHT = coptions.m_g_err_scale;
+
+ double total_error = 0;
+
+#ifdef _DEBUG
+ for (uint32_t i = 0; i < num_weight_levels; i++)
+ {
+ assert(!is_half_inf_or_nan(pDecoded_half[i * 3 + 0]));
+ assert(!is_half_inf_or_nan(pDecoded_half[i * 3 + 1]));
+ assert(!is_half_inf_or_nan(pDecoded_half[i * 3 + 2]));
+ }
+#endif
+
+ for (uint32_t p = 0; p < num_pixels; p++)
+ {
+ const half_float* pDesired_half = &pBlock_pixels_half[p * 3];
+
+ double lowest_e = 1e+30f;
+
+ // this is an approximation of MSLE
+ for (uint32_t i = 0; i < num_weight_levels; i++)
+ {
+ if (((1 << i) & usable_selector_bitmask) == 0)
+ continue;
+
+ // compute piecewise linear approximation of log2(a+eps)-log2(b+eps), for each component, then MSLE
+ double rd = q(pDecoded_half[i * 3 + 0]) - q(pDesired_half[0]);
+ double gd = q(pDecoded_half[i * 3 + 1]) - q(pDesired_half[1]);
+ double bd = q(pDecoded_half[i * 3 + 2]) - q(pDesired_half[2]);
+
+ double e = R_WEIGHT * (rd * rd) + G_WEIGHT * (gd * gd) + bd * bd;
+
+ if (e < lowest_e)
+ {
+ lowest_e = e;
+ pWeights[p] = (uint8_t)i;
+ }
+ }
+
+ total_error += lowest_e;
+
+ } // p
+
+ return total_error;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+double compute_block_error(const half_float* pOrig_block, const half_float* pPacked_block, const astc_hdr_codec_options& coptions)
+{
+ const float R_WEIGHT = coptions.m_r_err_scale;
+ const float G_WEIGHT = coptions.m_g_err_scale;
+
+ double total_error = 0;
+
+ for (uint32_t p = 0; p < 16; p++)
+ {
+ double rd = q(pOrig_block[p * 3 + 0]) - q(pPacked_block[p * 3 + 0]);
+ double gd = q(pOrig_block[p * 3 + 1]) - q(pPacked_block[p * 3 + 1]);
+ double bd = q(pOrig_block[p * 3 + 2]) - q(pPacked_block[p * 3 + 2]);
+
+ double e = R_WEIGHT * (rd * rd) + G_WEIGHT * (gd * gd) + bd * bd;
+
+ total_error += e;
+ }
+
+ return total_error;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static inline int compute_clamped_val(int v, int l, int h, bool& did_clamp, int& max_clamp_mag)
+{
+ assert(l < h);
+
+ if (v < l)
+ {
+ max_clamp_mag = basisu::maximum<int>(max_clamp_mag, l - v);
+
+ v = l;
+ did_clamp = true;
+ }
+ else if (v > h)
+ {
+ max_clamp_mag = basisu::maximum<int>(max_clamp_mag, v - h);
+
+ v = h;
+ did_clamp = true;
+ }
+
+ return v;
+}
+
+static bool pack_astc_mode11_submode(uint32_t submode, uint8_t* pEndpoints, const vec3F& low_q16, const vec3F& high_q16, int& max_clamp_mag)
+{
+ assert(submode <= 7);
+
+ const uint8_t s_b_bits[8] = { 7, 8, 6, 7, 8, 6, 7, 6 };
+ const uint8_t s_c_bits[8] = { 6, 6, 7, 7, 6, 7, 7, 7 };
+ const uint8_t s_d_bits[8] = { 7, 6, 7, 6, 5, 6, 5, 6 };
+
+ const uint32_t a_bits = 9 + (submode >> 1);
+ const uint32_t b_bits = s_b_bits[submode];
+ const uint32_t c_bits = s_c_bits[submode];
+ const uint32_t d_bits = s_d_bits[submode];
+
+ const int max_a_val = (1 << a_bits) - 1;
+ const int max_b_val = (1 << b_bits) - 1;
+ const int max_c_val = (1 << c_bits) - 1;
+
+ // The maximum usable value before it turns to NaN/Inf
+ const int max_a_qlog = get_max_qlog(a_bits);
+
+ const int min_d_val = -(1 << (d_bits - 1));
+ const int max_d_val = -min_d_val - 1;
+ assert((max_d_val - min_d_val + 1) == (1 << d_bits));
+
+ int val_q[2][3];
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+#if 1
+ // this is better
+ const half_float l = qlog16_to_half_slow((uint32_t)std::round(low_q16[c]));
+ val_q[0][c] = half_to_qlog7_12(l, a_bits);
+
+ const half_float h = qlog16_to_half_slow((uint32_t)std::round(high_q16[c]));
+ val_q[1][c] = half_to_qlog7_12(h, a_bits);
+#else
+ val_q[0][c] = quant_qlog16((uint32_t)std::round(low_q16[c]), a_bits);
+ val_q[1][c] = quant_qlog16((uint32_t)std::round(high_q16[c]), a_bits);
+#endif
+
+#if 1
+ if (val_q[0][c] == val_q[1][c])
+ {
+#if 0
+ if (l <= h)
+#else
+ if (low_q16[c] < high_q16[c])
+#endif
+ {
+ if (val_q[0][c])
+ val_q[0][c]--;
+
+ if (val_q[1][c] != max_a_val)
+ val_q[1][c]++;
+ }
+ else
+ {
+ if (val_q[0][c] != max_a_val)
+ val_q[0][c]++;
+
+ if (val_q[1][c])
+ val_q[1][c]--;
+ }
+ }
+#endif
+
+ val_q[0][c] = minimum<uint32_t>(val_q[0][c], max_a_qlog);
+ val_q[1][c] = minimum<uint32_t>(val_q[1][c], max_a_qlog);
+ }
+
+ int highest_q = -1, highest_val = 0, highest_comp = 0;
+
+ for (uint32_t v = 0; v < 2; v++)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ assert(val_q[v][c] >= 0 && val_q[v][c] <= max_a_val);
+
+ if (val_q[v][c] > highest_q)
+ {
+ highest_q = val_q[v][c];
+ highest_val = v;
+ highest_comp = c;
+ }
+ }
+ }
+
+ const bool had_tie = (val_q[highest_val ^ 1][highest_comp] == highest_q);
+
+ if (highest_val != 1)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ std::swap(val_q[0][c], val_q[1][c]);
+ }
+ }
+
+ if (highest_comp)
+ {
+ std::swap(val_q[0][0], val_q[0][highest_comp]);
+ std::swap(val_q[1][0], val_q[1][highest_comp]);
+ }
+
+ int orig_q[2][3];
+ memcpy(orig_q, val_q, sizeof(val_q));
+
+ // val[1][0] is now guaranteed to be highest
+ int best_va = 0, best_vb0 = 0, best_vb1 = 0, best_vc = 0, best_vd0 = 0, best_vd1 = 0;
+ int best_max_clamp_mag = 0;
+ bool best_did_clamp = false;
+ int best_q[2][3] = { { 0, 0, 0}, { 0, 0, 0 } };
+ BASISU_NOTE_UNUSED(best_q);
+ uint32_t best_dist = UINT_MAX;
+
+ for (uint32_t pass = 0; pass < 2; pass++)
+ {
+ int trial_va = val_q[1][0];
+
+ assert(trial_va <= max_a_val);
+ assert(trial_va >= val_q[1][1]);
+ assert(trial_va >= val_q[1][2]);
+
+ assert(trial_va >= val_q[0][0]);
+ assert(trial_va >= val_q[0][1]);
+ assert(trial_va >= val_q[0][2]);
+
+ bool did_clamp = false;
+ int trial_max_clamp_mag = 0;
+
+ int trial_vb0 = compute_clamped_val(trial_va - val_q[1][1], 0, max_b_val, did_clamp, trial_max_clamp_mag);
+ int trial_vb1 = compute_clamped_val(trial_va - val_q[1][2], 0, max_b_val, did_clamp, trial_max_clamp_mag);
+ int trial_vc = compute_clamped_val(trial_va - val_q[0][0], 0, max_c_val, did_clamp, trial_max_clamp_mag);
+ int trial_vd0 = compute_clamped_val((trial_va - trial_vb0 - trial_vc) - val_q[0][1], min_d_val, max_d_val, did_clamp, trial_max_clamp_mag);
+ int trial_vd1 = compute_clamped_val((trial_va - trial_vb1 - trial_vc) - val_q[0][2], min_d_val, max_d_val, did_clamp, trial_max_clamp_mag);
+
+ if (!did_clamp)
+ {
+ // Make sure decoder gets the expected values
+ assert(trial_va == val_q[1][0]);
+ assert(trial_va - trial_vb0 == val_q[1][1]);
+ assert(trial_va - trial_vb1 == val_q[1][2]);
+
+ assert((trial_va - trial_vc) == val_q[0][0]);
+ assert((trial_va - trial_vb0 - trial_vc - trial_vd0) == val_q[0][1]);
+ assert((trial_va - trial_vb1 - trial_vc - trial_vd1) == val_q[0][2]);
+ }
+
+ const int r_e0 = clamp<int>(trial_va, 0, max_a_val);
+ const int r_e1 = clamp<int>(trial_va - trial_vb0, 0, max_a_val);
+ const int r_e2 = clamp<int>(trial_va - trial_vb1, 0, max_a_val);
+
+ const int r_f0 = clamp<int>(trial_va - trial_vc, 0, max_a_val);
+ const int r_f1 = clamp<int>(trial_va - trial_vb0 - trial_vc - trial_vd0, 0, max_a_val);
+ const int r_f2 = clamp<int>(trial_va - trial_vb1 - trial_vc - trial_vd1, 0, max_a_val);
+
+ assert(r_e0 <= max_a_qlog);
+ assert(r_e1 <= max_a_qlog);
+ assert(r_e2 <= max_a_qlog);
+
+ assert(r_f0 <= max_a_qlog);
+ assert(r_f1 <= max_a_qlog);
+ assert(r_f2 <= max_a_qlog);
+
+ if ((!did_clamp) || (!had_tie))
+ {
+ best_va = trial_va;
+ best_vb0 = trial_vb0;
+ best_vb1 = trial_vb1;
+ best_vc = trial_vc;
+ best_vd0 = trial_vd0;
+ best_vd1 = trial_vd1;
+ best_max_clamp_mag = trial_max_clamp_mag;
+ best_did_clamp = did_clamp;
+
+ best_q[1][0] = r_e0;
+ best_q[1][1] = r_e1;
+ best_q[1][2] = r_e2;
+ best_q[0][0] = r_f0;
+ best_q[0][1] = r_f1;
+ best_q[0][2] = r_f2;
+ break;
+ }
+
+ // we had a tie and it did clamp, try swapping L/H for a potential slight gain
+
+ const uint32_t r_dist1 = basisu::square<int>(r_e0 - val_q[1][0]) + basisu::square<int>(r_e1 - val_q[1][1]) + basisu::square<int>(r_e2 - val_q[1][2]);
+ const uint32_t r_dist0 = basisu::square<int>(r_f0 - val_q[0][0]) + basisu::square<int>(r_f1 - val_q[0][1]) + basisu::square<int>(r_f2 - val_q[0][2]);
+
+ const uint32_t total_dist = r_dist1 + r_dist0;
+
+ if (total_dist < best_dist)
+ {
+ best_dist = total_dist;
+
+ best_va = trial_va;
+ best_vb0 = trial_vb0;
+ best_vb1 = trial_vb1;
+ best_vc = trial_vc;
+ best_vd0 = trial_vd0;
+ best_vd1 = trial_vd1;
+ best_did_clamp = did_clamp;
+
+ best_q[1][0] = r_e0;
+ best_q[1][1] = r_e1;
+ best_q[1][2] = r_e2;
+ best_q[0][0] = r_f0;
+ best_q[0][1] = r_f1;
+ best_q[0][2] = r_f2;
+ }
+
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(val_q[0][c], val_q[1][c]);
+ }
+
+ // pack bits now
+ int v0 = 0, v1 = 0, v2 = 0, v3 = 0, v4 = 0, v5 = 0;
+
+ int x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0;
+ switch (submode)
+ {
+ case 0:
+ x0 = get_bit(best_vb0, 6); x1 = get_bit(best_vb1, 6); x2 = get_bit(best_vd0, 6); x3 = get_bit(best_vd1, 6); x4 = get_bit(best_vd0, 5); x5 = get_bit(best_vd1, 5);
+ break;
+ case 1:
+ x0 = get_bit(best_vb0, 6); x1 = get_bit(best_vb1, 6); x2 = get_bit(best_vb0, 7); x3 = get_bit(best_vb1, 7); x4 = get_bit(best_vd0, 5); x5 = get_bit(best_vd1, 5);
+ break;
+ case 2:
+ x0 = get_bit(best_va, 9); x1 = get_bit(best_vc, 6); x2 = get_bit(best_vd0, 6); x3 = get_bit(best_vd1, 6); x4 = get_bit(best_vd0, 5); x5 = get_bit(best_vd1, 5);
+ break;
+ case 3:
+ x0 = get_bit(best_vb0, 6); x1 = get_bit(best_vb1, 6); x2 = get_bit(best_va, 9); x3 = get_bit(best_vc, 6); x4 = get_bit(best_vd0, 5); x5 = get_bit(best_vd1, 5);
+ break;
+ case 4:
+ x0 = get_bit(best_vb0, 6); x1 = get_bit(best_vb1, 6); x2 = get_bit(best_vb0, 7); x3 = get_bit(best_vb1, 7); x4 = get_bit(best_va, 9); x5 = get_bit(best_va, 10);
+ break;
+ case 5:
+ x0 = get_bit(best_va, 9); x1 = get_bit(best_va, 10); x2 = get_bit(best_vc, 7); x3 = get_bit(best_vc, 6); x4 = get_bit(best_vd0, 5); x5 = get_bit(best_vd1, 5);
+ break;
+ case 6:
+ x0 = get_bit(best_vb0, 6); x1 = get_bit(best_vb1, 6); x2 = get_bit(best_va, 11); x3 = get_bit(best_vc, 6); x4 = get_bit(best_va, 9); x5 = get_bit(best_va, 10);
+ break;
+ case 7:
+ x0 = get_bit(best_va, 9); x1 = get_bit(best_va, 10); x2 = get_bit(best_va, 11); x3 = get_bit(best_vc, 6); x4 = get_bit(best_vd0, 5); x5 = get_bit(best_vd1, 5);
+ break;
+ default:
+ break;
+ }
+
+ // write mode
+ pack_bit(v1, 7, submode, 0);
+ pack_bit(v2, 7, submode, 1);
+ pack_bit(v3, 7, submode, 2);
+
+ // highest component
+ pack_bit(v4, 7, highest_comp, 0);
+ pack_bit(v5, 7, highest_comp, 1);
+
+ // write bit 8 of va
+ pack_bit(v1, 6, best_va, 8);
+
+ // extra bits
+ pack_bit(v2, 6, x0);
+ pack_bit(v3, 6, x1);
+ pack_bit(v4, 6, x2);
+ pack_bit(v5, 6, x3);
+ pack_bit(v4, 5, x4);
+ pack_bit(v5, 5, x5);
+
+ v0 = best_va & 0xFF;
+ v1 |= (best_vc & 63);
+ v2 |= (best_vb0 & 63);
+ v3 |= (best_vb1 & 63);
+ v4 |= (best_vd0 & 31);
+ v5 |= (best_vd1 & 31);
+
+ assert(in_range(v0, 0, 255) && in_range(v1, 0, 255) && in_range(v2, 0, 255) && in_range(v3, 0, 255) && in_range(v4, 0, 255) && in_range(v5, 0, 255));
+
+ pEndpoints[0] = (uint8_t)v0;
+ pEndpoints[1] = (uint8_t)v1;
+ pEndpoints[2] = (uint8_t)v2;
+ pEndpoints[3] = (uint8_t)v3;
+ pEndpoints[4] = (uint8_t)v4;
+ pEndpoints[5] = (uint8_t)v5;
+
+#ifdef _DEBUG
+ // Test for valid pack by unpacking
+ {
+ if (highest_comp)
+ {
+ std::swap(best_q[0][0], best_q[0][highest_comp]);
+ std::swap(best_q[1][0], best_q[1][highest_comp]);
+
+ std::swap(orig_q[0][0], orig_q[0][highest_comp]);
+ std::swap(orig_q[1][0], orig_q[1][highest_comp]);
+ }
+
+ int test_e[2][3];
+ decode_mode11_to_qlog12(pEndpoints, test_e, astc_helpers::BISE_256_LEVELS);
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ for (uint32_t j = 0; j < 3; j++)
+ {
+ assert(best_q[i][j] == test_e[i][j] >> (12 - a_bits));
+
+ if (!best_did_clamp)
+ {
+ assert((orig_q[i][j] == test_e[i][j] >> (12 - a_bits)) ||
+ (orig_q[1 - i][j] == test_e[i][j] >> (12 - a_bits)));
+ }
+ }
+ }
+ }
+#endif
+
+ max_clamp_mag = best_max_clamp_mag;
+
+ return best_did_clamp;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static void pack_astc_mode11_direct(uint8_t* pEndpoints, const vec3F& l_q16, const vec3F& h_q16)
+{
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ // TODO: This goes from QLOG16->HALF->QLOG8/7
+ half_float l_half = qlog16_to_half_slow(clamp((int)std::round(l_q16[i]), 0, 65535));
+ half_float h_half = qlog16_to_half_slow(clamp((int)std::round(h_q16[i]), 0, 65535));
+
+ int l_q, h_q;
+
+ if (i == 2)
+ {
+ l_q = g_half_to_qlog7[bounds_check((uint32_t)l_half, 0U, 32768U)];
+ h_q = g_half_to_qlog7[bounds_check((uint32_t)h_half, 0U, 32768U)];
+
+ l_q = minimum<uint32_t>(l_q, MAX_QLOG7);
+ h_q = minimum<uint32_t>(h_q, MAX_QLOG7);
+ }
+ else
+ {
+ l_q = g_half_to_qlog8[bounds_check((uint32_t)l_half, 0U, 32768U)];
+ h_q = g_half_to_qlog8[bounds_check((uint32_t)h_half, 0U, 32768U)];
+
+ l_q = minimum<uint32_t>(l_q, MAX_QLOG8);
+ h_q = minimum<uint32_t>(h_q, MAX_QLOG8);
+ }
+
+#if 1
+ if (l_q == h_q)
+ {
+ const int m = (i == 2) ? MAX_QLOG7 : MAX_QLOG8;
+
+ if (l_q16[i] <= h_q16[i])
+ {
+ if (l_q)
+ l_q--;
+
+ if (h_q != m)
+ h_q++;
+ }
+ else
+ {
+ if (h_q)
+ h_q--;
+
+ if (l_q != m)
+ l_q++;
+ }
+ }
+#endif
+
+ if (i == 2)
+ {
+ assert(l_q <= (int)MAX_QLOG7 && h_q <= (int)MAX_QLOG7);
+ l_q |= 128;
+ h_q |= 128;
+ }
+ else
+ {
+ assert(l_q <= (int)MAX_QLOG8 && h_q <= (int)MAX_QLOG8);
+ }
+
+ pEndpoints[2 * i + 0] = (uint8_t)l_q;
+ pEndpoints[2 * i + 1] = (uint8_t)h_q;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static bool pack_astc_mode7_submode(uint32_t submode, uint8_t* pEndpoints, const vec3F& rgb_q16, float s_q16, int& max_clamp_mag, uint32_t ise_weight_range)
+{
+ assert((ise_weight_range >= 1) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+
+ assert(submode <= 5);
+ max_clamp_mag = 0;
+
+ static const uint8_t s_r_bits[6] = { 11, 11, 10, 9, 8, 7 };
+ static const uint8_t s_g_b_bits[6] = { 5, 6, 5, 6, 7, 7 };
+ static const uint8_t s_s_bits[6] = { 7, 5, 8, 7, 6, 7 };
+
+ // The precision of the components
+ const uint32_t prec_bits = s_r_bits[submode];
+
+ int qlog[4], pack_bits[4];
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ const float f = (i == 3) ? s_q16 : rgb_q16[i];
+
+ // The # of bits the component is packed into
+ if (i == 0)
+ pack_bits[i] = s_r_bits[submode];
+ else if (i == 3)
+ pack_bits[i] = s_s_bits[submode];
+ else
+ pack_bits[i] = s_g_b_bits[submode];
+
+#if 0
+ // this is slightly worse
+ // TODO: going from qlog16 to half loses some precision. Then going from half to qlog 7-12 will have extra error.
+ half_float h = qlog_to_half(clamp((int)std::round(f), 0, MAX_QLOG16), 16);
+ qlog[i] = half_to_qlog7_12((half_float)bounds_check((uint32_t)h, 0U, 32768U), prec_bits);
+#else
+ qlog[i] = quant_qlog16(clamp<int>((int)std::round(f), 0, MAX_QLOG16), prec_bits);
+
+ // Only bias if there are enough texel weights, 4=6 weights
+ if (ise_weight_range >= 4)
+ {
+ // Explictly bias the high color, and the scale up, to better exploit the weights.
+ // The quantized range also then encompases the complete input range.
+ const uint32_t max_val = (1 << prec_bits) - 1;
+ const uint32_t K = 3;
+ if (i == 3)
+ {
+ qlog[i] = minimum<uint32_t>(qlog[i] + K * 2, max_val);
+ }
+ else
+ {
+ qlog[i] = minimum<uint32_t>(qlog[i] + K, max_val);
+ }
+ }
+#endif
+
+ if (i != 3)
+ qlog[i] = minimum<uint32_t>(qlog[i], get_max_qlog(prec_bits));
+
+ // If S=0, we lose freedom for the texel weights to add any value.
+ if ((i == 3) && (qlog[i] == 0))
+ qlog[i] = 1;
+ }
+
+ uint32_t maj_index = 0;
+
+ bool did_clamp = false;
+
+ if (submode != 5)
+ {
+ int largest_qlog = 0;
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ if (qlog[i] > largest_qlog)
+ {
+ largest_qlog = qlog[i];
+ maj_index = i;
+ }
+ }
+
+ if (maj_index)
+ {
+ std::swap(qlog[0], qlog[maj_index]);
+ }
+
+ assert(qlog[0] >= qlog[1]);
+ assert(qlog[0] >= qlog[2]);
+
+ qlog[1] = qlog[0] - qlog[1];
+ qlog[2] = qlog[0] - qlog[2];
+
+ for (uint32_t i = 1; i < 4; i++)
+ {
+ const int max_val = (1 << pack_bits[i]) - 1;
+
+ if (qlog[i] > max_val)
+ {
+ max_clamp_mag = maximum<int>(max_clamp_mag, qlog[i] - max_val);
+ qlog[i] = max_val;
+ did_clamp = true;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ const int max_val = (1 << pack_bits[i]) - 1; (void)max_val;
+
+ assert(qlog[i] <= max_val);
+ }
+
+ int mode = 0;
+
+ int r = qlog[0] & 63; // 6-bits
+ int g = qlog[1] & 31; // 5-bits
+ int b = qlog[2] & 31; // 5-bits
+ int s = qlog[3] & 31; // 5-bits
+
+ int x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0;
+
+ switch (submode)
+ {
+ case 0:
+ {
+ mode = (maj_index << 2) | 0;
+ assert((mode & 0xC) != 0xC);
+
+ x0 = get_bit(qlog[0], 9); // R9
+ x1 = get_bit(qlog[0], 8); // R8
+ x2 = get_bit(qlog[0], 7); // R7
+ x3 = get_bit(qlog[0], 10); // R10
+ x4 = get_bit(qlog[0], 6); // R6
+ x5 = get_bit(qlog[3], 6); // S6
+ x6 = get_bit(qlog[3], 5); // S5
+ break;
+ }
+ case 1:
+ {
+ mode = (maj_index << 2) | 1;
+ assert((mode & 0xC) != 0xC);
+
+ x0 = get_bit(qlog[0], 8); // R8
+ x1 = get_bit(qlog[1], 5); // G5
+ x2 = get_bit(qlog[0], 7); // R7
+ x3 = get_bit(qlog[2], 5); // B5
+ x4 = get_bit(qlog[0], 6); // R6
+ x5 = get_bit(qlog[0], 10); // R10
+ x6 = get_bit(qlog[0], 9); // R9
+ break;
+ }
+ case 2:
+ {
+ mode = (maj_index << 2) | 2;
+ assert((mode & 0xC) != 0xC);
+
+ x0 = get_bit(qlog[0], 9); // R9
+ x1 = get_bit(qlog[0], 8); // R8
+ x2 = get_bit(qlog[0], 7); // R7
+ x3 = get_bit(qlog[0], 6); // R6
+ x4 = get_bit(qlog[3], 7); // S7
+ x5 = get_bit(qlog[3], 6); // S6
+ x6 = get_bit(qlog[3], 5); // S5
+ break;
+ }
+ case 3:
+ {
+ mode = (maj_index << 2) | 3;
+ assert((mode & 0xC) != 0xC);
+
+ x0 = get_bit(qlog[0], 8); // R8
+ x1 = get_bit(qlog[1], 5); // G5
+ x2 = get_bit(qlog[0], 7); // R7
+ x3 = get_bit(qlog[2], 5); // B5
+ x4 = get_bit(qlog[0], 6); // R6
+ x5 = get_bit(qlog[3], 6); // S6
+ x6 = get_bit(qlog[3], 5); // S5
+ break;
+ }
+ case 4:
+ {
+ mode = maj_index | 0xC; // 0b1100
+ assert((mode & 0xC) == 0xC);
+ assert(mode != 0xF);
+
+ x0 = get_bit(qlog[1], 6); // G6
+ x1 = get_bit(qlog[1], 5); // G5
+ x2 = get_bit(qlog[2], 6); // B6
+ x3 = get_bit(qlog[2], 5); // B5
+ x4 = get_bit(qlog[0], 6); // R6
+ x5 = get_bit(qlog[0], 7); // R7
+ x6 = get_bit(qlog[3], 5); // S5
+ break;
+ }
+ case 5:
+ {
+ mode = 0xF;
+
+ x0 = get_bit(qlog[1], 6); // G6
+ x1 = get_bit(qlog[1], 5); // G5
+ x2 = get_bit(qlog[2], 6); // B6
+ x3 = get_bit(qlog[2], 5); // B5
+ x4 = get_bit(qlog[0], 6); // R6
+ x5 = get_bit(qlog[3], 6); // S6
+ x6 = get_bit(qlog[3], 5); // S5
+ break;
+ }
+ default:
+ {
+ assert(0);
+ break;
+ }
+ }
+
+ pEndpoints[0] = (uint8_t)((get_bit(mode, 1) << 7) | (get_bit(mode, 0) << 6) | r);
+ pEndpoints[1] = (uint8_t)((get_bit(mode, 2) << 7) | (x0 << 6) | (x1 << 5) | g);
+ pEndpoints[2] = (uint8_t)((get_bit(mode, 3) << 7) | (x2 << 6) | (x3 << 5) | b);
+ pEndpoints[3] = (uint8_t)((x4 << 7) | (x5 << 6) | (x6 << 5) | s);
+
+#ifdef _DEBUG
+ // Test for valid pack by unpacking
+ {
+ const int inv_shift = 12 - prec_bits;
+
+ int unpacked_e[2][3];
+ if (submode != 5)
+ {
+ unpacked_e[1][0] = left_shift32(qlog[0], inv_shift);
+ unpacked_e[1][1] = clamp(left_shift32((qlog[0] - qlog[1]), inv_shift), 0, 0xFFF);
+ unpacked_e[1][2] = clamp(left_shift32((qlog[0] - qlog[2]), inv_shift), 0, 0xFFF);
+
+ unpacked_e[0][0] = clamp(left_shift32((qlog[0] - qlog[3]), inv_shift), 0, 0xFFF);
+ unpacked_e[0][1] = clamp(left_shift32(((qlog[0] - qlog[1]) - qlog[3]), inv_shift), 0, 0xFFF);
+ unpacked_e[0][2] = clamp(left_shift32(((qlog[0] - qlog[2]) - qlog[3]), inv_shift), 0, 0xFFF);
+ }
+ else
+ {
+ unpacked_e[1][0] = left_shift32(qlog[0], inv_shift);
+ unpacked_e[1][1] = left_shift32(qlog[1], inv_shift);
+ unpacked_e[1][2] = left_shift32(qlog[2], inv_shift);
+
+ unpacked_e[0][0] = clamp(left_shift32((qlog[0] - qlog[3]), inv_shift), 0, 0xFFF);
+ unpacked_e[0][1] = clamp(left_shift32((qlog[1] - qlog[3]), inv_shift), 0, 0xFFF);
+ unpacked_e[0][2] = clamp(left_shift32((qlog[2] - qlog[3]), inv_shift), 0, 0xFFF);
+ }
+
+ if (maj_index)
+ {
+ std::swap(unpacked_e[0][0], unpacked_e[0][maj_index]);
+ std::swap(unpacked_e[1][0], unpacked_e[1][maj_index]);
+ }
+
+ int e[2][3];
+ decode_mode7_to_qlog12_ise20(pEndpoints, e, nullptr);
+
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ assert(unpacked_e[0][i] == e[0][i]);
+ assert(unpacked_e[1][i] == e[1][i]);
+ }
+ }
+#endif
+
+ return did_clamp;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static void quantize_ise_endpoints(uint32_t ise_endpoint_range, const uint8_t* pSrc_endpoints, uint8_t *pDst_endpoints, uint32_t n)
+{
+ assert((ise_endpoint_range >= astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE) && (ise_endpoint_range <= astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE));
+
+ if (ise_endpoint_range == astc_helpers::BISE_256_LEVELS)
+ {
+ memcpy(pDst_endpoints, pSrc_endpoints, n);
+ }
+ else
+ {
+ for (uint32_t i = 0; i < n; i++)
+ {
+ uint32_t v = pSrc_endpoints[i];
+ assert(v <= 255);
+
+ pDst_endpoints[i] = astc_helpers::g_dequant_tables.get_endpoint_tab(ise_endpoint_range).m_val_to_ise[v];
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+// Note this could fail to find any valid solution if use_endpoint_range!=20.
+// Returns true if improved.
+static bool try_mode11(uint32_t num_pixels,
+ uint8_t* pEndpoints, uint8_t* pWeights, double& cur_block_error, uint32_t& submode_used,
+ vec3F& low_color_q16, const vec3F& high_color_q16,
+ half_float block_pixels_half[16][3],
+ uint32_t num_weight_levels, uint32_t ise_weight_range, const astc_hdr_codec_options& coptions, bool direct_only, uint32_t ise_endpoint_range,
+ bool constrain_ise_weight8_selectors,
+ int32_t first_submode, int32_t last_submode) // -1, 7
+{
+ assert((ise_weight_range >= 1) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+ assert((num_weight_levels >= 3) && (num_weight_levels <= 32));
+ assert((num_pixels >= 1) && (num_pixels <= 16));
+
+ bool improved_flag = false;
+
+ half_float decoded_half[32][3];
+ vec3F decoded_float[32];
+ uint8_t orig_trial_endpoints[NUM_MODE11_ENDPOINTS], trial_endpoints[NUM_MODE11_ENDPOINTS], trial_weights[16];
+
+ if (direct_only)
+ {
+ first_submode = -1;
+ last_submode = -1;
+ }
+
+ assert(first_submode <= last_submode);
+ assert((first_submode >= -1) && (first_submode <= 7));
+ assert((last_submode >= -1) && (last_submode <= 7));
+
+ // TODO: First determine if a submode doesn't clamp first. If one is found, encode to that and we're done.
+ for (int submode = last_submode; submode >= first_submode; submode--)
+ {
+ bool did_clamp = false;
+ int max_clamp_mag = 0;
+ if (submode == -1)
+ {
+ // If it had to clamp with one of the submodes, try direct which can't clamp, but has low precision.
+ pack_astc_mode11_direct(orig_trial_endpoints, low_color_q16, high_color_q16);
+ }
+ else
+ {
+ did_clamp = pack_astc_mode11_submode(submode, orig_trial_endpoints, low_color_q16, high_color_q16, max_clamp_mag);
+
+ // If it had to clamp and the clamp was too high, it'll distort the endpoint colors too much, which could lead to noticeable artifacts.
+ const int MAX_CLAMP_MAG_ACCEPT_THRESH = 4;
+ if ((did_clamp) && (max_clamp_mag > MAX_CLAMP_MAG_ACCEPT_THRESH))
+ continue;
+ }
+
+ // This will distort the endpoints if the ISE endpoint range isn't 256 levels (20).
+ // It could massively distort the endpoints, but still result in a valid encoding.
+ quantize_ise_endpoints(ise_endpoint_range, orig_trial_endpoints, trial_endpoints, NUM_MODE11_ENDPOINTS);
+
+ if (!get_astc_hdr_mode_11_block_colors(trial_endpoints, &decoded_half[0][0], decoded_float, num_weight_levels, ise_weight_range, ise_endpoint_range))
+ continue;
+
+ uint32_t usable_selector_bitmask = UINT32_MAX;
+ if ((constrain_ise_weight8_selectors) && (ise_weight_range == astc_helpers::BISE_16_LEVELS))
+ usable_selector_bitmask = (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 10) | (1 << 11) | (1 << 14) | (1 << 15);
+
+ double trial_blk_error = eval_selectors(num_pixels, trial_weights, &block_pixels_half[0][0], num_weight_levels, &decoded_half[0][0], coptions, usable_selector_bitmask);
+ if (trial_blk_error < cur_block_error)
+ {
+ cur_block_error = trial_blk_error;
+ memcpy(pEndpoints, trial_endpoints, NUM_MODE11_ENDPOINTS);
+ memcpy(pWeights, trial_weights, num_pixels);
+ submode_used = submode + 1;
+ improved_flag = true;
+ }
+
+ // If it didn't clamp it was a lossless encode at this precision, so we can stop early as there's probably no use trying lower precision submodes.
+ // (Although it may be, because a lower precision pack could try nearby voxel coords.)
+ // However, at lower levels quantization may cause the decoded endpoints to be very distorted, so we need to evaluate up to direct.
+ if (ise_endpoint_range == astc_helpers::BISE_256_LEVELS)
+ {
+ if (!did_clamp)
+ break;
+ }
+ }
+
+ return improved_flag;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static bool try_mode7(
+ uint32_t num_pixels,
+ uint8_t* pEndpoints, uint8_t* pWeights, double& cur_block_error, uint32_t& submode_used,
+ vec3F& high_color_q16, const float s_q16,
+ half_float block_pixels_half[16][3],
+ uint32_t num_weight_levels, uint32_t ise_weight_range, const astc_hdr_codec_options& coptions,
+ uint32_t ise_endpoint_range)
+{
+ assert((ise_weight_range >= 1) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+ assert((num_pixels >= 1) && (num_pixels <= 16));
+
+ bool improved_flag = false;
+
+ half_float decoded_half[24][3];
+ vec3F decoded_float[24];
+
+ uint8_t orig_trial_endpoints[NUM_MODE7_ENDPOINTS], trial_endpoints[NUM_MODE7_ENDPOINTS], trial_weights[16];
+
+ // TODO: First determine if a submode doesn't clamp first. If one is found, encode to that and we're done.
+ for (int submode = 0; submode <= 5; submode++)
+ {
+ int max_clamp_mag = 0;
+ const bool did_clamp = pack_astc_mode7_submode(submode, orig_trial_endpoints, high_color_q16, s_q16, max_clamp_mag, ise_weight_range);
+
+ if (submode < 5)
+ {
+ const int MAX_CLAMP_MAG_ACCEPT_THRESH = 4;
+ if ((did_clamp) && (max_clamp_mag > MAX_CLAMP_MAG_ACCEPT_THRESH))
+ continue;
+ }
+
+ // This will distort the endpoints if the ISE endpoint range isn't 256 levels (20).
+ // It could massively distort the endpoints, but still result in a valid encoding.
+ quantize_ise_endpoints(ise_endpoint_range, orig_trial_endpoints, trial_endpoints, NUM_MODE7_ENDPOINTS);
+
+ if (!get_astc_hdr_mode_7_block_colors(trial_endpoints, &decoded_half[0][0], decoded_float, num_weight_levels, ise_weight_range, ise_endpoint_range))
+ continue;
+
+ double trial_blk_error = eval_selectors(num_pixels, trial_weights, &block_pixels_half[0][0], num_weight_levels, &decoded_half[0][0], coptions);
+ if (trial_blk_error < cur_block_error)
+ {
+ cur_block_error = trial_blk_error;
+ memcpy(pEndpoints, trial_endpoints, NUM_MODE7_ENDPOINTS);
+ memcpy(pWeights, trial_weights, num_pixels);
+ submode_used = submode;
+ improved_flag = true;
+ }
+
+ if (ise_endpoint_range == astc_helpers::BISE_256_LEVELS)
+ {
+ if (!did_clamp)
+ break;
+ }
+ }
+
+ return improved_flag;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static double encode_astc_hdr_block_mode_11(
+ uint32_t num_pixels,
+ const vec4F* pBlock_pixels,
+ uint32_t ise_weight_range,
+ uint32_t& best_submode,
+ double cur_block_error,
+ uint8_t* blk_endpoints, uint8_t* blk_weights,
+ const astc_hdr_codec_options& coptions,
+ bool direct_only,
+ uint32_t ise_endpoint_range,
+ bool uber_mode,
+ bool constrain_ise_weight8_selectors,
+ int32_t first_submode, int32_t last_submode)
+{
+ assert((ise_weight_range >= 1) && (ise_weight_range <= MAX_SUPPORTED_ISE_WEIGHT_INDEX));
+ assert((ise_endpoint_range >= astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE) && (ise_endpoint_range <= astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE));
+ assert((num_pixels >= 1) && (num_pixels <= 16));
+
+ best_submode = 0;
+
+ half_float block_pixels_half[16][3];
+ vec4F block_pixels_q16[16];
+
+ // TODO: This is done redundantly.
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ block_pixels_half[i][0] = float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][0]);
+ block_pixels_q16[i][0] = (float)half_to_qlog16(block_pixels_half[i][0]);
+
+ block_pixels_half[i][1] = float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][1]);
+ block_pixels_q16[i][1] = (float)half_to_qlog16(block_pixels_half[i][1]);
+
+ block_pixels_half[i][2] = float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][2]);
+ block_pixels_q16[i][2] = (float)half_to_qlog16(block_pixels_half[i][2]);
+
+ block_pixels_q16[i][3] = 0.0f;
+ }
+
+ const uint32_t num_weight_levels = astc_helpers::get_ise_levels(ise_weight_range);
+
+ // TODO: should match MAX_SUPPORTED_ISE_WEIGHT_INDEX
+ const uint32_t MAX_WEIGHT_LEVELS = 32;
+ (void)MAX_WEIGHT_LEVELS;
+ assert(num_weight_levels <= MAX_WEIGHT_LEVELS);
+
+ vec3F block_mean_color_q16(calc_mean(num_pixels, block_pixels_q16));
+ vec3F block_axis_q16(calc_rgb_pca(num_pixels, block_pixels_q16, block_mean_color_q16));
+
+ aabb3F color_box_q16(cInitExpand);
+
+ float l = 1e+30f, h = -1e+30f;
+ vec3F low_color_q16, high_color_q16;
+
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ color_box_q16.expand(block_pixels_q16[i]);
+
+ vec3F k(vec3F(block_pixels_q16[i]) - block_mean_color_q16);
+ float kd = k.dot(block_axis_q16);
+
+ if (kd < l)
+ {
+ l = kd;
+ low_color_q16 = block_pixels_q16[i];
+ }
+
+ if (kd > h)
+ {
+ h = kd;
+ high_color_q16 = block_pixels_q16[i];
+ }
+ }
+
+ vec3F old_low_color_q16(low_color_q16), old_high_color_q16(high_color_q16);
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ low_color_q16[i] = lerp<float>(old_low_color_q16[i], old_high_color_q16[i], 1.0f / 64.0f);
+ high_color_q16[i] = lerp<float>(old_low_color_q16[i], old_high_color_q16[i], 63.0f / 64.0f);
+ }
+
+ uint8_t trial_blk_endpoints[NUM_MODE11_ENDPOINTS];
+ uint8_t trial_blk_weights[16];
+ uint32_t trial_best_submode = 0;
+
+ clear_obj(trial_blk_endpoints);
+ clear_obj(trial_blk_weights);
+
+ double trial_blk_error = 1e+30f;
+
+ bool did_improve = try_mode11(num_pixels, trial_blk_endpoints, trial_blk_weights, trial_blk_error, trial_best_submode,
+ low_color_q16, high_color_q16,
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, direct_only, ise_endpoint_range, constrain_ise_weight8_selectors,
+ first_submode, last_submode);
+
+ // If we couldn't find ANY usable solution due to endpoint quantization, just return. There's nothing we can do.
+ if (!did_improve)
+ return cur_block_error;
+
+ // Did the solution improve?
+ if (trial_blk_error < cur_block_error)
+ {
+ cur_block_error = trial_blk_error;
+ memcpy(blk_endpoints, trial_blk_endpoints, NUM_MODE11_ENDPOINTS);
+ memcpy(blk_weights, trial_blk_weights, num_pixels);
+ best_submode = trial_best_submode;
+ }
+
+#define USE_LEAST_SQUARES (1)
+#if USE_LEAST_SQUARES
+ // least squares on the most promising trial weight indices found
+ const uint32_t NUM_LS_PASSES = 3;
+
+ for (uint32_t pass = 0; pass < NUM_LS_PASSES; pass++)
+ {
+ vec3F l_q16, h_q16;
+ if (!compute_least_squares_endpoints_rgb(num_pixels, trial_blk_weights, &g_astc_ls_weights_ise[ise_weight_range][0], &l_q16, &h_q16, block_pixels_q16, color_box_q16))
+ break;
+
+ bool was_improved = try_mode11(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ l_q16, h_q16,
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, direct_only, ise_endpoint_range, constrain_ise_weight8_selectors,
+ first_submode, last_submode);
+
+ if (!was_improved)
+ break;
+
+ // It's improved, so let's take the new weight indices.
+ memcpy(trial_blk_weights, blk_weights, num_pixels);
+
+ } // pass
+#endif
+
+ if (uber_mode)
+ {
+ // Try varying the current best weight indices. This can be expanded/improved, but at potentially great cost.
+
+ uint8_t temp_astc_weights[16];
+ memcpy(temp_astc_weights, trial_blk_weights, num_pixels);
+
+ uint32_t min_lin_sel = 256, max_lin_sel = 0;
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ const uint32_t astc_sel = temp_astc_weights[i];
+
+ const uint32_t lin_sel = g_map_astc_to_linear_order[ise_weight_range][astc_sel];
+ assert(lin_sel < num_weight_levels);
+
+ min_lin_sel = minimumu(min_lin_sel, lin_sel);
+ max_lin_sel = maximumu(max_lin_sel, lin_sel);
+ }
+
+ bool was_improved = false;
+ (void)was_improved;
+
+ {
+ bool weights_changed = false;
+ uint8_t trial_weights[16];
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ uint32_t astc_sel = temp_astc_weights[i];
+ uint32_t lin_sel = g_map_astc_to_linear_order[ise_weight_range][astc_sel];
+
+ if ((lin_sel == min_lin_sel) && (lin_sel < (num_weight_levels - 1)))
+ {
+ lin_sel++;
+ weights_changed = true;
+ }
+
+ trial_weights[i] = g_map_linear_to_astc_order[ise_weight_range][lin_sel];
+ }
+
+ if (weights_changed)
+ {
+ vec3F l_q16, h_q16;
+ if (compute_least_squares_endpoints_rgb(num_pixels, trial_weights, &g_astc_ls_weights_ise[ise_weight_range][0], &l_q16, &h_q16, block_pixels_q16, color_box_q16))
+ {
+ if (try_mode11(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ l_q16, h_q16,
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, direct_only, ise_endpoint_range, constrain_ise_weight8_selectors,
+ first_submode, last_submode))
+ {
+ was_improved = true;
+ }
+ }
+ }
+ }
+
+ {
+ bool weights_changed = false;
+ uint8_t trial_weights[16];
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ uint32_t astc_sel = temp_astc_weights[i];
+ uint32_t lin_sel = g_map_astc_to_linear_order[ise_weight_range][astc_sel];
+
+ if ((lin_sel == max_lin_sel) && (lin_sel > 0))
+ {
+ lin_sel--;
+ weights_changed = true;
+ }
+
+ trial_weights[i] = g_map_linear_to_astc_order[ise_weight_range][lin_sel];
+ }
+
+ if (weights_changed)
+ {
+ vec3F l_q16, h_q16;
+ if (compute_least_squares_endpoints_rgb(num_pixels, trial_weights, &g_astc_ls_weights_ise[ise_weight_range][0], &l_q16, &h_q16, block_pixels_q16, color_box_q16))
+ {
+ if (try_mode11(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ l_q16, h_q16,
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, direct_only, ise_endpoint_range, constrain_ise_weight8_selectors,
+ first_submode, last_submode))
+ {
+ was_improved = true;
+ }
+ }
+ }
+ }
+
+ {
+ bool weights_changed = false;
+ uint8_t trial_weights[16];
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ uint32_t astc_sel = temp_astc_weights[i];
+ uint32_t lin_sel = g_map_astc_to_linear_order[ise_weight_range][astc_sel];
+
+ if ((lin_sel == max_lin_sel) && (lin_sel > 0))
+ {
+ lin_sel--;
+ weights_changed = true;
+ }
+ else if ((lin_sel == min_lin_sel) && (lin_sel < (num_weight_levels - 1)))
+ {
+ lin_sel++;
+ weights_changed = true;
+ }
+
+ trial_weights[i] = g_map_linear_to_astc_order[ise_weight_range][lin_sel];
+ }
+
+ if (weights_changed)
+ {
+ vec3F l_q16, h_q16;
+ if (compute_least_squares_endpoints_rgb(num_pixels, trial_weights, &g_astc_ls_weights_ise[ise_weight_range][0], &l_q16, &h_q16, block_pixels_q16, color_box_q16))
+ {
+ if (try_mode11(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ l_q16, h_q16,
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, direct_only, ise_endpoint_range, constrain_ise_weight8_selectors,
+ first_submode, last_submode))
+ {
+ was_improved = true;
+ }
+ }
+ }
+ }
+ } // uber_mode
+
+ return cur_block_error;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static double encode_astc_hdr_block_mode_7(
+ uint32_t num_pixels, const vec4F* pBlock_pixels,
+ uint32_t ise_weight_range,
+ uint32_t& best_submode,
+ double cur_block_error,
+ uint8_t* blk_endpoints, //[4]
+ uint8_t* blk_weights, // [num_pixels]
+ const astc_hdr_codec_options& coptions,
+ uint32_t ise_endpoint_range)
+{
+ assert((num_pixels >= 1) && (num_pixels <= 16));
+ assert((ise_weight_range >= 1) && (ise_weight_range <= 10));
+ assert((ise_endpoint_range >= astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE) && (ise_endpoint_range <= astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE));
+ const uint32_t num_weight_levels = astc_helpers::get_ise_levels(ise_weight_range);
+
+ const uint32_t MAX_WEIGHT_LEVELS = 24;
+ assert(num_weight_levels <= MAX_WEIGHT_LEVELS);
+ BASISU_NOTE_UNUSED(MAX_WEIGHT_LEVELS);
+
+ best_submode = 0;
+
+ half_float block_pixels_half[16][3];
+
+ vec4F block_pixels_q16[16];
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ block_pixels_half[i][0] = float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][0]);
+ block_pixels_q16[i][0] = (float)half_to_qlog16(block_pixels_half[i][0]);
+
+ block_pixels_half[i][1] = float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][1]);
+ block_pixels_q16[i][1] = (float)half_to_qlog16(block_pixels_half[i][1]);
+
+ block_pixels_half[i][2] = float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][2]);
+ block_pixels_q16[i][2] = (float)half_to_qlog16(block_pixels_half[i][2]);
+
+ block_pixels_q16[i][3] = 0.0f;
+ }
+
+ vec3F block_mean_color_q16(calc_mean(num_pixels, block_pixels_q16));
+
+ vec3F block_axis_q16(0.577350259f);
+
+ aabb3F color_box_q16(cInitExpand);
+
+ float l = 1e+30f, h = -1e+30f;
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ color_box_q16.expand(block_pixels_q16[i]);
+
+ vec3F k(vec3F(block_pixels_q16[i]) - block_mean_color_q16);
+ float kd = k.dot(block_axis_q16);
+
+ l = basisu::minimum<float>(l, kd);
+ h = basisu::maximum<float>(h, kd);
+ }
+
+ vec3F low_color_q16(interp_color(block_mean_color_q16, block_axis_q16, l, color_box_q16, color_box_q16));
+ vec3F high_color_q16(interp_color(block_mean_color_q16, block_axis_q16, h, color_box_q16, color_box_q16));
+
+ low_color_q16.clamp(0.0f, MAX_QLOG16_VAL);
+ high_color_q16.clamp(0.0f, MAX_QLOG16_VAL);
+
+ vec3F diff(high_color_q16 - low_color_q16);
+ float s_q16 = diff.dot(block_axis_q16) * block_axis_q16[0];
+
+ uint8_t trial_blk_endpoints[NUM_MODE7_ENDPOINTS];
+ uint8_t trial_blk_weights[16];
+ uint32_t trial_best_submode = 0;
+
+ clear_obj(trial_blk_endpoints);
+ clear_obj(trial_blk_weights);
+
+ double trial_blk_error = 1e+30f;
+
+ bool did_improve = try_mode7(num_pixels, trial_blk_endpoints, trial_blk_weights, trial_blk_error, trial_best_submode,
+ high_color_q16, ceilf(s_q16),
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, ise_endpoint_range);
+
+ // If we couldn't find ANY usable solution due to endpoint quantization, just return. There's nothing we can do.
+ if (!did_improve)
+ {
+ return cur_block_error;
+ }
+
+ // Did the solution improve?
+ if (trial_blk_error < cur_block_error)
+ {
+ cur_block_error = trial_blk_error;
+ memcpy(blk_endpoints, trial_blk_endpoints, NUM_MODE7_ENDPOINTS);
+ memcpy(blk_weights, trial_blk_weights, num_pixels);
+ best_submode = trial_best_submode;
+ }
+
+ const float one_over_num_pixels = 1.0f / (float)num_pixels;
+
+ const uint32_t NUM_TRIALS = 2;
+ for (uint32_t trial = 0; trial < NUM_TRIALS; trial++)
+ {
+ // Given a set of selectors and S, try to compute a better high color
+ vec3F new_high_color_q16(block_mean_color_q16);
+
+ int e[2][3];
+ int cur_s = 0;
+ if (!decode_mode7_to_qlog12(trial_blk_endpoints, e, &cur_s, ise_endpoint_range))
+ break;
+
+ cur_s <<= 4;
+
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ uint32_t astc_sel = trial_blk_weights[i];
+ float lerp = g_ise_weight_lerps[ise_weight_range][astc_sel + 1] * (1.0f / 64.0f);
+
+ float k = (float)cur_s * (1.0f - lerp) * one_over_num_pixels;
+ new_high_color_q16[0] += k;
+ new_high_color_q16[1] += k;
+ new_high_color_q16[2] += k;
+ }
+
+ bool improved = try_mode7(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ new_high_color_q16, (float)cur_s,
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, ise_endpoint_range);
+
+ if (improved)
+ {
+ memcpy(trial_blk_endpoints, blk_endpoints, NUM_MODE7_ENDPOINTS);
+ memcpy(trial_blk_weights, blk_weights, num_pixels);
+ }
+
+ // Given a set of selectors and a high color, try to compute a better S.
+ float t = 0.0f;
+
+ for (uint32_t i = 0; i < num_pixels; i++)
+ {
+ uint32_t astc_sel = trial_blk_weights[i];
+ float lerp = g_ise_weight_lerps[ise_weight_range][astc_sel + 1] * (1.0f / 64.0f);
+
+ t += (1.0f) - lerp;
+ }
+
+ t *= one_over_num_pixels;
+
+ //int e[2][3];
+ if (!decode_mode7_to_qlog12(trial_blk_endpoints, e, nullptr, ise_endpoint_range))
+ break;
+
+ vec3F cur_h_q16((float)(e[1][0] << 4), (float)(e[1][1] << 4), (float)(e[1][2] << 4));
+
+ if (fabs(t) > .0000125f)
+ {
+ float s_r = (cur_h_q16[0] - block_mean_color_q16[0]) / t;
+ float s_g = (cur_h_q16[1] - block_mean_color_q16[1]) / t;
+ float s_b = (cur_h_q16[2] - block_mean_color_q16[2]) / t;
+
+ // TODO: gather statistics on these
+ if (try_mode7(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ cur_h_q16, ceilf(s_r),
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, ise_endpoint_range))
+ {
+ improved = true;
+ }
+
+ if (try_mode7(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ cur_h_q16, ceilf(s_g),
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, ise_endpoint_range))
+ {
+ improved = true;
+ }
+
+ if (try_mode7(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ cur_h_q16, ceilf(s_b),
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, ise_endpoint_range))
+ {
+ improved = true;
+ }
+
+ if (try_mode7(num_pixels, blk_endpoints, blk_weights, cur_block_error, best_submode,
+ cur_h_q16, ceilf((s_r + s_g + s_b) / 3.0f),
+ block_pixels_half, num_weight_levels, ise_weight_range, coptions, ise_endpoint_range))
+ {
+ improved = true;
+ }
+ }
+
+ if (!improved)
+ break;
+
+ memcpy(trial_blk_endpoints, blk_endpoints, NUM_MODE7_ENDPOINTS);
+ memcpy(trial_blk_weights, blk_weights, num_pixels);
+
+ } // trial
+
+ return cur_block_error;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static bool pack_solid(const vec4F* pBlock_linear_colors, basisu::vector<astc_hdr_pack_results>& all_results, const astc_hdr_codec_options& coptions)
+{
+ float r = 0.0f, g = 0.0f, b = 0.0f;
+
+ const float LOG_BIAS = .125f;
+
+ bool solid_block = true;
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ if ((pBlock_linear_colors[0][0] != pBlock_linear_colors[i][0]) ||
+ (pBlock_linear_colors[0][1] != pBlock_linear_colors[i][1]) ||
+ (pBlock_linear_colors[0][2] != pBlock_linear_colors[i][2]))
+ {
+ solid_block = false;
+ }
+
+ r += log2f(pBlock_linear_colors[i][0] + LOG_BIAS);
+ g += log2f(pBlock_linear_colors[i][1] + LOG_BIAS);
+ b += log2f(pBlock_linear_colors[i][2] + LOG_BIAS);
+ }
+
+ if (solid_block)
+ {
+ r = pBlock_linear_colors[0][0];
+ g = pBlock_linear_colors[0][1];
+ b = pBlock_linear_colors[0][2];
+ }
+ else
+ {
+ r = maximum<float>(0.0f, powf(2.0f, r * (1.0f / 16.0f)) - LOG_BIAS);
+ g = maximum<float>(0.0f, powf(2.0f, g * (1.0f / 16.0f)) - LOG_BIAS);
+ b = maximum<float>(0.0f, powf(2.0f, b * (1.0f / 16.0f)) - LOG_BIAS);
+
+ // for safety
+ r = minimum<float>(r, MAX_HALF_FLOAT);
+ g = minimum<float>(g, MAX_HALF_FLOAT);
+ b = minimum<float>(b, MAX_HALF_FLOAT);
+ }
+
+ half_float rh = float_to_half_non_neg_no_nan_inf(r), gh = float_to_half_non_neg_no_nan_inf(g), bh = float_to_half_non_neg_no_nan_inf(b), ah = float_to_half_non_neg_no_nan_inf(1.0f);
+
+ astc_hdr_pack_results results;
+ results.clear();
+
+ uint8_t* packed_blk = (uint8_t*)&results.m_solid_blk;
+ results.m_is_solid = true;
+
+ packed_blk[0] = 0b11111100;
+ packed_blk[1] = 255;
+ packed_blk[2] = 255;
+ packed_blk[3] = 255;
+ packed_blk[4] = 255;
+ packed_blk[5] = 255;
+ packed_blk[6] = 255;
+ packed_blk[7] = 255;
+
+ packed_blk[8] = (uint8_t)rh;
+ packed_blk[9] = (uint8_t)(rh >> 8);
+ packed_blk[10] = (uint8_t)gh;
+ packed_blk[11] = (uint8_t)(gh >> 8);
+ packed_blk[12] = (uint8_t)bh;
+ packed_blk[13] = (uint8_t)(bh >> 8);
+ packed_blk[14] = (uint8_t)ah;
+ packed_blk[15] = (uint8_t)(ah >> 8);
+
+ results.m_best_block_error = 0;
+
+ if (!solid_block)
+ {
+ const float R_WEIGHT = coptions.m_r_err_scale;
+ const float G_WEIGHT = coptions.m_g_err_scale;
+
+ // This MUST match how errors are computed in eval_selectors().
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ half_float dr = float_to_half_non_neg_no_nan_inf(pBlock_linear_colors[i][0]), dg = float_to_half_non_neg_no_nan_inf(pBlock_linear_colors[i][1]), db = float_to_half_non_neg_no_nan_inf(pBlock_linear_colors[i][2]);
+ double rd = q(rh) - q(dr);
+ double gd = q(gh) - q(dg);
+ double bd = q(bh) - q(db);
+
+ double e = R_WEIGHT * (rd * rd) + G_WEIGHT * (gd * gd) + bd * bd;
+
+ results.m_best_block_error += e;
+ }
+ }
+
+ const half_float hc[3] = { rh, gh, bh };
+
+ bc6h_enc_block_solid_color(&results.m_bc6h_block, hc);
+
+ all_results.push_back(results);
+
+ return solid_block;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static void pack_mode11(
+ const vec4F* pBlock_linear_colors,
+ basisu::vector<astc_hdr_pack_results>& all_results,
+ const astc_hdr_codec_options& coptions,
+ uint32_t first_weight_ise_range, uint32_t last_weight_ise_range, bool constrain_ise_weight8_selectors)
+{
+ uint8_t trial_endpoints[NUM_MODE11_ENDPOINTS], trial_weights[16];
+ uint32_t trial_submode11 = 0;
+
+ clear_obj(trial_endpoints);
+ clear_obj(trial_weights);
+
+ for (uint32_t weight_ise_range = first_weight_ise_range; weight_ise_range <= last_weight_ise_range; weight_ise_range++)
+ {
+ const bool direct_only = coptions.m_mode11_direct_only;
+
+ uint32_t endpoint_ise_range = astc_helpers::BISE_256_LEVELS;
+ if (weight_ise_range == astc_helpers::BISE_16_LEVELS)
+ endpoint_ise_range = astc_helpers::BISE_192_LEVELS;
+ else
+ {
+ assert(weight_ise_range < astc_helpers::BISE_16_LEVELS);
+ }
+
+ double trial_error = encode_astc_hdr_block_mode_11(16, pBlock_linear_colors, weight_ise_range, trial_submode11, 1e+30f, trial_endpoints, trial_weights, coptions, direct_only,
+ endpoint_ise_range, coptions.m_mode11_uber_mode && (weight_ise_range >= astc_helpers::BISE_4_LEVELS) && coptions.m_allow_uber_mode, constrain_ise_weight8_selectors, coptions.m_first_mode11_submode, coptions.m_last_mode11_submode);
+
+ if (trial_error < 1e+30f)
+ {
+ astc_hdr_pack_results results;
+ results.clear();
+
+ results.m_best_block_error = trial_error;
+
+ results.m_best_submodes[0] = trial_submode11;
+ results.m_constrained_weights = constrain_ise_weight8_selectors;
+
+ results.m_best_blk.m_num_partitions = 1;
+ results.m_best_blk.m_color_endpoint_modes[0] = 11;
+ results.m_best_blk.m_weight_ise_range = weight_ise_range;
+ results.m_best_blk.m_endpoint_ise_range = endpoint_ise_range;
+
+ memcpy(results.m_best_blk.m_endpoints, trial_endpoints, NUM_MODE11_ENDPOINTS);
+ memcpy(results.m_best_blk.m_weights, trial_weights, 16);
+
+#ifdef _DEBUG
+ {
+ half_float block_pixels_half[16][3];
+
+ vec4F block_pixels_q16[16];
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ block_pixels_half[i][0] = float_to_half_non_neg_no_nan_inf(pBlock_linear_colors[i][0]);
+ block_pixels_half[i][1] = float_to_half_non_neg_no_nan_inf(pBlock_linear_colors[i][1]);
+ block_pixels_half[i][2] = float_to_half_non_neg_no_nan_inf(pBlock_linear_colors[i][2]);
+ }
+
+ half_float unpacked_astc_blk_rgba[4][4][4];
+ bool res = astc_helpers::decode_block(results.m_best_blk, unpacked_astc_blk_rgba, 4, 4, astc_helpers::cDecodeModeHDR16);
+ assert(res);
+
+ half_float unpacked_astc_blk_rgb[4][4][3];
+ for (uint32_t y = 0; y < 4; y++)
+ for (uint32_t x = 0; x < 4; x++)
+ for (uint32_t c = 0; c < 3; c++)
+ unpacked_astc_blk_rgb[y][x][c] = unpacked_astc_blk_rgba[y][x][c];
+
+ double cmp_err = compute_block_error(&block_pixels_half[0][0], &unpacked_astc_blk_rgb[0][0][0], coptions);
+ assert(results.m_best_block_error == cmp_err);
+ }
+#endif
+
+ // transcode to BC6H
+ assert(results.m_best_blk.m_color_endpoint_modes[0] == 11);
+
+ // Get qlog12 endpoints
+ int e[2][3];
+ bool success = decode_mode11_to_qlog12(results.m_best_blk.m_endpoints, e, results.m_best_blk.m_endpoint_ise_range);
+ assert(success);
+ BASISU_NOTE_UNUSED(success);
+
+ // Transform endpoints to half float
+ half_float h_e[3][2] =
+ {
+ { qlog_to_half(e[0][0], 12), qlog_to_half(e[1][0], 12) },
+ { qlog_to_half(e[0][1], 12), qlog_to_half(e[1][1], 12) },
+ { qlog_to_half(e[0][2], 12), qlog_to_half(e[1][2], 12) }
+ };
+
+ // Transcode to bc6h
+ success = transcode_bc6h_1subset(h_e, results.m_best_blk, results.m_bc6h_block);
+ assert(success);
+
+ all_results.push_back(results);
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static void pack_mode7_single_part(const vec4F* pBlock_linear_colors, basisu::vector<astc_hdr_pack_results>& all_results, const astc_hdr_codec_options& coptions)
+{
+ uint8_t trial_endpoints[NUM_MODE7_ENDPOINTS], trial_weights[16];
+ uint32_t trial_submode7 = 0;
+
+ clear_obj(trial_endpoints);
+ clear_obj(trial_weights);
+
+ for (uint32_t weight_ise_range = coptions.m_first_mode7_part1_weight_ise_range; weight_ise_range <= coptions.m_last_mode7_part1_weight_ise_range; weight_ise_range++)
+ {
+ const uint32_t ise_endpoint_range = astc_helpers::BISE_256_LEVELS;
+
+ double trial_error = encode_astc_hdr_block_mode_7(16, pBlock_linear_colors, weight_ise_range, trial_submode7, 1e+30f, trial_endpoints, trial_weights, coptions, ise_endpoint_range);
+
+ if (trial_error < 1e+30f)
+ {
+ astc_hdr_pack_results results;
+ results.clear();
+
+ results.m_best_block_error = trial_error;
+
+ results.m_best_submodes[0] = trial_submode7;
+
+ results.m_best_blk.m_num_partitions = 1;
+ results.m_best_blk.m_color_endpoint_modes[0] = 7;
+ results.m_best_blk.m_weight_ise_range = weight_ise_range;
+ results.m_best_blk.m_endpoint_ise_range = ise_endpoint_range;
+
+ memcpy(results.m_best_blk.m_endpoints, trial_endpoints, NUM_MODE7_ENDPOINTS);
+ memcpy(results.m_best_blk.m_weights, trial_weights, 16);
+
+ // transcode to BC6H
+ assert(results.m_best_blk.m_color_endpoint_modes[0] == 7);
+
+ // Get qlog12 endpoints
+ int e[2][3];
+ if (!decode_mode7_to_qlog12(results.m_best_blk.m_endpoints, e, nullptr, results.m_best_blk.m_endpoint_ise_range))
+ continue;
+
+ // Transform endpoints to half float
+ half_float h_e[3][2] =
+ {
+ { qlog_to_half(e[0][0], 12), qlog_to_half(e[1][0], 12) },
+ { qlog_to_half(e[0][1], 12), qlog_to_half(e[1][1], 12) },
+ { qlog_to_half(e[0][2], 12), qlog_to_half(e[1][2], 12) }
+ };
+
+ // Transcode to bc6h
+ bool status = transcode_bc6h_1subset(h_e, results.m_best_blk, results.m_bc6h_block);
+ assert(status);
+ (void)status;
+
+ all_results.push_back(results);
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static bool estimate_partition2(const vec4F* pBlock_pixels, int* pBest_parts, uint32_t num_best_parts)
+{
+ assert(num_best_parts <= basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2);
+
+ vec3F training_vecs[16], mean(0.0f);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ vec3F& v = training_vecs[i];
+
+ v[0] = (float)float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][0]);
+ v[1] = (float)float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][1]);
+ v[2] = (float)float_to_half_non_neg_no_nan_inf(pBlock_pixels[i][2]);
+
+ mean += v;
+ }
+ mean *= (1.0f / 16.0f);
+
+ vec3F cluster_centroids[2] = { mean - vec3F(.1f), mean + vec3F(.1f) };
+
+ uint32_t cluster_pixels[2][16];
+ uint32_t num_cluster_pixels[2];
+ vec3F new_cluster_means[2];
+
+ for (uint32_t s = 0; s < 4; s++)
+ {
+ num_cluster_pixels[0] = 0;
+ num_cluster_pixels[1] = 0;
+
+ new_cluster_means[0].clear();
+ new_cluster_means[1].clear();
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ float d0 = training_vecs[i].squared_distance(cluster_centroids[0]);
+ float d1 = training_vecs[i].squared_distance(cluster_centroids[1]);
+
+ if (d0 < d1)
+ {
+ cluster_pixels[0][num_cluster_pixels[0]] = i;
+ new_cluster_means[0] += training_vecs[i];
+ num_cluster_pixels[0]++;
+ }
+ else
+ {
+ cluster_pixels[1][num_cluster_pixels[1]] = i;
+ new_cluster_means[1] += training_vecs[i];
+ num_cluster_pixels[1]++;
+ }
+ }
+
+ if (!num_cluster_pixels[0] || !num_cluster_pixels[1])
+ return false;
+
+ cluster_centroids[0] = new_cluster_means[0] / (float)num_cluster_pixels[0];
+ cluster_centroids[1] = new_cluster_means[1] / (float)num_cluster_pixels[1];
+ }
+
+ int desired_parts[4][4]; // [y][x]
+ for (uint32_t p = 0; p < 2; p++)
+ {
+ for (uint32_t i = 0; i < num_cluster_pixels[p]; i++)
+ {
+ const uint32_t pix_index = cluster_pixels[p][i];
+
+ desired_parts[pix_index >> 2][pix_index & 3] = p;
+ }
+ }
+
+ uint32_t part_similarity[basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2];
+
+ for (uint32_t part_index = 0; part_index < basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2; part_index++)
+ {
+ const uint32_t bc7_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_bc7;
+
+ int total_sim_non_inv = 0;
+ int total_sim_inv = 0;
+
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ int part = basist::g_bc7_partition2[16 * bc7_pattern + x + y * 4];
+
+ if (part == desired_parts[y][x])
+ total_sim_non_inv++;
+
+ if ((part ^ 1) == desired_parts[y][x])
+ total_sim_inv++;
+ }
+ }
+
+ int total_sim = maximum(total_sim_non_inv, total_sim_inv);
+
+ part_similarity[part_index] = (total_sim << 8) | part_index;
+
+ } // part_index;
+
+ std::sort(part_similarity, part_similarity + basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2);
+
+ for (uint32_t i = 0; i < num_best_parts; i++)
+ pBest_parts[i] = part_similarity[(basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2 - 1) - i] & 0xFF;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static void pack_mode7_2part(const vec4F* pBlock_linear_colors, basisu::vector<astc_hdr_pack_results>& all_results, const astc_hdr_codec_options& coptions,
+ int num_estimated_partitions, const int *pEstimated_partitions,
+ uint32_t first_weight_ise_range, uint32_t last_weight_ise_range)
+{
+ assert(coptions.m_mode7_part2_part_masks);
+
+ astc_helpers::log_astc_block trial_blk;
+ clear_obj(trial_blk);
+ trial_blk.m_grid_width = 4;
+ trial_blk.m_grid_height = 4;
+
+ trial_blk.m_num_partitions = 2;
+ trial_blk.m_color_endpoint_modes[0] = 7;
+ trial_blk.m_color_endpoint_modes[1] = 7;
+
+ uint32_t first_part_index = 0, last_part_index = basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2;
+
+ if (num_estimated_partitions)
+ {
+ first_part_index = 0;
+ last_part_index = num_estimated_partitions;
+ }
+
+ for (uint32_t part_index_iter = first_part_index; part_index_iter < last_part_index; ++part_index_iter)
+ {
+ uint32_t part_index;
+ if (num_estimated_partitions)
+ {
+ part_index = pEstimated_partitions[part_index_iter];
+ assert(part_index < basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2);
+ }
+ else
+ {
+ part_index = part_index_iter;
+ if (((1U << part_index) & coptions.m_mode7_part2_part_masks) == 0)
+ continue;
+ }
+
+ const uint32_t astc_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_astc;
+ const uint32_t bc7_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_bc7;
+ const bool invert_flag = basist::g_astc_bc7_common_partitions2[part_index].m_invert;
+
+ vec4F part_pixels[2][16];
+ uint32_t pixel_part_index[4][4]; // [y][x]
+ uint32_t num_part_pixels[2] = { 0, 0 };
+
+ // Extract each subset's texels for this partition pattern
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ uint32_t part = basist::g_bc7_partition2[16 * bc7_pattern + x + y * 4];
+ if (invert_flag)
+ part = 1 - part;
+
+ pixel_part_index[y][x] = part;
+ part_pixels[part][num_part_pixels[part]] = pBlock_linear_colors[x + y * 4];
+
+ num_part_pixels[part]++;
+ }
+ }
+
+ trial_blk.m_partition_id = astc_pattern;
+
+ for (uint32_t weight_ise_range = first_weight_ise_range; weight_ise_range <= last_weight_ise_range; weight_ise_range++)
+ {
+ assert(weight_ise_range <= astc_helpers::BISE_8_LEVELS);
+
+ uint32_t ise_endpoint_range = astc_helpers::BISE_256_LEVELS;
+ if (weight_ise_range == astc_helpers::BISE_5_LEVELS)
+ ise_endpoint_range = astc_helpers::BISE_192_LEVELS;
+ else if (weight_ise_range == astc_helpers::BISE_6_LEVELS)
+ ise_endpoint_range = astc_helpers::BISE_128_LEVELS;
+ else if (weight_ise_range == astc_helpers::BISE_8_LEVELS)
+ ise_endpoint_range = astc_helpers::BISE_80_LEVELS;
+
+ uint8_t trial_endpoints[2][NUM_MODE7_ENDPOINTS], trial_weights[2][16];
+ uint32_t trial_submode7[2];
+
+ clear_obj(trial_endpoints);
+ clear_obj(trial_weights);
+ clear_obj(trial_submode7);
+
+ double total_trial_err = 0;
+ for (uint32_t pack_part_index = 0; pack_part_index < 2; pack_part_index++)
+ {
+ total_trial_err += encode_astc_hdr_block_mode_7(
+ num_part_pixels[pack_part_index], &part_pixels[pack_part_index][0],
+ weight_ise_range, trial_submode7[pack_part_index], 1e+30f,
+ &trial_endpoints[pack_part_index][0], &trial_weights[pack_part_index][0], coptions, ise_endpoint_range);
+
+ } // pack_part_index
+
+ if (total_trial_err < 1e+30f)
+ {
+ trial_blk.m_weight_ise_range = weight_ise_range;
+ trial_blk.m_endpoint_ise_range = ise_endpoint_range;
+
+ for (uint32_t pack_part_index = 0; pack_part_index < 2; pack_part_index++)
+ memcpy(&trial_blk.m_endpoints[pack_part_index * NUM_MODE7_ENDPOINTS], &trial_endpoints[pack_part_index][0], NUM_MODE7_ENDPOINTS);
+
+ uint32_t src_pixel_index[2] = { 0, 0 };
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ uint32_t p = pixel_part_index[y][x];
+ trial_blk.m_weights[x + y * 4] = trial_weights[p][src_pixel_index[p]++];
+ }
+ }
+
+ astc_hdr_pack_results results;
+ results.clear();
+
+ results.m_best_block_error = total_trial_err;
+ results.m_best_submodes[0] = trial_submode7[0];
+ results.m_best_submodes[1] = trial_submode7[1];
+ results.m_best_pat_index = part_index;
+
+ results.m_best_blk = trial_blk;
+
+ bool status = transcode_bc6h_2subsets(part_index, results.m_best_blk, results.m_bc6h_block);
+ assert(status);
+ BASISU_NOTE_UNUSED(status);
+
+ all_results.push_back(results);
+ }
+
+ } // weight_ise_range
+
+ } // part_index
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+static void pack_mode11_2part(const vec4F* pBlock_linear_colors, basisu::vector<astc_hdr_pack_results>& all_results, const astc_hdr_codec_options& coptions,
+ int num_estimated_partitions, const int* pEstimated_partitions)
+{
+ assert(coptions.m_mode11_part2_part_masks);
+
+ astc_helpers::log_astc_block trial_blk;
+ clear_obj(trial_blk);
+ trial_blk.m_grid_width = 4;
+ trial_blk.m_grid_height = 4;
+
+ trial_blk.m_num_partitions = 2;
+ trial_blk.m_color_endpoint_modes[0] = 11;
+ trial_blk.m_color_endpoint_modes[1] = 11;
+
+ uint32_t first_part_index = 0, last_part_index = basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2;
+
+ if (num_estimated_partitions)
+ {
+ first_part_index = 0;
+ last_part_index = num_estimated_partitions;
+ }
+
+ for (uint32_t part_index_iter = first_part_index; part_index_iter < last_part_index; ++part_index_iter)
+ {
+ uint32_t part_index;
+ if (num_estimated_partitions)
+ {
+ part_index = pEstimated_partitions[part_index_iter];
+ assert(part_index < basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2);
+ }
+ else
+ {
+ part_index = part_index_iter;
+ if (((1U << part_index) & coptions.m_mode11_part2_part_masks) == 0)
+ continue;
+ }
+
+ const uint32_t astc_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_astc;
+ const uint32_t bc7_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_bc7;
+ const bool invert_flag = basist::g_astc_bc7_common_partitions2[part_index].m_invert;
+
+ vec4F part_pixels[2][16];
+ uint32_t pixel_part_index[4][4]; // [y][x]
+ uint32_t num_part_pixels[2] = { 0, 0 };
+
+ // Extract each subset's texels for this partition pattern
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ uint32_t part = basist::g_bc7_partition2[16 * bc7_pattern + x + y * 4];
+ if (invert_flag)
+ part = 1 - part;
+
+ pixel_part_index[y][x] = part;
+ part_pixels[part][num_part_pixels[part]] = pBlock_linear_colors[x + y * 4];
+
+ num_part_pixels[part]++;
+ }
+ }
+
+ trial_blk.m_partition_id = astc_pattern;
+
+ for (uint32_t weight_ise_range = coptions.m_first_mode11_part2_weight_ise_range; weight_ise_range <= coptions.m_last_mode11_part2_weight_ise_range; weight_ise_range++)
+ {
+ bool direct_only = false;
+ uint32_t ise_endpoint_range = astc_helpers::BISE_64_LEVELS;
+ if (weight_ise_range == astc_helpers::BISE_4_LEVELS)
+ ise_endpoint_range = astc_helpers::BISE_40_LEVELS;
+
+ uint8_t trial_endpoints[2][NUM_MODE11_ENDPOINTS], trial_weights[2][16];
+ uint32_t trial_submode11[2];
+
+ clear_obj(trial_endpoints);
+ clear_obj(trial_weights);
+ clear_obj(trial_submode11);
+
+ double total_trial_err = 0;
+ for (uint32_t pack_part_index = 0; pack_part_index < 2; pack_part_index++)
+ {
+ total_trial_err += encode_astc_hdr_block_mode_11(
+ num_part_pixels[pack_part_index], &part_pixels[pack_part_index][0],
+ weight_ise_range, trial_submode11[pack_part_index], 1e+30f,
+ &trial_endpoints[pack_part_index][0], &trial_weights[pack_part_index][0], coptions,
+ direct_only, ise_endpoint_range, coptions.m_mode11_uber_mode && (weight_ise_range >= astc_helpers::BISE_4_LEVELS) && coptions.m_allow_uber_mode, false,
+ coptions.m_first_mode11_submode, coptions.m_last_mode11_submode);
+
+ } // pack_part_index
+
+ if (total_trial_err < 1e+30f)
+ {
+ trial_blk.m_weight_ise_range = weight_ise_range;
+ trial_blk.m_endpoint_ise_range = ise_endpoint_range;
+
+ for (uint32_t pack_part_index = 0; pack_part_index < 2; pack_part_index++)
+ memcpy(&trial_blk.m_endpoints[pack_part_index * NUM_MODE11_ENDPOINTS], &trial_endpoints[pack_part_index][0], NUM_MODE11_ENDPOINTS);
+
+ uint32_t src_pixel_index[2] = { 0, 0 };
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ uint32_t p = pixel_part_index[y][x];
+ trial_blk.m_weights[x + y * 4] = trial_weights[p][src_pixel_index[p]++];
+ }
+ }
+
+ astc_hdr_pack_results results;
+ results.clear();
+
+ results.m_best_block_error = total_trial_err;
+ results.m_best_submodes[0] = trial_submode11[0];
+ results.m_best_submodes[1] = trial_submode11[1];
+ results.m_best_pat_index = part_index;
+
+ results.m_best_blk = trial_blk;
+
+ bool status = transcode_bc6h_2subsets(part_index, results.m_best_blk, results.m_bc6h_block);
+ assert(status);
+ BASISU_NOTE_UNUSED(status);
+
+ all_results.push_back(results);
+ }
+
+ } // weight_ise_range
+
+ } // part_index
+}
+
+//--------------------------------------------------------------------------------------------------------------------------
+
+bool g_astc_hdr_enc_initialized;
+
+void astc_hdr_enc_init()
+{
+ if (g_astc_hdr_enc_initialized)
+ return;
+
+ astc_hdr_core_init();
+
+ astc_helpers::init_tables(true);
+
+ init_qlog_tables();
+
+ encode_astc_hdr_init();
+
+ g_astc_hdr_enc_initialized = true;
+}
+
+bool astc_hdr_enc_block(
+ const float* pRGBPixels,
+ const astc_hdr_codec_options& coptions,
+ basisu::vector<astc_hdr_pack_results>& all_results)
+{
+ assert(g_astc_hdr_enc_initialized);
+ if (!g_astc_hdr_enc_initialized)
+ {
+ // astc_hdr_enc_init() MUST be called first.
+ assert(0);
+ return false;
+ }
+
+ all_results.resize(0);
+
+ vec4F block_linear_colors[16];
+
+ // Sanity check the input block.
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ for (uint32_t j = 0; j < 3; j++)
+ {
+ float v = pRGBPixels[i * 3 + j];
+
+ if (std::isinf(v) || std::isnan(v))
+ {
+ // Input pixels cannot be NaN or +-Inf.
+ assert(0);
+ return false;
+ }
+
+ if (v < 0.0f)
+ {
+ // Input pixels cannot be signed.
+ assert(0);
+ return false;
+ }
+
+ if (v > MAX_HALF_FLOAT)
+ {
+ // Too large for half float.
+ assert(0);
+ return false;
+ }
+
+ block_linear_colors[i][j] = v;
+ }
+
+ block_linear_colors[i][3] = 1.0f;
+ }
+
+ assert(coptions.m_use_solid || coptions.m_use_mode11 || coptions.m_use_mode7_part2 || coptions.m_use_mode7_part1 || coptions.m_use_mode11_part2);
+
+ bool is_solid = false;
+ if (coptions.m_use_solid)
+ is_solid = pack_solid(block_linear_colors, all_results, coptions);
+
+ if (!is_solid)
+ {
+ if (coptions.m_use_mode11)
+ {
+ const size_t cur_num_results = all_results.size();
+
+ pack_mode11(block_linear_colors, all_results, coptions, coptions.m_first_mode11_weight_ise_range, coptions.m_last_mode11_weight_ise_range, false);
+
+ if (coptions.m_last_mode11_weight_ise_range == astc_helpers::BISE_16_LEVELS)
+ {
+ pack_mode11(block_linear_colors, all_results, coptions, astc_helpers::BISE_16_LEVELS, astc_helpers::BISE_16_LEVELS, true);
+ }
+
+ // If we couldn't get any mode 11 results at all, and we were restricted to just trying weight ISE range 8 (which required endpoint quantization) then
+ // fall back to weight ISE range 7 (which doesn't need any endpoint quantization).
+ // This is to guarantee we always get at least 1 non-solid result.
+ if (all_results.size() == cur_num_results)
+ {
+ if (coptions.m_first_mode11_weight_ise_range == astc_helpers::BISE_16_LEVELS)
+ {
+ pack_mode11(block_linear_colors, all_results, coptions, astc_helpers::BISE_12_LEVELS, astc_helpers::BISE_12_LEVELS, false);
+ }
+ }
+ }
+
+ if (coptions.m_use_mode7_part1)
+ {
+ // Mode 7 1-subset never requires endpoint quantization, so it cannot fail to find at least one usable solution.
+ pack_mode7_single_part(block_linear_colors, all_results, coptions);
+ }
+
+ bool have_est = false;
+ int best_parts[basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2];
+
+ if ((coptions.m_use_mode7_part2) || (coptions.m_use_mode11_part2))
+ {
+ if (coptions.m_use_estimated_partitions)
+ have_est = estimate_partition2(block_linear_colors, best_parts, coptions.m_max_estimated_partitions);
+ }
+
+ if (coptions.m_use_mode7_part2)
+ {
+ const size_t cur_num_results = all_results.size();
+
+ pack_mode7_2part(block_linear_colors, all_results, coptions, have_est ? coptions.m_max_estimated_partitions : 0, best_parts,
+ coptions.m_first_mode7_part2_weight_ise_range, coptions.m_last_mode7_part2_weight_ise_range);
+
+ // If we couldn't find any packable 2-subset mode 7 results at weight levels >= 5 levels (which always requires endpoint quant), then try falling back to
+ // 5 levels which doesn't require endpoint quantization.
+ if (all_results.size() == cur_num_results)
+ {
+ if (coptions.m_first_mode7_part2_weight_ise_range >= astc_helpers::BISE_5_LEVELS)
+ {
+ pack_mode7_2part(block_linear_colors, all_results, coptions, have_est ? coptions.m_max_estimated_partitions : 0, best_parts,
+ astc_helpers::BISE_4_LEVELS, astc_helpers::BISE_4_LEVELS);
+ }
+ }
+ }
+
+ if (coptions.m_use_mode11_part2)
+ {
+ // This always requires endpoint quant, so it could fail to find any usable solutions.
+ pack_mode11_2part(block_linear_colors, all_results, coptions, have_est ? coptions.m_max_estimated_partitions : 0, best_parts);
+ }
+ }
+
+ if (coptions.m_refine_weights)
+ {
+ // TODO: Move this above, do it once only.
+ basist::half_float rgb_pixels_half[16 * 3];
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ rgb_pixels_half[i * 3 + 0] = float_to_half_non_neg_no_nan_inf(pRGBPixels[i * 3 + 0]);
+ rgb_pixels_half[i * 3 + 1] = float_to_half_non_neg_no_nan_inf(pRGBPixels[i * 3 + 1]);
+ rgb_pixels_half[i * 3 + 2] = float_to_half_non_neg_no_nan_inf(pRGBPixels[i * 3 + 2]);
+ }
+
+ for (uint32_t i = 0; i < all_results.size(); i++)
+ {
+ bool status = astc_hdr_refine_weights(rgb_pixels_half, all_results[i], coptions, coptions.m_bc6h_err_weight, &all_results[i].m_improved_via_refinement_flag);
+ assert(status);
+ BASISU_NOTE_UNUSED(status);
+ }
+ }
+
+ return true;
+}
+
+bool astc_hdr_pack_results_to_block(astc_blk& dst_blk, const astc_hdr_pack_results& results)
+{
+ assert(g_astc_hdr_enc_initialized);
+ if (!g_astc_hdr_enc_initialized)
+ return false;
+
+ if (results.m_is_solid)
+ {
+ memcpy(&dst_blk, &results.m_solid_blk, sizeof(results.m_solid_blk));
+ }
+ else
+ {
+ bool status = astc_helpers::pack_astc_block((astc_helpers::astc_block&)dst_blk, results.m_best_blk);
+ if (!status)
+ {
+ assert(0);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Refines a block's chosen weight indices, balancing BC6H and ASTC HDR error.
+bool astc_hdr_refine_weights(const half_float *pSource_block, astc_hdr_pack_results& cur_results, const astc_hdr_codec_options& coptions, float bc6h_weight, bool *pImproved_flag)
+{
+ if (pImproved_flag)
+ *pImproved_flag = false;
+
+ if (cur_results.m_is_solid)
+ return true;
+
+ const uint32_t total_weights = astc_helpers::get_ise_levels(cur_results.m_best_blk.m_weight_ise_range);
+
+ assert((total_weights >= 3) && (total_weights <= 16));
+
+ double best_err[4][4];
+ uint8_t best_weight[4][4];
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ best_err[y][x] = 1e+30f;
+ best_weight[y][x] = 0;
+ }
+ }
+
+ astc_hdr_pack_results temp_results;
+
+ const float c_weights[3] = { coptions.m_r_err_scale, coptions.m_g_err_scale, 1.0f };
+
+ for (uint32_t weight_index = 0; weight_index < total_weights; weight_index++)
+ {
+ temp_results = cur_results;
+ for (uint32_t i = 0; i < 16; i++)
+ temp_results.m_best_blk.m_weights[i] = (uint8_t)weight_index;
+
+ half_float unpacked_astc_blk_rgba[4][4][4];
+ bool res = astc_helpers::decode_block(temp_results.m_best_blk, unpacked_astc_blk_rgba, 4, 4, astc_helpers::cDecodeModeHDR16);
+ assert(res);
+
+ basist::bc6h_block trial_bc6h_blk;
+ res = basist::astc_hdr_transcode_to_bc6h(temp_results.m_best_blk, trial_bc6h_blk);
+ assert(res);
+
+ half_float unpacked_bc6h_blk[4][4][3];
+ res = unpack_bc6h(&trial_bc6h_blk, unpacked_bc6h_blk, false);
+ assert(res);
+ BASISU_NOTE_UNUSED(res);
+
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ double total_err = 0.0f;
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ const half_float orig_c = pSource_block[(x + y * 4) * 3 + c];
+ const double orig_c_q = q(orig_c);
+
+ const half_float astc_c = unpacked_astc_blk_rgba[y][x][c];
+ const double astc_c_q = q(astc_c);
+ const double astc_e = square(astc_c_q - orig_c_q) * c_weights[c];
+
+ const half_float bc6h_c = unpacked_bc6h_blk[y][x][c];
+ const double bc6h_c_q = q(bc6h_c);
+ const double bc6h_e = square(bc6h_c_q - orig_c_q) * c_weights[c];
+
+ const double overall_err = astc_e * (1.0f - bc6h_weight) + bc6h_e * bc6h_weight;
+
+ total_err += overall_err;
+
+ } // c
+
+ if (total_err < best_err[y][x])
+ {
+ best_err[y][x] = total_err;
+ best_weight[y][x] = (uint8_t)weight_index;
+ }
+
+ } // x
+ } // y
+
+ } // weight_index
+
+ bool any_changed = false;
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ if (cur_results.m_best_blk.m_weights[i] != best_weight[i >> 2][i & 3])
+ {
+ any_changed = true;
+ break;
+ }
+ }
+
+ if (any_changed)
+ {
+ memcpy(cur_results.m_best_blk.m_weights, best_weight, 16);
+
+ {
+ bool res = basist::astc_hdr_transcode_to_bc6h(cur_results.m_best_blk, cur_results.m_bc6h_block);
+ assert(res);
+ BASISU_NOTE_UNUSED(res);
+
+ half_float unpacked_astc_blk_rgba[4][4][4];
+ res = astc_helpers::decode_block(cur_results.m_best_blk, unpacked_astc_blk_rgba, 4, 4, astc_helpers::cDecodeModeHDR16);
+ assert(res);
+
+ half_float unpacked_astc_blk_rgb[4][4][3];
+ for (uint32_t y = 0; y < 4; y++)
+ for (uint32_t x = 0; x < 4; x++)
+ for (uint32_t c = 0; c < 3; c++)
+ unpacked_astc_blk_rgb[y][x][c] = unpacked_astc_blk_rgba[y][x][c];
+
+ cur_results.m_best_block_error = compute_block_error(pSource_block, &unpacked_astc_blk_rgb[0][0][0], coptions);
+ }
+
+ if (pImproved_flag)
+ *pImproved_flag = true;
+ }
+
+ return true;
+}
+
+void astc_hdr_block_stats::update(const astc_hdr_pack_results& log_blk)
+{
+ std::lock_guard<std::mutex> lck(m_mutex);
+
+ m_total_blocks++;
+
+ if (log_blk.m_improved_via_refinement_flag)
+ m_total_refined++;
+
+ if (log_blk.m_is_solid)
+ {
+ m_total_solid++;
+ }
+ else
+ {
+ int best_weight_range = log_blk.m_best_blk.m_weight_ise_range;
+
+ if (log_blk.m_best_blk.m_color_endpoint_modes[0] == 7)
+ {
+ m_mode7_submode_hist[bounds_check(log_blk.m_best_submodes[0], 0U, 6U)]++;
+
+ if (log_blk.m_best_blk.m_num_partitions == 2)
+ {
+ m_total_mode7_2part++;
+
+ m_mode7_submode_hist[bounds_check(log_blk.m_best_submodes[1], 0U, 6U)]++;
+ m_total_2part++;
+
+ m_weight_range_hist_7_2part[bounds_check(best_weight_range, 0, 11)]++;
+
+ m_part_hist[bounds_check(log_blk.m_best_pat_index, 0U, 32U)]++;
+ }
+ else
+ {
+ m_total_mode7_1part++;
+
+ m_weight_range_hist_7[bounds_check(best_weight_range, 0, 11)]++;
+ }
+ }
+ else
+ {
+ m_mode11_submode_hist[bounds_check(log_blk.m_best_submodes[0], 0U, 9U)]++;
+ if (log_blk.m_constrained_weights)
+ m_total_mode11_1part_constrained_weights++;
+
+ if (log_blk.m_best_blk.m_num_partitions == 2)
+ {
+ m_total_mode11_2part++;
+
+ m_mode11_submode_hist[bounds_check(log_blk.m_best_submodes[1], 0U, 9U)]++;
+ m_total_2part++;
+
+ m_weight_range_hist_11_2part[bounds_check(best_weight_range, 0, 11)]++;
+
+ m_part_hist[bounds_check(log_blk.m_best_pat_index, 0U, 32U)]++;
+ }
+ else
+ {
+ m_total_mode11_1part++;
+
+ m_weight_range_hist_11[bounds_check(best_weight_range, 0, 11)]++;
+ }
+ }
+ }
+}
+
+void astc_hdr_block_stats::print()
+{
+ std::lock_guard<std::mutex> lck(m_mutex);
+
+ assert(m_total_blocks);
+ if (!m_total_blocks)
+ return;
+
+ printf("\nLow-level ASTC Encoder Statistics:\n");
+ printf("Total blocks: %u\n", m_total_blocks);
+ printf("Total solid: %u %3.2f%%\n", m_total_solid, (m_total_solid * 100.0f) / m_total_blocks);
+ printf("Total refined: %u %3.2f%%\n", m_total_refined, (m_total_refined * 100.0f) / m_total_blocks);
+
+ printf("Total mode 11, 1 partition: %u %3.2f%%\n", m_total_mode11_1part, (m_total_mode11_1part * 100.0f) / m_total_blocks);
+ printf("Total mode 11, 1 partition, constrained weights: %u %3.2f%%\n", m_total_mode11_1part_constrained_weights, (m_total_mode11_1part_constrained_weights * 100.0f) / m_total_blocks);
+ printf("Total mode 11, 2 partition: %u %3.2f%%\n", m_total_mode11_2part, (m_total_mode11_2part * 100.0f) / m_total_blocks);
+
+ printf("Total mode 7, 1 partition: %u %3.2f%%\n", m_total_mode7_1part, (m_total_mode7_1part * 100.0f) / m_total_blocks);
+ printf("Total mode 7, 2 partition: %u %3.2f%%\n", m_total_mode7_2part, (m_total_mode7_2part * 100.0f) / m_total_blocks);
+
+ printf("Total 2 partitions: %u %3.2f%%\n", m_total_2part, (m_total_2part * 100.0f) / m_total_blocks);
+ printf("\n");
+
+ printf("ISE texel weight range histogram mode 11:\n");
+ for (uint32_t i = 1; i <= MODE11_LAST_ISE_RANGE; i++)
+ printf("%u %u\n", i, m_weight_range_hist_11[i]);
+ printf("\n");
+
+ printf("ISE texel weight range histogram mode 11, 2 partition:\n");
+ for (uint32_t i = 1; i <= MODE11_PART2_LAST_ISE_RANGE; i++)
+ printf("%u %u\n", i, m_weight_range_hist_11_2part[i]);
+ printf("\n");
+
+ printf("ISE texel weight range histogram mode 7:\n");
+ for (uint32_t i = 1; i <= MODE7_PART1_LAST_ISE_RANGE; i++)
+ printf("%u %u\n", i, m_weight_range_hist_7[i]);
+ printf("\n");
+
+ printf("ISE texel weight range histogram mode 7, 2 partition:\n");
+ for (uint32_t i = 1; i <= MODE7_PART2_LAST_ISE_RANGE; i++)
+ printf("%u %u\n", i, m_weight_range_hist_7_2part[i]);
+ printf("\n");
+
+ printf("Mode 11 submode histogram:\n");
+ for (uint32_t i = 0; i <= MODE11_TOTAL_SUBMODES; i++) // +1 because of the extra direct encoding
+ printf("%u %u\n", i, m_mode11_submode_hist[i]);
+ printf("\n");
+
+ printf("Mode 7 submode histogram:\n");
+ for (uint32_t i = 0; i < MODE7_TOTAL_SUBMODES; i++)
+ printf("%u %u\n", i, m_mode7_submode_hist[i]);
+ printf("\n");
+
+ printf("Partition pattern table usage histogram:\n");
+ for (uint32_t i = 0; i < basist::TOTAL_ASTC_BC7_COMMON_PARTITIONS2; i++)
+ printf("%u:%u ", i, m_part_hist[i]);
+ printf("\n\n");
+}
+
+} // namespace basisu
+
diff --git a/thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.h b/thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.h
new file mode 100644
index 0000000000..ee122ff7ce
--- /dev/null
+++ b/thirdparty/basis_universal/encoder/basisu_astc_hdr_enc.h
@@ -0,0 +1,224 @@
+// basisu_astc_hdr_enc.h
+#pragma once
+#include "basisu_enc.h"
+#include "basisu_gpu_texture.h"
+#include "../transcoder/basisu_astc_helpers.h"
+#include "../transcoder/basisu_astc_hdr_core.h"
+
+namespace basisu
+{
+ // This MUST be called before encoding any blocks.
+ void astc_hdr_enc_init();
+
+ const uint32_t MODE11_FIRST_ISE_RANGE = astc_helpers::BISE_3_LEVELS, MODE11_LAST_ISE_RANGE = astc_helpers::BISE_16_LEVELS;
+ const uint32_t MODE7_PART1_FIRST_ISE_RANGE = astc_helpers::BISE_3_LEVELS, MODE7_PART1_LAST_ISE_RANGE = astc_helpers::BISE_16_LEVELS;
+ const uint32_t MODE7_PART2_FIRST_ISE_RANGE = astc_helpers::BISE_3_LEVELS, MODE7_PART2_LAST_ISE_RANGE = astc_helpers::BISE_8_LEVELS;
+ const uint32_t MODE11_PART2_FIRST_ISE_RANGE = astc_helpers::BISE_3_LEVELS, MODE11_PART2_LAST_ISE_RANGE = astc_helpers::BISE_4_LEVELS;
+ const uint32_t MODE11_TOTAL_SUBMODES = 8; // plus an extra hidden submode, directly encoded, for direct, so really 9 (see tables 99/100 of the ASTC spec)
+ const uint32_t MODE7_TOTAL_SUBMODES = 6;
+
+ struct astc_hdr_codec_options
+ {
+ float m_bc6h_err_weight;
+
+ bool m_use_solid;
+
+ bool m_use_mode11;
+ bool m_mode11_uber_mode;
+ uint32_t m_first_mode11_weight_ise_range;
+ uint32_t m_last_mode11_weight_ise_range;
+ bool m_mode11_direct_only;
+ int32_t m_first_mode11_submode;
+ int32_t m_last_mode11_submode;
+
+ bool m_use_mode7_part1;
+ uint32_t m_first_mode7_part1_weight_ise_range;
+ uint32_t m_last_mode7_part1_weight_ise_range;
+
+ bool m_use_mode7_part2;
+ uint32_t m_mode7_part2_part_masks;
+ uint32_t m_first_mode7_part2_weight_ise_range;
+ uint32_t m_last_mode7_part2_weight_ise_range;
+
+ bool m_use_mode11_part2;
+ uint32_t m_mode11_part2_part_masks;
+ uint32_t m_first_mode11_part2_weight_ise_range;
+ uint32_t m_last_mode11_part2_weight_ise_range;
+
+ float m_r_err_scale, m_g_err_scale;
+
+ bool m_refine_weights;
+
+ uint32_t m_level;
+
+ bool m_use_estimated_partitions;
+ uint32_t m_max_estimated_partitions;
+
+ // If true, the ASTC HDR compressor is allowed to more aggressively vary weight indices for slightly higher compression in non-fastest mode. This will hurt BC6H quality, however.
+ bool m_allow_uber_mode;
+
+ astc_hdr_codec_options();
+
+ void init();
+
+ // TODO: set_quality_level() is preferred to configure the codec for transcoding purposes.
+ static const int cMinLevel = 0;
+ static const int cMaxLevel = 4;
+ static const int cDefaultLevel = 1;
+ void set_quality_level(int level);
+
+ private:
+ void set_quality_best();
+ void set_quality_normal();
+ void set_quality_fastest();
+ };
+
+ struct astc_hdr_pack_results
+ {
+ double m_best_block_error;
+ double m_bc6h_block_error; // note this is not used/set by the encoder, here for convienance
+
+ // Encoder results (logical ASTC block)
+ astc_helpers::log_astc_block m_best_blk;
+
+ // For statistical use
+ uint32_t m_best_submodes[2];
+ uint32_t m_best_pat_index;
+ bool m_constrained_weights;
+
+ bool m_improved_via_refinement_flag;
+
+ // Only valid if the block is solid
+ basist::astc_blk m_solid_blk;
+
+ // The BC6H transcoded block
+ basist::bc6h_block m_bc6h_block;
+
+ // Solid color/void extent flag
+ bool m_is_solid;
+
+ void clear()
+ {
+ m_best_block_error = 1e+30f;
+ m_bc6h_block_error = 1e+30f;
+
+ m_best_blk.clear();
+ m_best_blk.m_grid_width = 4;
+ m_best_blk.m_grid_height = 4;
+ m_best_blk.m_endpoint_ise_range = 20; // 0-255
+
+ clear_obj(m_best_submodes);
+
+ m_best_pat_index = 0;
+ m_constrained_weights = false;
+
+ clear_obj(m_bc6h_block);
+
+ m_is_solid = false;
+ m_improved_via_refinement_flag = false;
+ }
+ };
+
+ void interpolate_qlog12_colors(
+ const int e[2][3],
+ basist::half_float* pDecoded_half,
+ vec3F* pDecoded_float,
+ uint32_t n, uint32_t ise_weight_range);
+
+ bool get_astc_hdr_mode_11_block_colors(
+ const uint8_t* pEndpoints,
+ basist::half_float* pDecoded_half,
+ vec3F* pDecoded_float,
+ uint32_t n, uint32_t ise_weight_range, uint32_t ise_endpoint_range);
+
+ bool get_astc_hdr_mode_7_block_colors(
+ const uint8_t* pEndpoints,
+ basist::half_float* pDecoded_half,
+ vec3F* pDecoded_float,
+ uint32_t n, uint32_t ise_weight_range, uint32_t ise_endpoint_range);
+
+ double eval_selectors(
+ uint32_t num_pixels,
+ uint8_t* pWeights,
+ const basist::half_float* pBlock_pixels_half,
+ uint32_t num_weight_levels,
+ const basist::half_float* pDecoded_half,
+ const astc_hdr_codec_options& coptions,
+ uint32_t usable_selector_bitmask = UINT32_MAX);
+
+ double compute_block_error(const basist::half_float* pOrig_block, const basist::half_float* pPacked_block, const astc_hdr_codec_options& coptions);
+
+ // Encodes a 4x4 ASTC HDR block given a 4x4 array of source block pixels/texels.
+ // Supports solid color blocks, mode 11 (all submodes), mode 7/1 partition (all submodes),
+ // and mode 7/2 partitions (all submodes) - 30 patterns, only the ones also in common with the BC6H format.
+ // The packed ASTC weight grid dimensions are currently always 4x4 texels, but may be also 3x3 in the future.
+ // This function is thread safe, i.e. it may be called from multiple encoding threads simultanously with different blocks.
+ //
+ // Parameters:
+ // pRGBPixels - An array of 48 (16 RGB) floats: the 4x4 block to pack
+ // pPacked_block - A pointer to the packed ASTC HDR block
+ // coptions - Codec options
+ // pInternal_results - An optional pointer to details about how the block was packed, for statistics/debugging purposes. May be nullptr.
+ //
+ // Requirements:
+ // astc_hdr_enc_init() MUST have been called first to initialized the codec.
+ // Input pixels are checked and cannot be NaN's, Inf's, signed, or too large (greater than MAX_HALF_FLOAT, or 65504).
+ // Normal values and denormals are okay.
+ bool astc_hdr_enc_block(
+ const float* pRGBPixels,
+ const astc_hdr_codec_options& coptions,
+ basisu::vector<astc_hdr_pack_results> &all_results);
+
+ bool astc_hdr_pack_results_to_block(basist::astc_blk& dst_blk, const astc_hdr_pack_results& results);
+
+ bool astc_hdr_refine_weights(const basist::half_float* pSource_block, astc_hdr_pack_results& cur_results, const astc_hdr_codec_options& coptions, float bc6h_weight, bool* pImproved_flag);
+
+ struct astc_hdr_block_stats
+ {
+ std::mutex m_mutex;
+
+ uint32_t m_total_blocks;
+ uint32_t m_total_2part, m_total_solid;
+ uint32_t m_total_mode7_1part, m_total_mode7_2part;
+ uint32_t m_total_mode11_1part, m_total_mode11_2part;
+ uint32_t m_total_mode11_1part_constrained_weights;
+
+ uint32_t m_weight_range_hist_7[11];
+ uint32_t m_weight_range_hist_7_2part[11];
+ uint32_t m_mode7_submode_hist[6];
+
+ uint32_t m_weight_range_hist_11[11];
+ uint32_t m_weight_range_hist_11_2part[11];
+ uint32_t m_mode11_submode_hist[9];
+
+ uint32_t m_part_hist[32];
+
+ uint32_t m_total_refined;
+
+ astc_hdr_block_stats() { clear(); }
+
+ void clear()
+ {
+ std::lock_guard<std::mutex> lck(m_mutex);
+
+ m_total_blocks = 0;
+ m_total_mode7_1part = 0, m_total_mode7_2part = 0, m_total_mode11_1part = 0, m_total_2part = 0, m_total_solid = 0, m_total_mode11_2part = 0;
+ m_total_mode11_1part_constrained_weights = 0;
+ m_total_refined = 0;
+
+ clear_obj(m_weight_range_hist_11);
+ clear_obj(m_weight_range_hist_11_2part);
+ clear_obj(m_weight_range_hist_7);
+ clear_obj(m_weight_range_hist_7_2part);
+ clear_obj(m_mode7_submode_hist);
+ clear_obj(m_mode11_submode_hist);
+ clear_obj(m_part_hist);
+ }
+
+ void update(const astc_hdr_pack_results& log_blk);
+
+ void print();
+ };
+
+} // namespace basisu
+
diff --git a/thirdparty/basis_universal/encoder/basisu_backend.cpp b/thirdparty/basis_universal/encoder/basisu_backend.cpp
index abb61750a6..3fa3d8892f 100644
--- a/thirdparty/basis_universal/encoder/basisu_backend.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_backend.cpp
@@ -1,5 +1,5 @@
// basisu_backend.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_backend.h b/thirdparty/basis_universal/encoder/basisu_backend.h
index 07778aeb9b..58a9a8aa0e 100644
--- a/thirdparty/basis_universal/encoder/basisu_backend.h
+++ b/thirdparty/basis_universal/encoder/basisu_backend.h
@@ -1,5 +1,5 @@
// basisu_backend.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_basis_file.cpp b/thirdparty/basis_universal/encoder/basisu_basis_file.cpp
index f4c77bef23..77f467f670 100644
--- a/thirdparty/basis_universal/encoder/basisu_basis_file.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_basis_file.cpp
@@ -1,5 +1,5 @@
// basisu_basis_file.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_basis_file.h b/thirdparty/basis_universal/encoder/basisu_basis_file.h
index 98498a0121..57448bccb1 100644
--- a/thirdparty/basis_universal/encoder/basisu_basis_file.h
+++ b/thirdparty/basis_universal/encoder/basisu_basis_file.h
@@ -1,5 +1,5 @@
// basisu_basis_file.h
-// Copyright (C) 2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_bc7enc.cpp b/thirdparty/basis_universal/encoder/basisu_bc7enc.cpp
index 22fdfa603f..914e7fbbb9 100644
--- a/thirdparty/basis_universal/encoder/basisu_bc7enc.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_bc7enc.cpp
@@ -1,5 +1,5 @@
// File: basisu_bc7enc.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -394,6 +394,7 @@ void bc7enc_compress_block_init()
static void compute_least_squares_endpoints_rgba(uint32_t N, const uint8_t *pSelectors, const bc7enc_vec4F* pSelector_weights, bc7enc_vec4F* pXl, bc7enc_vec4F* pXh, const color_quad_u8 *pColors)
{
// Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf
+ // https://web.archive.org/web/20150319232457/http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf
// I did this in matrix form first, expanded out all the ops, then optimized it a bit.
double z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f;
double q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f;
@@ -1301,6 +1302,7 @@ void check_best_overall_error(const color_cell_compressor_params *pParams, color
for (uint32_t c = 0; c < 4; c++)
colors[i].m_c[c] = (uint8_t)astc_interpolate_linear(colors[0].m_c[c], colors[n - 1].m_c[c], pParams->m_pSelector_weights[i]);
+#ifdef _DEBUG
uint64_t total_err = 0;
for (uint32_t p = 0; p < pParams->m_num_pixels; p++)
{
@@ -1313,6 +1315,7 @@ void check_best_overall_error(const color_cell_compressor_params *pParams, color
total_err += compute_color_distance_rgb(&orig, &packed, pParams->m_perceptual, pParams->m_weights);
}
assert(total_err == pResults->m_best_overall_err);
+#endif
// HACK HACK
//if (total_err != pResults->m_best_overall_err)
diff --git a/thirdparty/basis_universal/encoder/basisu_bc7enc.h b/thirdparty/basis_universal/encoder/basisu_bc7enc.h
index 8d8b7888ca..925d6b2e8d 100644
--- a/thirdparty/basis_universal/encoder/basisu_bc7enc.h
+++ b/thirdparty/basis_universal/encoder/basisu_bc7enc.h
@@ -1,5 +1,5 @@
// File: basisu_bc7enc.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_comp.cpp b/thirdparty/basis_universal/encoder/basisu_comp.cpp
index 4e69e9e2ee..81813257cd 100644
--- a/thirdparty/basis_universal/encoder/basisu_comp.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_comp.cpp
@@ -1,5 +1,5 @@
// basisu_comp.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
#include "basisu_enc.h"
#include <unordered_set>
#include <atomic>
+#include <map>
+
+//#define UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
// basisu_transcoder.cpp is where basisu_miniz lives now, we just need the declarations here.
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
@@ -23,6 +26,8 @@
#include "basisu_opencl.h"
+#include "../transcoder/basisu_astc_hdr_core.h"
+
#if !BASISD_SUPPORT_KTX2
#error BASISD_SUPPORT_KTX2 must be enabled (set to 1).
#endif
@@ -34,7 +39,7 @@
// Set to 1 to disable the mipPadding alignment workaround (which only seems to be needed when no key-values are written at all)
#define BASISU_DISABLE_KTX2_ALIGNMENT_WORKAROUND (0)
-// Set to 1 to disable writing all KTX2 key values, triggering the validator bug.
+// Set to 1 to disable writing all KTX2 key values, triggering an early validator bug.
#define BASISU_DISABLE_KTX2_KEY_VALUES (0)
using namespace buminiz;
@@ -46,27 +51,143 @@ using namespace buminiz;
namespace basisu
{
- basis_compressor::basis_compressor() :
- m_pOpenCL_context(nullptr),
+ basis_compressor::basis_compressor() :
+ m_pOpenCL_context(nullptr),
m_basis_file_size(0),
m_basis_bits_per_texel(0.0f),
m_total_blocks(0),
m_any_source_image_has_alpha(false),
- m_opencl_failed(false)
+ m_opencl_failed(false)
{
debug_printf("basis_compressor::basis_compressor\n");
assert(g_library_initialized);
}
- basis_compressor::~basis_compressor()
- {
- if (m_pOpenCL_context)
- {
- opencl_destroy_context(m_pOpenCL_context);
- m_pOpenCL_context = nullptr;
- }
- }
+ basis_compressor::~basis_compressor()
+ {
+ if (m_pOpenCL_context)
+ {
+ opencl_destroy_context(m_pOpenCL_context);
+ m_pOpenCL_context = nullptr;
+ }
+ }
+
+ void basis_compressor::check_for_hdr_inputs()
+ {
+ if ((!m_params.m_source_filenames.size()) && (!m_params.m_source_images.size()))
+ {
+ if (m_params.m_source_images_hdr.size())
+ {
+ // Assume they want UASTC HDR if they've specified any HDR source images.
+ m_params.m_hdr = true;
+ }
+ }
+
+ if (!m_params.m_hdr)
+ {
+ // See if any files are .EXR or .HDR, if so switch the compressor to UASTC HDR mode.
+ for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++)
+ {
+ std::string filename;
+ string_get_filename(m_params.m_source_filenames[i].c_str(), filename);
+
+ std::string ext(string_get_extension(filename));
+ string_tolower(ext);
+
+ if ((ext == "exr") || (ext == "hdr"))
+ {
+ m_params.m_hdr = true;
+ break;
+ }
+ }
+ }
+
+ if (m_params.m_hdr)
+ {
+ if (m_params.m_source_alpha_filenames.size())
+ {
+ debug_printf("Warning: Alpha channel image filenames are not supported in UASTC HDR mode.\n");
+ m_params.m_source_alpha_filenames.clear();
+ }
+ }
+
+ if (m_params.m_hdr)
+ m_params.m_uastc = true;
+ }
+
+ bool basis_compressor::sanity_check_input_params()
+ {
+ // Check for no source filenames specified.
+ if ((m_params.m_read_source_images) && (!m_params.m_source_filenames.size()))
+ {
+ assert(0);
+ return false;
+ }
+
+ // See if they've specified any source filenames, but didn't tell us to read them.
+ if ((!m_params.m_read_source_images) && (m_params.m_source_filenames.size()))
+ {
+ assert(0);
+ return false;
+ }
+
+ // Sanity check the input image parameters.
+ if (m_params.m_read_source_images)
+ {
+ // Caller can't specify their own images if they want us to read source images from files.
+ if (m_params.m_source_images.size() || m_params.m_source_images_hdr.size())
+ {
+ assert(0);
+ return false;
+ }
+
+ if (m_params.m_source_mipmap_images.size() || m_params.m_source_mipmap_images_hdr.size())
+ {
+ assert(0);
+ return false;
+ }
+ }
+ else
+ {
+ // They didn't tell us to read any source files, so check for no LDR/HDR source images.
+ if (!m_params.m_source_images.size() && !m_params.m_source_images_hdr.size())
+ {
+ assert(0);
+ return false;
+ }
+
+ // Now we know we've been supplied LDR and/or HDR source images, check for LDR vs. HDR conflicts.
+
+ if (m_params.m_source_images.size())
+ {
+ // They've supplied LDR images, so make sure they also haven't specified HDR input images.
+ if (m_params.m_source_images_hdr.size() || m_params.m_source_mipmap_images_hdr.size())
+ {
+ assert(0);
+ return false;
+ }
+ }
+ else
+ {
+ // No LDR images, so make sure they haven't specified any LDR mipmaps.
+ if (m_params.m_source_mipmap_images.size())
+ {
+ assert(0);
+ return false;
+ }
+
+ // No LDR images, so ensure they've supplied some HDR images to process.
+ if (!m_params.m_source_images_hdr.size())
+ {
+ assert(0);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
bool basis_compressor::init(const basis_compressor_params &params)
{
@@ -85,7 +206,12 @@ namespace basisu
}
m_params = params;
-
+
+ if ((m_params.m_compute_stats) && (!m_params.m_validate_output_data))
+ m_params.m_validate_output_data = true;
+
+ check_for_hdr_inputs();
+
if (m_params.m_debug)
{
debug_printf("basis_compressor::init:\n");
@@ -95,8 +221,10 @@ namespace basisu
#define PRINT_UINT_VALUE(v) debug_printf("%s: %u %u\n", BASISU_STRINGIZE2(v), static_cast<uint32_t>(m_params.v), m_params.v.was_changed());
#define PRINT_FLOAT_VALUE(v) debug_printf("%s: %f %u\n", BASISU_STRINGIZE2(v), static_cast<float>(m_params.v), m_params.v.was_changed());
- debug_printf("Source images: %u, source filenames: %u, source alpha filenames: %i, Source mipmap images: %u\n",
- m_params.m_source_images.size(), m_params.m_source_filenames.size(), m_params.m_source_alpha_filenames.size(), m_params.m_source_mipmap_images.size());
+ debug_printf("Source LDR images: %u, HDR images: %u, filenames: %u, alpha filenames: %i, LDR mipmap images: %u, HDR mipmap images: %u\n",
+ m_params.m_source_images.size(), m_params.m_source_images_hdr.size(),
+ m_params.m_source_filenames.size(), m_params.m_source_alpha_filenames.size(),
+ m_params.m_source_mipmap_images.size(), m_params.m_source_mipmap_images_hdr.size());
if (m_params.m_source_mipmap_images.size())
{
@@ -106,6 +234,15 @@ namespace basisu
debug_printf("\n");
}
+ if (m_params.m_source_mipmap_images_hdr.size())
+ {
+ debug_printf("m_source_mipmap_images_hdr array sizes:\n");
+ for (uint32_t i = 0; i < m_params.m_source_mipmap_images_hdr.size(); i++)
+ debug_printf("%u ", m_params.m_source_mipmap_images_hdr[i].size());
+ debug_printf("\n");
+ }
+
+ PRINT_BOOL_VALUE(m_hdr);
PRINT_BOOL_VALUE(m_uastc);
PRINT_BOOL_VALUE(m_use_opencl);
PRINT_BOOL_VALUE(m_y_flip);
@@ -117,7 +254,7 @@ namespace basisu
PRINT_BOOL_VALUE(m_no_endpoint_rdo);
PRINT_BOOL_VALUE(m_no_selector_rdo);
PRINT_BOOL_VALUE(m_read_source_images);
- PRINT_BOOL_VALUE(m_write_output_basis_files);
+ PRINT_BOOL_VALUE(m_write_output_basis_or_ktx2_files);
PRINT_BOOL_VALUE(m_compute_stats);
PRINT_BOOL_VALUE(m_check_for_alpha);
PRINT_BOOL_VALUE(m_force_alpha);
@@ -146,6 +283,7 @@ namespace basisu
debug_printf("m_max_endpoint_clusters: %u\n", m_params.m_max_endpoint_clusters);
debug_printf("m_max_selector_clusters: %u\n", m_params.m_max_selector_clusters);
debug_printf("m_quality_level: %i\n", m_params.m_quality_level);
+ debug_printf("UASTC HDR quality level: %u\n", m_params.m_uastc_hdr_options.m_level);
debug_printf("m_tex_type: %u\n", m_params.m_tex_type);
debug_printf("m_userdata0: 0x%X, m_userdata1: 0x%X\n", m_params.m_userdata0, m_params.m_userdata1);
@@ -185,6 +323,9 @@ namespace basisu
}
PRINT_BOOL_VALUE(m_validate_output_data);
+ PRINT_BOOL_VALUE(m_hdr_ldr_srgb_to_linear_conversion);
+ debug_printf("Allow UASTC HDR uber mode: %u\n", m_params.m_uastc_hdr_options.m_allow_uber_mode);
+ PRINT_BOOL_VALUE(m_hdr_favor_astc);
#undef PRINT_BOOL_VALUE
#undef PRINT_INT_VALUE
@@ -192,19 +333,9 @@ namespace basisu
#undef PRINT_FLOAT_VALUE
}
- if ((m_params.m_read_source_images) && (!m_params.m_source_filenames.size()))
- {
- assert(0);
+ if (!sanity_check_input_params())
return false;
- }
-
- if ((m_params.m_compute_stats) && (!m_params.m_validate_output_data))
- {
- m_params.m_validate_output_data = true;
-
- debug_printf("Note: m_compute_stats is true, so forcing m_validate_output_data to true as well\n");
- }
-
+
if ((m_params.m_use_opencl) && opencl_is_available() && !m_pOpenCL_context && !m_opencl_failed)
{
m_pOpenCL_context = opencl_create_context();
@@ -219,6 +350,9 @@ namespace basisu
{
debug_printf("basis_compressor::process\n");
+ if (!read_dds_source_images())
+ return cECFailedReadingSourceImages;
+
if (!read_source_images())
return cECFailedReadingSourceImages;
@@ -228,20 +362,38 @@ namespace basisu
if (m_params.m_create_ktx2_file)
{
if (!validate_ktx2_constraints())
+ {
+ error_printf("Inputs do not satisfy .KTX2 texture constraints: all source images must be the same resolution and have the same number of mipmap levels.\n");
return cECFailedValidating;
+ }
}
if (!extract_source_blocks())
return cECFailedFrontEnd;
- if (m_params.m_uastc)
+ if (m_params.m_hdr)
+ {
+ // UASTC HDR
+ printf("Mode: UASTC HDR Level %u\n", m_params.m_uastc_hdr_options.m_level);
+
+ error_code ec = encode_slices_to_uastc_hdr();
+ if (ec != cECSuccess)
+ return ec;
+ }
+ else if (m_params.m_uastc)
{
+ // UASTC
+ printf("Mode: UASTC LDR Level %u\n", m_params.m_pack_uastc_flags & cPackUASTCLevelMask);
+
error_code ec = encode_slices_to_uastc();
if (ec != cECSuccess)
return ec;
}
else
{
+ // ETC1S
+ printf("Mode: ETC1S Quality %i, Level %i\n", m_params.m_quality_level, (int)m_params.m_compression_level);
+
if (!process_frontend())
return cECFailedFrontEnd;
@@ -254,7 +406,7 @@ namespace basisu
if (!create_basis_file_and_transcode())
return cECFailedCreateBasisFile;
-
+
if (m_params.m_create_ktx2_file)
{
if (!create_ktx2_file())
@@ -267,6 +419,309 @@ namespace basisu
return cECSuccess;
}
+ basis_compressor::error_code basis_compressor::encode_slices_to_uastc_hdr()
+ {
+ debug_printf("basis_compressor::encode_slices_to_uastc_hdr\n");
+
+ interval_timer tm;
+ tm.start();
+
+ m_uastc_slice_textures.resize(m_slice_descs.size());
+ for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
+ m_uastc_slice_textures[slice_index].init(texture_format::cUASTC_HDR_4x4, m_slice_descs[slice_index].m_orig_width, m_slice_descs[slice_index].m_orig_height);
+
+ m_uastc_backend_output.m_tex_format = basist::basis_tex_format::cUASTC_HDR_4x4;
+ m_uastc_backend_output.m_etc1s = false;
+ m_uastc_backend_output.m_slice_desc = m_slice_descs;
+ m_uastc_backend_output.m_slice_image_data.resize(m_slice_descs.size());
+ m_uastc_backend_output.m_slice_image_crcs.resize(m_slice_descs.size());
+
+ if (!m_params.m_perceptual)
+ {
+ m_params.m_uastc_hdr_options.m_r_err_scale = 1.0f;
+ m_params.m_uastc_hdr_options.m_g_err_scale = 1.0f;
+ }
+
+ const float DEFAULT_BC6H_ERROR_WEIGHT = .85f;
+ const float LOWEST_BC6H_ERROR_WEIGHT = .1f;
+ m_params.m_uastc_hdr_options.m_bc6h_err_weight = m_params.m_hdr_favor_astc ? LOWEST_BC6H_ERROR_WEIGHT : DEFAULT_BC6H_ERROR_WEIGHT;
+
+ std::atomic<bool> any_failures;
+ any_failures = false;
+
+ astc_hdr_block_stats enc_stats;
+
+ struct uastc_blk_desc
+ {
+ uint32_t m_solid_flag;
+ uint32_t m_num_partitions;
+ uint32_t m_cem_index;
+ uint32_t m_weight_ise_range;
+ uint32_t m_endpoint_ise_range;
+
+ bool operator< (const uastc_blk_desc& desc) const
+ {
+ if (this == &desc)
+ return false;
+
+#define COMP(XX) if (XX < desc.XX) return true; else if (XX != desc.XX) return false;
+ COMP(m_solid_flag)
+ COMP(m_num_partitions)
+ COMP(m_cem_index)
+ COMP(m_weight_ise_range)
+ COMP(m_endpoint_ise_range)
+#undef COMP
+
+ return false;
+ }
+
+ bool operator== (const uastc_blk_desc& desc) const
+ {
+ if (this == &desc)
+ return true;
+ if ((*this < desc) || (desc < *this))
+ return false;
+ return true;
+ }
+
+ bool operator!= (const uastc_blk_desc& desc) const
+ {
+ return !(*this == desc);
+ }
+ };
+
+ struct uastc_blk_desc_stats
+ {
+ uastc_blk_desc_stats() : m_count(0) { }
+ uint32_t m_count;
+#ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
+ basisu::vector<basist::astc_blk> m_blks;
+#endif
+ };
+
+ std::map<uastc_blk_desc, uastc_blk_desc_stats> unique_block_descs;
+ std::mutex unique_block_desc_mutex;
+
+ for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
+ {
+ gpu_image& tex = m_uastc_slice_textures[slice_index];
+ basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
+ (void)slice_desc;
+
+ const uint32_t num_blocks_x = tex.get_blocks_x();
+ const uint32_t num_blocks_y = tex.get_blocks_y();
+ const uint32_t total_blocks = tex.get_total_blocks();
+ const imagef& source_image = m_slice_images_hdr[slice_index];
+
+ std::atomic<uint32_t> total_blocks_processed;
+ total_blocks_processed = 0;
+
+ const uint32_t N = 256;
+ for (uint32_t block_index_iter = 0; block_index_iter < total_blocks; block_index_iter += N)
+ {
+ const uint32_t first_index = block_index_iter;
+ const uint32_t last_index = minimum<uint32_t>(total_blocks, block_index_iter + N);
+
+ // FIXME: This sucks, but we're having a stack size related problem with std::function with emscripten.
+#ifndef __EMSCRIPTEN__
+ m_params.m_pJob_pool->add_job([this, first_index, last_index, num_blocks_x, num_blocks_y, total_blocks, &source_image,
+ &tex, &total_blocks_processed, &any_failures, &enc_stats, &unique_block_descs, &unique_block_desc_mutex]
+ {
+#endif
+ BASISU_NOTE_UNUSED(num_blocks_y);
+
+ basisu::vector<astc_hdr_pack_results> all_results;
+ all_results.reserve(256);
+
+ for (uint32_t block_index = first_index; block_index < last_index; block_index++)
+ {
+ const uint32_t block_x = block_index % num_blocks_x;
+ const uint32_t block_y = block_index / num_blocks_x;
+
+ vec4F block_pixels[16];
+
+ source_image.extract_block_clamped(&block_pixels[0], block_x * 4, block_y * 4, 4, 4);
+
+ basist::astc_blk& dest_block = *(basist::astc_blk*)tex.get_block_ptr(block_x, block_y);
+
+ float rgb_pixels[16 * 3];
+ basist::half_float rgb_pixels_half[16 * 3];
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ rgb_pixels[i * 3 + 0] = block_pixels[i][0];
+ rgb_pixels_half[i * 3 + 0] = float_to_half_non_neg_no_nan_inf(block_pixels[i][0]);
+
+ rgb_pixels[i * 3 + 1] = block_pixels[i][1];
+ rgb_pixels_half[i * 3 + 1] = float_to_half_non_neg_no_nan_inf(block_pixels[i][1]);
+
+ rgb_pixels[i * 3 + 2] = block_pixels[i][2];
+ rgb_pixels_half[i * 3 + 2] = float_to_half_non_neg_no_nan_inf(block_pixels[i][2]);
+ }
+
+ bool status = astc_hdr_enc_block(&rgb_pixels[0], m_params.m_uastc_hdr_options, all_results);
+ if (!status)
+ {
+ any_failures = true;
+ continue;
+ }
+
+ double best_err = 1e+30f;
+ int best_result_index = -1;
+
+ const double bc6h_err_weight = m_params.m_uastc_hdr_options.m_bc6h_err_weight;
+ const double astc_err_weight = (1.0f - bc6h_err_weight);
+
+ for (uint32_t i = 0; i < all_results.size(); i++)
+ {
+ basist::half_float unpacked_bc6h_block[4 * 4 * 3];
+ unpack_bc6h(&all_results[i].m_bc6h_block, unpacked_bc6h_block, false);
+
+ all_results[i].m_bc6h_block_error = compute_block_error(rgb_pixels_half, unpacked_bc6h_block, m_params.m_uastc_hdr_options);
+
+ double overall_err = (all_results[i].m_bc6h_block_error * bc6h_err_weight) + (all_results[i].m_best_block_error * astc_err_weight);
+
+ if ((!i) || (overall_err < best_err))
+ {
+ best_err = overall_err;
+ best_result_index = i;
+ }
+ }
+
+ const astc_hdr_pack_results& best_results = all_results[best_result_index];
+
+ astc_hdr_pack_results_to_block(dest_block, best_results);
+
+ // Verify that this block is valid UASTC HDR and we can successfully transcode it to BC6H.
+ // (Well, except in fastest mode.)
+ if (m_params.m_uastc_hdr_options.m_level > 0)
+ {
+ basist::bc6h_block transcoded_bc6h_blk;
+ bool transcode_results = astc_hdr_transcode_to_bc6h(dest_block, transcoded_bc6h_blk);
+ assert(transcode_results);
+ if ((!transcode_results) && (!any_failures))
+ {
+ error_printf("basis_compressor::encode_slices_to_uastc_hdr: UASTC HDR block transcode check failed!\n");
+
+ any_failures = true;
+ continue;
+ }
+ }
+
+ if (m_params.m_debug)
+ {
+ // enc_stats has its own mutex
+ enc_stats.update(best_results);
+
+ uastc_blk_desc blk_desc;
+ clear_obj(blk_desc);
+
+ blk_desc.m_solid_flag = best_results.m_is_solid;
+ if (!blk_desc.m_solid_flag)
+ {
+ blk_desc.m_num_partitions = best_results.m_best_blk.m_num_partitions;
+ blk_desc.m_cem_index = best_results.m_best_blk.m_color_endpoint_modes[0];
+ blk_desc.m_weight_ise_range = best_results.m_best_blk.m_weight_ise_range;
+ blk_desc.m_endpoint_ise_range = best_results.m_best_blk.m_endpoint_ise_range;
+ }
+
+ {
+ std::lock_guard<std::mutex> lck(unique_block_desc_mutex);
+
+ auto res = unique_block_descs.insert(std::make_pair(blk_desc, uastc_blk_desc_stats()));
+
+ (res.first)->second.m_count++;
+#ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
+ (res.first)->second.m_blks.push_back(dest_block);
+#endif
+ }
+ }
+
+ total_blocks_processed++;
+
+ uint32_t val = total_blocks_processed;
+ if (((val & 1023) == 1023) && m_params.m_status_output)
+ {
+ debug_printf("basis_compressor::encode_slices_to_uastc_hdr: %3.1f%% done\n", static_cast<float>(val) * 100.0f / total_blocks);
+ }
+ }
+
+#ifndef __EMSCRIPTEN__
+ });
+#endif
+
+ } // block_index_iter
+
+#ifndef __EMSCRIPTEN__
+ m_params.m_pJob_pool->wait_for_all();
+#endif
+
+ if (any_failures)
+ return cECFailedEncodeUASTC;
+
+ m_uastc_backend_output.m_slice_image_data[slice_index].resize(tex.get_size_in_bytes());
+ memcpy(&m_uastc_backend_output.m_slice_image_data[slice_index][0], tex.get_ptr(), tex.get_size_in_bytes());
+
+ m_uastc_backend_output.m_slice_image_crcs[slice_index] = basist::crc16(tex.get_ptr(), tex.get_size_in_bytes(), 0);
+
+ } // slice_index
+
+ debug_printf("basis_compressor::encode_slices_to_uastc_hdr: Total time: %3.3f secs\n", tm.get_elapsed_secs());
+
+ if (m_params.m_debug)
+ {
+ debug_printf("\n----- Total unique UASTC block descs: %u\n", (uint32_t)unique_block_descs.size());
+
+ uint32_t c = 0;
+ for (auto it = unique_block_descs.begin(); it != unique_block_descs.end(); ++it)
+ {
+ debug_printf("%u. Total uses: %u %3.2f%%, solid color: %u\n", c, it->second.m_count,
+ ((float)it->second.m_count * 100.0f) / enc_stats.m_total_blocks, it->first.m_solid_flag);
+
+ if (!it->first.m_solid_flag)
+ {
+ debug_printf(" Num partitions: %u\n", it->first.m_num_partitions);
+ debug_printf(" CEM index: %u\n", it->first.m_cem_index);
+ debug_printf(" Weight ISE range: %u (%u levels)\n", it->first.m_weight_ise_range, astc_helpers::get_ise_levels(it->first.m_weight_ise_range));
+ debug_printf(" Endpoint ISE range: %u (%u levels)\n", it->first.m_endpoint_ise_range, astc_helpers::get_ise_levels(it->first.m_endpoint_ise_range));
+ }
+
+#ifdef UASTC_HDR_DEBUG_SAVE_CATEGORIZED_BLOCKS
+ debug_printf(" -- UASTC HDR block bytes:\n");
+ for (uint32_t j = 0; j < minimum<uint32_t>(4, it->second.m_blks.size()); j++)
+ {
+ basist::astc_blk& blk = it->second.m_blks[j];
+
+ debug_printf(" - UASTC HDR: { ");
+ for (uint32_t k = 0; k < 16; k++)
+ debug_printf("%u%s", ((const uint8_t*)&blk)[k], (k != 15) ? ", " : "");
+ debug_printf(" }\n");
+
+ basist::bc6h_block bc6h_blk;
+ bool res = astc_hdr_transcode_to_bc6h(blk, bc6h_blk);
+ assert(res);
+ if (!res)
+ {
+ error_printf("astc_hdr_transcode_to_bc6h() failed!\n");
+ return cECFailedEncodeUASTC;
+ }
+
+ debug_printf(" - BC6H: { ");
+ for (uint32_t k = 0; k < 16; k++)
+ debug_printf("%u%s", ((const uint8_t*)&bc6h_blk)[k], (k != 15) ? ", " : "");
+ debug_printf(" }\n");
+ }
+#endif
+
+ c++;
+ }
+ printf("\n");
+
+ enc_stats.print();
+ }
+
+ return cECSuccess;
+ }
+
basis_compressor::error_code basis_compressor::encode_slices_to_uastc()
{
debug_printf("basis_compressor::encode_slices_to_uastc\n");
@@ -328,7 +783,7 @@ namespace basisu
total_blocks_processed++;
uint32_t val = total_blocks_processed;
- if ((val & 16383) == 16383)
+ if (((val & 16383) == 16383) && m_params.m_status_output)
{
debug_printf("basis_compressor::encode_slices_to_uastc: %3.1f%% done\n", static_cast<float>(val) * 100.0f / total_blocks);
}
@@ -374,6 +829,57 @@ namespace basisu
return cECSuccess;
}
+ bool basis_compressor::generate_mipmaps(const imagef& img, basisu::vector<imagef>& mips, bool has_alpha)
+ {
+ debug_printf("basis_compressor::generate_mipmaps\n");
+
+ interval_timer tm;
+ tm.start();
+
+ uint32_t total_levels = 1;
+ uint32_t w = img.get_width(), h = img.get_height();
+ while (maximum<uint32_t>(w, h) > (uint32_t)m_params.m_mip_smallest_dimension)
+ {
+ w = maximum(w >> 1U, 1U);
+ h = maximum(h >> 1U, 1U);
+ total_levels++;
+ }
+
+ for (uint32_t level = 1; level < total_levels; level++)
+ {
+ const uint32_t level_width = maximum<uint32_t>(1, img.get_width() >> level);
+ const uint32_t level_height = maximum<uint32_t>(1, img.get_height() >> level);
+
+ imagef& level_img = *enlarge_vector(mips, 1);
+ level_img.resize(level_width, level_height);
+
+ const imagef* pSource_image = &img;
+
+ if (m_params.m_mip_fast)
+ {
+ if (level > 1)
+ pSource_image = &mips[level - 1];
+ }
+
+ bool status = image_resample(*pSource_image, level_img,
+ //m_params.m_mip_filter.c_str(),
+ "box", // TODO: negative lobes in the filter are causing negative colors, try Mitchell
+ m_params.m_mip_scale, m_params.m_mip_wrapping, 0, has_alpha ? 4 : 3);
+ if (!status)
+ {
+ error_printf("basis_compressor::generate_mipmaps: image_resample() failed!\n");
+ return false;
+ }
+
+ clean_hdr_image(level_img);
+ }
+
+ if (m_params.m_debug)
+ debug_printf("Total mipmap generation time: %3.3f secs\n", tm.get_elapsed_secs());
+
+ return true;
+ }
+
bool basis_compressor::generate_mipmaps(const image &img, basisu::vector<image> &mips, bool has_alpha)
{
debug_printf("basis_compressor::generate_mipmaps\n");
@@ -463,17 +969,224 @@ namespace basisu
return true;
}
+ void basis_compressor::clean_hdr_image(imagef& src_img)
+ {
+ const uint32_t width = src_img.get_width();
+ const uint32_t height = src_img.get_height();
+
+ float max_used_val = 0.0f;
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ vec4F& c = src_img(x, y);
+ for (uint32_t i = 0; i < 3; i++)
+ max_used_val = maximum(max_used_val, c[i]);
+ }
+ }
+
+ double hdr_image_scale = 1.0f;
+ if (max_used_val > basist::ASTC_HDR_MAX_VAL)
+ {
+ hdr_image_scale = max_used_val / basist::ASTC_HDR_MAX_VAL;
+
+ const double inv_hdr_image_scale = basist::ASTC_HDR_MAX_VAL / max_used_val;
+
+ for (uint32_t y = 0; y < src_img.get_height(); y++)
+ {
+ for (uint32_t x = 0; x < src_img.get_width(); x++)
+ {
+ vec4F& c = src_img(x, y);
+
+ for (uint32_t i = 0; i < 3; i++)
+ c[i] = (float)minimum<double>(c[i] * inv_hdr_image_scale, basist::ASTC_HDR_MAX_VAL);
+ }
+ }
+
+ printf("Warning: The input HDR image's maximum used float value was %f, which is too high to encode as ASTC HDR. The image's components have been linearly scaled so the maximum used value is %f, by multiplying by %f.\n",
+ max_used_val, basist::ASTC_HDR_MAX_VAL, inv_hdr_image_scale);
+
+ printf("The decoded ASTC HDR texture will have to be scaled up by %f.\n", hdr_image_scale);
+ }
+
+ // TODO: Determine a constant scale factor, apply if > MAX_HALF_FLOAT
+ if (!src_img.clean_astc_hdr_pixels(basist::ASTC_HDR_MAX_VAL))
+ printf("Warning: clean_astc_hdr_pixels() had to modify the input image to encode to ASTC HDR - see previous warning(s).\n");
+
+ float lowest_nonzero_val = 1e+30f;
+ float lowest_val = 1e+30f;
+ float highest_val = -1e+30f;
+
+ for (uint32_t y = 0; y < src_img.get_height(); y++)
+ {
+ for (uint32_t x = 0; x < src_img.get_width(); x++)
+ {
+ const vec4F& c = src_img(x, y);
+
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ lowest_val = basisu::minimum(lowest_val, c[i]);
+
+ if (c[i] != 0.0f)
+ lowest_nonzero_val = basisu::minimum(lowest_nonzero_val, c[i]);
+
+ highest_val = basisu::maximum(highest_val, c[i]);
+ }
+ }
+ }
+
+ debug_printf("Lowest image value: %e, lowest non-zero value: %e, highest value: %e, dynamic range: %e\n", lowest_val, lowest_nonzero_val, highest_val, highest_val / lowest_nonzero_val);
+ }
+
+ bool basis_compressor::read_dds_source_images()
+ {
+ debug_printf("basis_compressor::read_dds_source_images\n");
+
+ // Nothing to do if the caller doesn't want us reading source images.
+ if ((!m_params.m_read_source_images) || (!m_params.m_source_filenames.size()))
+ return true;
+
+ // Just bail of the caller has specified their own source images.
+ if (m_params.m_source_images.size() || m_params.m_source_images_hdr.size())
+ return true;
+
+ if (m_params.m_source_mipmap_images.size() || m_params.m_source_mipmap_images_hdr.size())
+ return true;
+
+ // See if any input filenames are .DDS
+ bool any_dds = false, all_dds = true;
+ for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++)
+ {
+ std::string ext(string_get_extension(m_params.m_source_filenames[i]));
+ if (strcasecmp(ext.c_str(), "dds") == 0)
+ any_dds = true;
+ else
+ all_dds = false;
+ }
+
+ // Bail if no .DDS files specified.
+ if (!any_dds)
+ return true;
+
+ // If any input is .DDS they all must be .DDS, for simplicity.
+ if (!all_dds)
+ {
+ error_printf("If any filename is DDS, all filenames must be DDS.\n");
+ return false;
+ }
+
+ // Can't jam in alpha channel images if any .DDS files specified.
+ if (m_params.m_source_alpha_filenames.size())
+ {
+ error_printf("Source alpha filenames are not supported in DDS mode.\n");
+ return false;
+ }
+
+ bool any_mipmaps = false;
+
+ // Read each .DDS texture file
+ for (uint32_t i = 0; i < m_params.m_source_filenames.size(); i++)
+ {
+ basisu::vector<image> ldr_mips;
+ basisu::vector<imagef> hdr_mips;
+ bool status = read_uncompressed_dds_file(m_params.m_source_filenames[i].c_str(), ldr_mips, hdr_mips);
+ if (!status)
+ return false;
+
+ assert(ldr_mips.size() || hdr_mips.size());
+
+ if (m_params.m_status_output)
+ {
+ printf("Read DDS file \"%s\", %s, %ux%u, %u mipmap levels\n",
+ m_params.m_source_filenames[i].c_str(),
+ ldr_mips.size() ? "LDR" : "HDR",
+ ldr_mips.size() ? ldr_mips[0].get_width() : hdr_mips[0].get_width(),
+ ldr_mips.size() ? ldr_mips[0].get_height() : hdr_mips[0].get_height(),
+ ldr_mips.size() ? ldr_mips.size() : hdr_mips.size());
+ }
+
+ if (ldr_mips.size())
+ {
+ if (m_params.m_source_images_hdr.size())
+ {
+ error_printf("All DDS files must be of the same type (all LDR, or all HDR)\n");
+ return false;
+ }
+
+ m_params.m_source_images.push_back(ldr_mips[0]);
+ m_params.m_source_mipmap_images.resize(m_params.m_source_mipmap_images.size() + 1);
+
+ if (ldr_mips.size() > 1)
+ {
+ ldr_mips.erase(0U);
+
+ m_params.m_source_mipmap_images.back().swap(ldr_mips);
+
+ any_mipmaps = true;
+ }
+ }
+ else
+ {
+ if (m_params.m_source_images.size())
+ {
+ error_printf("All DDS files must be of the same type (all LDR, or all HDR)\n");
+ return false;
+ }
+
+ m_params.m_source_images_hdr.push_back(hdr_mips[0]);
+ m_params.m_source_mipmap_images_hdr.resize(m_params.m_source_mipmap_images_hdr.size() + 1);
+
+ if (hdr_mips.size() > 1)
+ {
+ hdr_mips.erase(0U);
+
+ m_params.m_source_mipmap_images_hdr.back().swap(hdr_mips);
+
+ any_mipmaps = true;
+ }
+
+ m_params.m_hdr = true;
+ m_params.m_uastc = true;
+ }
+ }
+
+ m_params.m_read_source_images = false;
+ m_params.m_source_filenames.clear();
+ m_params.m_source_alpha_filenames.clear();
+
+ if (!any_mipmaps)
+ {
+ m_params.m_source_mipmap_images.clear();
+ m_params.m_source_mipmap_images_hdr.clear();
+ }
+
+ if ((m_params.m_hdr) && (!m_params.m_source_images_hdr.size()))
+ {
+ error_printf("HDR mode enabled, but only LDR .DDS files were loaded. HDR mode requires half or float (HDR) .DDS inputs.\n");
+ return false;
+ }
+
+ return true;
+ }
+
bool basis_compressor::read_source_images()
{
debug_printf("basis_compressor::read_source_images\n");
- const uint32_t total_source_files = m_params.m_read_source_images ? (uint32_t)m_params.m_source_filenames.size() : (uint32_t)m_params.m_source_images.size();
+ const uint32_t total_source_files = m_params.m_read_source_images ? (uint32_t)m_params.m_source_filenames.size() :
+ (m_params.m_hdr ? (uint32_t)m_params.m_source_images_hdr.size() : (uint32_t)m_params.m_source_images.size());
+
if (!total_source_files)
+ {
+ debug_printf("basis_compressor::read_source_images: No source images to process\n");
+
return false;
+ }
m_stats.resize(0);
m_slice_descs.resize(0);
m_slice_images.resize(0);
+ m_slice_images_hdr.resize(0);
m_total_blocks = 0;
uint32_t total_macroblocks = 0;
@@ -481,106 +1194,196 @@ namespace basisu
m_any_source_image_has_alpha = false;
basisu::vector<image> source_images;
+ basisu::vector<imagef> source_images_hdr;
+
basisu::vector<std::string> source_filenames;
+ // TODO: Note HDR images don't support alpha here, currently.
+
// First load all source images, and determine if any have an alpha channel.
for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
{
- const char *pSource_filename = "";
+ const char* pSource_filename = "";
image file_image;
-
+ imagef file_image_hdr;
+
if (m_params.m_read_source_images)
{
pSource_filename = m_params.m_source_filenames[source_file_index].c_str();
// Load the source image
- if (!load_image(pSource_filename, file_image))
+ if (m_params.m_hdr)
{
- error_printf("Failed reading source image: %s\n", pSource_filename);
- return false;
+ if (!load_image_hdr(pSource_filename, file_image_hdr, m_params.m_hdr_ldr_srgb_to_linear_conversion))
+ {
+ error_printf("Failed reading source image: %s\n", pSource_filename);
+ return false;
+ }
+
+ // For now, just slam alpha to 1.0f. UASTC HDR doesn't support alpha yet.
+ for (uint32_t y = 0; y < file_image_hdr.get_height(); y++)
+ for (uint32_t x = 0; x < file_image_hdr.get_width(); x++)
+ file_image_hdr(x, y)[3] = 1.0f;
}
+ else
+ {
+ if (!load_image(pSource_filename, file_image))
+ {
+ error_printf("Failed reading source image: %s\n", pSource_filename);
+ return false;
+ }
+ }
+
+ const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width();
+ const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height();
if (m_params.m_status_output)
{
- printf("Read source image \"%s\", %ux%u\n", pSource_filename, file_image.get_width(), file_image.get_height());
+ printf("Read source image \"%s\", %ux%u\n", pSource_filename, width, height);
}
- // Optionally load another image and put a grayscale version of it into the alpha channel.
- if ((source_file_index < m_params.m_source_alpha_filenames.size()) && (m_params.m_source_alpha_filenames[source_file_index].size()))
+ if (m_params.m_hdr)
+ {
+ clean_hdr_image(file_image_hdr);
+ }
+ else
{
- const char *pSource_alpha_image = m_params.m_source_alpha_filenames[source_file_index].c_str();
+ // Optionally load another image and put a grayscale version of it into the alpha channel.
+ if ((source_file_index < m_params.m_source_alpha_filenames.size()) && (m_params.m_source_alpha_filenames[source_file_index].size()))
+ {
+ const char* pSource_alpha_image = m_params.m_source_alpha_filenames[source_file_index].c_str();
- image alpha_data;
+ image alpha_data;
- if (!load_image(pSource_alpha_image, alpha_data))
- {
- error_printf("Failed reading source image: %s\n", pSource_alpha_image);
- return false;
- }
+ if (!load_image(pSource_alpha_image, alpha_data))
+ {
+ error_printf("Failed reading source image: %s\n", pSource_alpha_image);
+ return false;
+ }
- printf("Read source alpha image \"%s\", %ux%u\n", pSource_alpha_image, alpha_data.get_width(), alpha_data.get_height());
+ printf("Read source alpha image \"%s\", %ux%u\n", pSource_alpha_image, alpha_data.get_width(), alpha_data.get_height());
- alpha_data.crop(file_image.get_width(), file_image.get_height());
+ alpha_data.crop(width, height);
- for (uint32_t y = 0; y < file_image.get_height(); y++)
- for (uint32_t x = 0; x < file_image.get_width(); x++)
- file_image(x, y).a = (uint8_t)alpha_data(x, y).get_709_luma();
+ for (uint32_t y = 0; y < height; y++)
+ for (uint32_t x = 0; x < width; x++)
+ file_image(x, y).a = (uint8_t)alpha_data(x, y).get_709_luma();
+ }
}
}
else
{
- file_image = m_params.m_source_images[source_file_index];
+ if (m_params.m_hdr)
+ {
+ file_image_hdr = m_params.m_source_images_hdr[source_file_index];
+ clean_hdr_image(file_image_hdr);
+ }
+ else
+ {
+ file_image = m_params.m_source_images[source_file_index];
+ }
}
- if (m_params.m_renormalize)
- file_image.renormalize_normal_map();
+ if (!m_params.m_hdr)
+ {
+ if (m_params.m_renormalize)
+ file_image.renormalize_normal_map();
+ }
bool alpha_swizzled = false;
+
if (m_params.m_swizzle[0] != 0 ||
m_params.m_swizzle[1] != 1 ||
m_params.m_swizzle[2] != 2 ||
m_params.m_swizzle[3] != 3)
{
- // Used for XY normal maps in RG - puts X in color, Y in alpha
- for (uint32_t y = 0; y < file_image.get_height(); y++)
- for (uint32_t x = 0; x < file_image.get_width(); x++)
+ if (!m_params.m_hdr)
+ {
+ // Used for XY normal maps in RG - puts X in color, Y in alpha
+ for (uint32_t y = 0; y < file_image.get_height(); y++)
+ {
+ for (uint32_t x = 0; x < file_image.get_width(); x++)
+ {
+ const color_rgba& c = file_image(x, y);
+ file_image(x, y).set_noclamp_rgba(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], c[m_params.m_swizzle[3]]);
+ }
+ }
+
+ alpha_swizzled = (m_params.m_swizzle[3] != 3);
+ }
+ else
+ {
+ // Used for XY normal maps in RG - puts X in color, Y in alpha
+ for (uint32_t y = 0; y < file_image_hdr.get_height(); y++)
{
- const color_rgba &c = file_image(x, y);
- file_image(x, y).set_noclamp_rgba(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], c[m_params.m_swizzle[3]]);
+ for (uint32_t x = 0; x < file_image_hdr.get_width(); x++)
+ {
+ const vec4F& c = file_image_hdr(x, y);
+
+ // For now, alpha is always 1.0f in UASTC HDR.
+ file_image_hdr(x, y).set(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], 1.0f); // c[m_params.m_swizzle[3]]);
+ }
}
- alpha_swizzled = m_params.m_swizzle[3] != 3;
+ }
}
-
+
bool has_alpha = false;
- if (m_params.m_force_alpha || alpha_swizzled)
- has_alpha = true;
- else if (!m_params.m_check_for_alpha)
- file_image.set_alpha(255);
- else if (file_image.has_alpha())
- has_alpha = true;
- if (has_alpha)
- m_any_source_image_has_alpha = true;
+ if (!m_params.m_hdr)
+ {
+ if (m_params.m_force_alpha || alpha_swizzled)
+ has_alpha = true;
+ else if (!m_params.m_check_for_alpha)
+ file_image.set_alpha(255);
+ else if (file_image.has_alpha())
+ has_alpha = true;
+
+ if (has_alpha)
+ m_any_source_image_has_alpha = true;
+ }
+
+ {
+ const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width();
+ const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height();
+
+ debug_printf("Source image index %u filename %s %ux%u has alpha: %u\n", source_file_index, pSource_filename, width, height, has_alpha);
+ }
- debug_printf("Source image index %u filename %s %ux%u has alpha: %u\n", source_file_index, pSource_filename, file_image.get_width(), file_image.get_height(), has_alpha);
-
if (m_params.m_y_flip)
- file_image.flip_y();
+ {
+ if (m_params.m_hdr)
+ file_image_hdr.flip_y();
+ else
+ file_image.flip_y();
+ }
#if DEBUG_EXTRACT_SINGLE_BLOCK
- image block_image(4, 4);
const uint32_t block_x = 0;
const uint32_t block_y = 0;
- block_image.blit(block_x * 4, block_y * 4, 4, 4, 0, 0, file_image, 0);
- file_image = block_image;
+
+ if (m_params.m_hdr)
+ {
+ imagef block_image(4, 4);
+ block_image_hdr.blit(block_x * 4, block_y * 4, 4, 4, 0, 0, file_image_hdr, 0);
+ file_image_hdr = block_image;
+ }
+ else
+ {
+ image block_image(4, 4);
+ block_image.blit(block_x * 4, block_y * 4, 4, 4, 0, 0, file_image, 0);
+ file_image = block_image;
+ }
#endif
#if DEBUG_CROP_TEXTURE_TO_64x64
- file_image.resize(64, 64);
+ if (m_params.m_hdr)
+ file_image_hdr.resize(64, 64);
+ else
+ file_image.resize(64, 64);
#endif
- if (m_params.m_resample_width > 0 && m_params.m_resample_height > 0)
+ if ((m_params.m_resample_width > 0) && (m_params.m_resample_height > 0))
{
int new_width = basisu::minimum<int>(m_params.m_resample_width, BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
int new_height = basisu::minimum<int>(m_params.m_resample_height, BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
@@ -588,129 +1391,225 @@ namespace basisu
debug_printf("Resampling to %ix%i\n", new_width, new_height);
// TODO: A box filter - kaiser looks too sharp on video. Let the caller control this.
- image temp_img(new_width, new_height);
- image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser");
- temp_img.swap(file_image);
+ if (m_params.m_hdr)
+ {
+ imagef temp_img(new_width, new_height);
+ image_resample(file_image_hdr, temp_img, "box"); // "kaiser");
+ clean_hdr_image(temp_img);
+ temp_img.swap(file_image_hdr);
+ }
+ else
+ {
+ image temp_img(new_width, new_height);
+ image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser");
+ temp_img.swap(file_image);
+ }
}
else if (m_params.m_resample_factor > 0.0f)
{
- int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_width() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
- int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_height() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
+ // TODO: A box filter - kaiser looks too sharp on video. Let the caller control this.
+ if (m_params.m_hdr)
+ {
+ int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image_hdr.get_width() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
+ int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image_hdr.get_height() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
- debug_printf("Resampling to %ix%i\n", new_width, new_height);
+ debug_printf("Resampling to %ix%i\n", new_width, new_height);
- // TODO: A box filter - kaiser looks too sharp on video. Let the caller control this.
- image temp_img(new_width, new_height);
- image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser");
- temp_img.swap(file_image);
+ imagef temp_img(new_width, new_height);
+ image_resample(file_image_hdr, temp_img, "box"); // "kaiser");
+ clean_hdr_image(temp_img);
+ temp_img.swap(file_image_hdr);
+ }
+ else
+ {
+ int new_width = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_width() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
+ int new_height = basisu::minimum<int>(basisu::maximum(1, (int)ceilf(file_image.get_height() * m_params.m_resample_factor)), BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
+
+ debug_printf("Resampling to %ix%i\n", new_width, new_height);
+
+ image temp_img(new_width, new_height);
+ image_resample(file_image, temp_img, m_params.m_perceptual, "box"); // "kaiser");
+ temp_img.swap(file_image);
+ }
}
- if ((!file_image.get_width()) || (!file_image.get_height()))
+ const uint32_t width = m_params.m_hdr ? file_image_hdr.get_width() : file_image.get_width();
+ const uint32_t height = m_params.m_hdr ? file_image_hdr.get_height() : file_image.get_height();
+
+ if ((!width) || (!height))
{
error_printf("basis_compressor::read_source_images: Source image has a zero width and/or height!\n");
return false;
}
- if ((file_image.get_width() > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (file_image.get_height() > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION))
+ if ((width > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION) || (height > BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION))
{
error_printf("basis_compressor::read_source_images: Source image \"%s\" is too large!\n", pSource_filename);
return false;
}
- source_images.enlarge(1)->swap(file_image);
+ if (!m_params.m_hdr)
+ source_images.enlarge(1)->swap(file_image);
+ else
+ source_images_hdr.enlarge(1)->swap(file_image_hdr);
+
source_filenames.push_back(pSource_filename);
}
// Check if the caller has generated their own mipmaps.
- if (m_params.m_source_mipmap_images.size())
+ if (m_params.m_hdr)
{
- // Make sure they've passed us enough mipmap chains.
- if ((m_params.m_source_images.size() != m_params.m_source_mipmap_images.size()) || (total_source_files != m_params.m_source_images.size()))
+ if (m_params.m_source_mipmap_images_hdr.size())
{
- error_printf("basis_compressor::read_source_images(): m_params.m_source_mipmap_images.size() must equal m_params.m_source_images.size()!\n");
- return false;
+ // Make sure they've passed us enough mipmap chains.
+ if ((m_params.m_source_images_hdr.size() != m_params.m_source_mipmap_images_hdr.size()) || (total_source_files != m_params.m_source_images_hdr.size()))
+ {
+ error_printf("basis_compressor::read_source_images(): m_params.m_source_mipmap_images_hdr.size() must equal m_params.m_source_images_hdr.size()!\n");
+ return false;
+ }
}
-
- // Check if any of the user-supplied mipmap levels has alpha.
- // We're assuming the user has already preswizzled their mipmap source images.
- if (!m_any_source_image_has_alpha)
+ }
+ else
+ {
+ if (m_params.m_source_mipmap_images.size())
{
- for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
+ // Make sure they've passed us enough mipmap chains.
+ if ((m_params.m_source_images.size() != m_params.m_source_mipmap_images.size()) || (total_source_files != m_params.m_source_images.size()))
{
- for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++)
- {
- const image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index];
+ error_printf("basis_compressor::read_source_images(): m_params.m_source_mipmap_images.size() must equal m_params.m_source_images.size()!\n");
+ return false;
+ }
- if (mip_img.has_alpha())
+ // Check if any of the user-supplied mipmap levels has alpha.
+ if (!m_any_source_image_has_alpha)
+ {
+ for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
+ {
+ for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++)
{
- m_any_source_image_has_alpha = true;
- break;
+ const image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index];
+
+ // Be sure to take into account any swizzling which will be applied.
+ if (mip_img.has_alpha(m_params.m_swizzle[3]))
+ {
+ m_any_source_image_has_alpha = true;
+ break;
+ }
}
- }
- if (m_any_source_image_has_alpha)
- break;
+ if (m_any_source_image_has_alpha)
+ break;
+ }
}
}
}
debug_printf("Any source image has alpha: %u\n", m_any_source_image_has_alpha);
+ // Now, for each source image, create the slices corresponding to that image.
for (uint32_t source_file_index = 0; source_file_index < total_source_files; source_file_index++)
{
const std::string &source_filename = source_filenames[source_file_index];
-
- // Now, for each source image, create the slices corresponding to that image.
+
basisu::vector<image> slices;
+ basisu::vector<imagef> slices_hdr;
slices.reserve(32);
+ slices_hdr.reserve(32);
// The first (largest) mipmap level.
- image& file_image = source_images[source_file_index];
-
+ image *pFile_image = source_images.size() ? &source_images[source_file_index] : nullptr;
+ imagef *pFile_image_hdr = source_images_hdr.size() ? &source_images_hdr[source_file_index] : nullptr;
+
// Reserve a slot for mip0.
- slices.resize(1);
-
- if (m_params.m_source_mipmap_images.size())
+ if (m_params.m_hdr)
+ slices_hdr.resize(1);
+ else
+ slices.resize(1);
+
+ if ((!m_params.m_hdr) && (m_params.m_source_mipmap_images.size()))
{
// User-provided mipmaps for each layer or image in the texture array.
for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images[source_file_index].size(); mip_index++)
{
image& mip_img = m_params.m_source_mipmap_images[source_file_index][mip_index];
- if (m_params.m_swizzle[0] != 0 ||
- m_params.m_swizzle[1] != 1 ||
- m_params.m_swizzle[2] != 2 ||
- m_params.m_swizzle[3] != 3)
+ if ((m_params.m_swizzle[0] != 0) ||
+ (m_params.m_swizzle[1] != 1) ||
+ (m_params.m_swizzle[2] != 2) ||
+ (m_params.m_swizzle[3] != 3))
{
// Used for XY normal maps in RG - puts X in color, Y in alpha
for (uint32_t y = 0; y < mip_img.get_height(); y++)
+ {
for (uint32_t x = 0; x < mip_img.get_width(); x++)
{
- const color_rgba &c = mip_img(x, y);
+ const color_rgba& c = mip_img(x, y);
mip_img(x, y).set_noclamp_rgba(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], c[m_params.m_swizzle[3]]);
}
+ }
}
slices.push_back(mip_img);
}
}
+ else if ((m_params.m_hdr) && (m_params.m_source_mipmap_images_hdr.size()))
+ {
+ // User-provided mipmaps for each layer or image in the texture array.
+ for (uint32_t mip_index = 0; mip_index < m_params.m_source_mipmap_images_hdr[source_file_index].size(); mip_index++)
+ {
+ imagef& mip_img = m_params.m_source_mipmap_images_hdr[source_file_index][mip_index];
+
+ if ((m_params.m_swizzle[0] != 0) ||
+ (m_params.m_swizzle[1] != 1) ||
+ (m_params.m_swizzle[2] != 2) ||
+ (m_params.m_swizzle[3] != 3))
+ {
+ // Used for XY normal maps in RG - puts X in color, Y in alpha
+ for (uint32_t y = 0; y < mip_img.get_height(); y++)
+ {
+ for (uint32_t x = 0; x < mip_img.get_width(); x++)
+ {
+ const vec4F& c = mip_img(x, y);
+
+ // For now, HDR alpha is always 1.0f.
+ mip_img(x, y).set(c[m_params.m_swizzle[0]], c[m_params.m_swizzle[1]], c[m_params.m_swizzle[2]], 1.0f); // c[m_params.m_swizzle[3]]);
+ }
+ }
+ }
+
+ clean_hdr_image(mip_img);
+
+ slices_hdr.push_back(mip_img);
+ }
+ }
else if (m_params.m_mip_gen)
{
// Automatically generate mipmaps.
- if (!generate_mipmaps(file_image, slices, m_any_source_image_has_alpha))
- return false;
+ if (m_params.m_hdr)
+ {
+ if (!generate_mipmaps(*pFile_image_hdr, slices_hdr, m_any_source_image_has_alpha))
+ return false;
+ }
+ else
+ {
+ if (!generate_mipmaps(*pFile_image, slices, m_any_source_image_has_alpha))
+ return false;
+ }
}
// Swap in the largest mipmap level here to avoid copying it, because generate_mips() will change the array.
// NOTE: file_image is now blank.
- slices[0].swap(file_image);
+ if (m_params.m_hdr)
+ slices_hdr[0].swap(*pFile_image_hdr);
+ else
+ slices[0].swap(*pFile_image);
- uint_vec mip_indices(slices.size());
- for (uint32_t i = 0; i < slices.size(); i++)
+ uint_vec mip_indices(m_params.m_hdr ? slices_hdr.size() : slices.size());
+ for (uint32_t i = 0; i < (m_params.m_hdr ? slices_hdr.size() : slices.size()); i++)
mip_indices[i] = i;
- if ((m_any_source_image_has_alpha) && (!m_params.m_uastc))
+ if ((!m_params.m_hdr) && (m_any_source_image_has_alpha) && (!m_params.m_uastc))
{
// For ETC1S, if source has alpha, then even mips will have RGB, and odd mips will have alpha in RGB.
basisu::vector<image> alpha_slices;
@@ -745,20 +1644,29 @@ namespace basisu
mip_indices.swap(new_mip_indices);
}
- assert(slices.size() == mip_indices.size());
-
- for (uint32_t slice_index = 0; slice_index < slices.size(); slice_index++)
+ if (m_params.m_hdr)
+ {
+ assert(slices_hdr.size() == mip_indices.size());
+ }
+ else
+ {
+ assert(slices.size() == mip_indices.size());
+ }
+
+ for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? slices_hdr.size() : slices.size()); slice_index++)
{
- image& slice_image = slices[slice_index];
- const uint32_t orig_width = slice_image.get_width();
- const uint32_t orig_height = slice_image.get_height();
+ image *pSlice_image = m_params.m_hdr ? nullptr : &slices[slice_index];
+ imagef *pSlice_image_hdr = m_params.m_hdr ? &slices_hdr[slice_index] : nullptr;
+
+ const uint32_t orig_width = m_params.m_hdr ? pSlice_image_hdr->get_width() : pSlice_image->get_width();
+ const uint32_t orig_height = m_params.m_hdr ? pSlice_image_hdr->get_height() : pSlice_image->get_height();
bool is_alpha_slice = false;
- if (m_any_source_image_has_alpha)
+ if ((!m_params.m_hdr) && (m_any_source_image_has_alpha))
{
if (m_params.m_uastc)
{
- is_alpha_slice = slice_image.has_alpha();
+ is_alpha_slice = pSlice_image->has_alpha();
}
else
{
@@ -767,43 +1675,69 @@ namespace basisu
}
// Enlarge the source image to 4x4 block boundaries, duplicating edge pixels if necessary to avoid introducing extra colors into blocks.
- slice_image.crop_dup_borders(slice_image.get_block_width(4) * 4, slice_image.get_block_height(4) * 4);
+ if (m_params.m_hdr)
+ pSlice_image_hdr->crop_dup_borders(pSlice_image_hdr->get_block_width(4) * 4, pSlice_image_hdr->get_block_height(4) * 4);
+ else
+ pSlice_image->crop_dup_borders(pSlice_image->get_block_width(4) * 4, pSlice_image->get_block_height(4) * 4);
if (m_params.m_debug_images)
{
- save_png(string_format("basis_debug_source_image_%u_slice_%u.png", source_file_index, slice_index).c_str(), slice_image);
+ if (m_params.m_hdr)
+ write_exr(string_format("basis_debug_source_image_%u_slice_%u.exr", source_file_index, slice_index).c_str(), *pSlice_image_hdr, 3, 0);
+ else
+ save_png(string_format("basis_debug_source_image_%u_slice_%u.png", source_file_index, slice_index).c_str(), *pSlice_image);
}
- const uint32_t dest_image_index = m_slice_images.size();
+ const uint32_t dest_image_index = (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size());
enlarge_vector(m_stats, 1);
- enlarge_vector(m_slice_images, 1);
+
+ if (m_params.m_hdr)
+ enlarge_vector(m_slice_images_hdr, 1);
+ else
+ enlarge_vector(m_slice_images, 1);
+
enlarge_vector(m_slice_descs, 1);
-
+
m_stats[dest_image_index].m_filename = source_filename.c_str();
m_stats[dest_image_index].m_width = orig_width;
m_stats[dest_image_index].m_height = orig_height;
-
- debug_printf("****** Slice %u: mip %u, alpha_slice: %u, filename: \"%s\", original: %ux%u actual: %ux%u\n", m_slice_descs.size() - 1, mip_indices[slice_index], is_alpha_slice, source_filename.c_str(), orig_width, orig_height, slice_image.get_width(), slice_image.get_height());
- basisu_backend_slice_desc &slice_desc = m_slice_descs[dest_image_index];
+ debug_printf("****** Slice %u: mip %u, alpha_slice: %u, filename: \"%s\", original: %ux%u actual: %ux%u\n",
+ m_slice_descs.size() - 1, mip_indices[slice_index], is_alpha_slice, source_filename.c_str(),
+ orig_width, orig_height,
+ m_params.m_hdr ? pSlice_image_hdr->get_width() : pSlice_image->get_width(),
+ m_params.m_hdr ? pSlice_image_hdr->get_height() : pSlice_image->get_height());
+
+ basisu_backend_slice_desc& slice_desc = m_slice_descs[dest_image_index];
slice_desc.m_first_block_index = m_total_blocks;
slice_desc.m_orig_width = orig_width;
slice_desc.m_orig_height = orig_height;
- slice_desc.m_width = slice_image.get_width();
- slice_desc.m_height = slice_image.get_height();
+ if (m_params.m_hdr)
+ {
+ slice_desc.m_width = pSlice_image_hdr->get_width();
+ slice_desc.m_height = pSlice_image_hdr->get_height();
- slice_desc.m_num_blocks_x = slice_image.get_block_width(4);
- slice_desc.m_num_blocks_y = slice_image.get_block_height(4);
+ slice_desc.m_num_blocks_x = pSlice_image_hdr->get_block_width(4);
+ slice_desc.m_num_blocks_y = pSlice_image_hdr->get_block_height(4);
+ }
+ else
+ {
+ slice_desc.m_width = pSlice_image->get_width();
+ slice_desc.m_height = pSlice_image->get_height();
+
+ slice_desc.m_num_blocks_x = pSlice_image->get_block_width(4);
+ slice_desc.m_num_blocks_y = pSlice_image->get_block_height(4);
+ }
slice_desc.m_num_macroblocks_x = (slice_desc.m_num_blocks_x + 1) >> 1;
slice_desc.m_num_macroblocks_y = (slice_desc.m_num_blocks_y + 1) >> 1;
slice_desc.m_source_file_index = source_file_index;
-
+
slice_desc.m_mip_index = mip_indices[slice_index];
slice_desc.m_alpha = is_alpha_slice;
@@ -818,8 +1752,11 @@ namespace basisu
// Finally, swap in the slice's image to avoid copying it.
// NOTE: slice_image is now blank.
- m_slice_images[dest_image_index].swap(slice_image);
-
+ if (m_params.m_hdr)
+ m_slice_images_hdr[dest_image_index].swap(*pSlice_image_hdr);
+ else
+ m_slice_images[dest_image_index].swap(*pSlice_image);
+
} // slice_index
} // source_file_index
@@ -855,7 +1792,7 @@ namespace basisu
if (m_params.m_status_output)
{
- printf("Total basis file slices: %u\n", (uint32_t)m_slice_descs.size());
+ printf("Total slices: %u\n", (uint32_t)m_slice_descs.size());
}
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
@@ -865,11 +1802,17 @@ namespace basisu
if (m_params.m_status_output)
{
printf("Slice: %u, alpha: %u, orig width/height: %ux%u, width/height: %ux%u, first_block: %u, image_index: %u, mip_level: %u, iframe: %u\n",
- i, slice_desc.m_alpha, slice_desc.m_orig_width, slice_desc.m_orig_height, slice_desc.m_width, slice_desc.m_height, slice_desc.m_first_block_index, slice_desc.m_source_file_index, slice_desc.m_mip_index, slice_desc.m_iframe);
+ i, slice_desc.m_alpha, slice_desc.m_orig_width, slice_desc.m_orig_height,
+ slice_desc.m_width, slice_desc.m_height,
+ slice_desc.m_first_block_index, slice_desc.m_source_file_index, slice_desc.m_mip_index, slice_desc.m_iframe);
}
if (m_any_source_image_has_alpha)
{
+ // HDR doesn't support alpha yet
+ if (m_params.m_hdr)
+ return false;
+
if (!m_params.m_uastc)
{
// For ETC1S, alpha slices must be at odd slice indices.
@@ -903,6 +1846,7 @@ namespace basisu
if ((slice_desc.m_orig_width > slice_desc.m_width) || (slice_desc.m_orig_height > slice_desc.m_height))
return false;
+
if ((slice_desc.m_source_file_index == 0) && (m_params.m_tex_type == basist::cBASISTexTypeVideoFrames))
{
if (!slice_desc.m_iframe)
@@ -924,7 +1868,7 @@ namespace basisu
uint32_t total_basis_images = 0;
- for (uint32_t slice_index = 0; slice_index < m_slice_images.size(); slice_index++)
+ for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++)
{
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
@@ -945,7 +1889,7 @@ namespace basisu
uint_vec image_mipmap_levels(total_basis_images);
int width = -1, height = -1;
- for (uint32_t slice_index = 0; slice_index < m_slice_images.size(); slice_index++)
+ for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++)
{
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
@@ -982,20 +1926,52 @@ namespace basisu
{
debug_printf("basis_compressor::extract_source_blocks\n");
- m_source_blocks.resize(m_total_blocks);
+ if (m_params.m_hdr)
+ m_source_blocks_hdr.resize(m_total_blocks);
+ else
+ m_source_blocks.resize(m_total_blocks);
- for (uint32_t slice_index = 0; slice_index < m_slice_images.size(); slice_index++)
+ for (uint32_t slice_index = 0; slice_index < (m_params.m_hdr ? m_slice_images_hdr.size() : m_slice_images.size()); slice_index++)
{
const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
const uint32_t num_blocks_x = slice_desc.m_num_blocks_x;
const uint32_t num_blocks_y = slice_desc.m_num_blocks_y;
- const image& source_image = m_slice_images[slice_index];
+ const image *pSource_image = m_params.m_hdr ? nullptr : &m_slice_images[slice_index];
+ const imagef *pSource_image_hdr = m_params.m_hdr ? &m_slice_images_hdr[slice_index] : nullptr;
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
+ {
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
- source_image.extract_block_clamped(m_source_blocks[slice_desc.m_first_block_index + block_x + block_y * num_blocks_x].get_ptr(), block_x * 4, block_y * 4, 4, 4);
+ {
+ if (m_params.m_hdr)
+ {
+ vec4F* pBlock = m_source_blocks_hdr[slice_desc.m_first_block_index + block_x + block_y * num_blocks_x].get_ptr();
+
+ pSource_image_hdr->extract_block_clamped(pBlock, block_x * 4, block_y * 4, 4, 4);
+
+ // Additional (technically optional) early sanity checking of the block texels.
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ float v = pBlock[i][c];
+
+ if (std::isnan(v) || std::isinf(v) || (v < 0.0f) || (v > basist::MAX_HALF_FLOAT))
+ {
+ error_printf("basis_compressor::extract_source_blocks: invalid float component\n");
+ return false;
+ }
+ }
+ }
+ }
+ else
+ {
+ pSource_image->extract_block_clamped(m_source_blocks[slice_desc.m_first_block_index + block_x + block_y * num_blocks_x].get_ptr(), block_x * 4, block_y * 4, 4, 4);
+ }
+ }
+ }
}
return true;
@@ -1304,6 +2280,8 @@ namespace basisu
m_output_basis_file = comp_data;
uint32_t total_orig_pixels = 0, total_texels = 0, total_orig_texels = 0;
+ (void)total_texels;
+
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
{
const basisu_backend_slice_desc& slice_desc = m_slice_descs[i];
@@ -1335,10 +2313,21 @@ namespace basisu
}
m_decoded_output_textures.resize(m_slice_descs.size());
- m_decoded_output_textures_unpacked.resize(m_slice_descs.size());
- m_decoded_output_textures_bc7.resize(m_slice_descs.size());
- m_decoded_output_textures_unpacked_bc7.resize(m_slice_descs.size());
+ if (m_params.m_hdr)
+ {
+ m_decoded_output_textures_bc6h_hdr_unpacked.resize(m_slice_descs.size());
+
+ m_decoded_output_textures_astc_hdr.resize(m_slice_descs.size());
+ m_decoded_output_textures_astc_hdr_unpacked.resize(m_slice_descs.size());
+ }
+ else
+ {
+ m_decoded_output_textures_unpacked.resize(m_slice_descs.size());
+
+ m_decoded_output_textures_bc7.resize(m_slice_descs.size());
+ m_decoded_output_textures_unpacked_bc7.resize(m_slice_descs.size());
+ }
tm.start();
if (m_params.m_pGlobal_codebooks)
@@ -1360,12 +2349,16 @@ namespace basisu
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
{
+ basisu::texture_format tex_format = m_params.m_hdr ? texture_format::cBC6HUnsigned : (m_params.m_uastc ? texture_format::cUASTC4x4 : texture_format::cETC1);
+ basist::block_format format = m_params.m_hdr ? basist::block_format::cBC6H : (m_params.m_uastc ? basist::block_format::cUASTC_4x4 : basist::block_format::cETC1);
+
gpu_image decoded_texture;
- decoded_texture.init(m_params.m_uastc ? texture_format::cUASTC4x4 : texture_format::cETC1, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
+ decoded_texture.init(
+ tex_format,
+ m_slice_descs[i].m_width, m_slice_descs[i].m_height);
tm.start();
-
- basist::block_format format = m_params.m_uastc ? basist::block_format::cUASTC_4x4 : basist::block_format::cETC1;
+
uint32_t bytes_per_block = m_params.m_uastc ? 16 : 8;
if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i,
@@ -1391,43 +2384,87 @@ namespace basisu
m_decoded_output_textures[i] = decoded_texture;
}
- double total_time_bc7 = 0;
+ double total_alt_transcode_time = 0;
+ tm.start();
- if (basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cUASTC4x4) &&
- basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cETC1S))
+ if (m_params.m_hdr)
{
+ assert(basist::basis_is_format_supported(basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA, basist::basis_tex_format::cUASTC_HDR_4x4));
+
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
{
gpu_image decoded_texture;
- decoded_texture.init(texture_format::cBC7, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
+ decoded_texture.init(texture_format::cASTC_HDR_4x4, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
tm.start();
if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i,
- reinterpret_cast<etc_block*>(decoded_texture.get_ptr()), m_slice_descs[i].m_num_blocks_x * m_slice_descs[i].m_num_blocks_y, basist::block_format::cBC7, 16))
+ reinterpret_cast<basist::astc_blk*>(decoded_texture.get_ptr()), m_slice_descs[i].m_num_blocks_x * m_slice_descs[i].m_num_blocks_y, basist::block_format::cASTC_HDR_4x4, 16))
{
- error_printf("Transcoding failed to BC7 on slice %u!\n", i);
+ error_printf("Transcoding failed to ASTC HDR on slice %u!\n", i);
return false;
}
-
- total_time_bc7 += tm.get_elapsed_secs();
-
- m_decoded_output_textures_bc7[i] = decoded_texture;
+
+ m_decoded_output_textures_astc_hdr[i] = decoded_texture;
+ }
+ }
+ else
+ {
+ if (basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cUASTC4x4) &&
+ basist::basis_is_format_supported(basist::transcoder_texture_format::cTFBC7_RGBA, basist::basis_tex_format::cETC1S))
+ {
+ for (uint32_t i = 0; i < m_slice_descs.size(); i++)
+ {
+ gpu_image decoded_texture;
+ decoded_texture.init(texture_format::cBC7, m_slice_descs[i].m_width, m_slice_descs[i].m_height);
+
+ if (!decoder.transcode_slice(&comp_data[0], (uint32_t)comp_data.size(), i,
+ reinterpret_cast<etc_block*>(decoded_texture.get_ptr()), m_slice_descs[i].m_num_blocks_x * m_slice_descs[i].m_num_blocks_y, basist::block_format::cBC7, 16))
+ {
+ error_printf("Transcoding failed to BC7 on slice %u!\n", i);
+ return false;
+ }
+
+ m_decoded_output_textures_bc7[i] = decoded_texture;
+ }
}
}
+ total_alt_transcode_time = tm.get_elapsed_secs();
+
for (uint32_t i = 0; i < m_slice_descs.size(); i++)
{
- m_decoded_output_textures[i].unpack(m_decoded_output_textures_unpacked[i]);
+ if (m_params.m_hdr)
+ {
+ // BC6H
+ bool status = m_decoded_output_textures[i].unpack_hdr(m_decoded_output_textures_bc6h_hdr_unpacked[i]);
+ assert(status);
+ BASISU_NOTE_UNUSED(status);
+
+ // ASTC HDR
+ status = m_decoded_output_textures_astc_hdr[i].unpack_hdr(m_decoded_output_textures_astc_hdr_unpacked[i]);
+ assert(status);
+ }
+ else
+ {
+ bool status = m_decoded_output_textures[i].unpack(m_decoded_output_textures_unpacked[i]);
+ assert(status);
+ BASISU_NOTE_UNUSED(status);
- if (m_decoded_output_textures_bc7[i].get_pixel_width())
- m_decoded_output_textures_bc7[i].unpack(m_decoded_output_textures_unpacked_bc7[i]);
+ if (m_decoded_output_textures_bc7[i].get_pixel_width())
+ {
+ status = m_decoded_output_textures_bc7[i].unpack(m_decoded_output_textures_unpacked_bc7[i]);
+ assert(status);
+ }
+ }
}
- debug_printf("Transcoded to %s in %3.3fms, %f texels/sec\n", m_params.m_uastc ? "ASTC" : "ETC1", total_time_etc1s_or_astc * 1000.0f, total_orig_pixels / total_time_etc1s_or_astc);
+ debug_printf("Transcoded to %s in %3.3fms, %f texels/sec\n",
+ m_params.m_hdr ? "BC6H" : (m_params.m_uastc ? "ASTC" : "ETC1"),
+ total_time_etc1s_or_astc * 1000.0f, total_orig_pixels / total_time_etc1s_or_astc);
- if (total_time_bc7 != 0)
- debug_printf("Transcoded to BC7 in %3.3fms, %f texels/sec\n", total_time_bc7 * 1000.0f, total_orig_pixels / total_time_bc7);
+ if (total_alt_transcode_time != 0)
+ debug_printf("Alternate transcode in %3.3fms, %f texels/sec\n", total_alt_transcode_time * 1000.0f, total_orig_pixels / total_alt_transcode_time);
for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
{
@@ -1438,17 +2475,82 @@ namespace basisu
assert(m_decoded_output_textures[slice_index].get_total_blocks() == total_blocks);
}
+
} // if (m_params.m_validate_output_data)
return true;
}
+ bool basis_compressor::write_hdr_debug_images(const char* pBasename, const imagef& orig_hdr_img, uint32_t width, uint32_t height)
+ {
+ // Copy image to account for 4x4 block expansion
+ imagef hdr_img(orig_hdr_img);
+ hdr_img.resize(width, height);
+
+ image srgb_img(width, height);
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ vec4F p(hdr_img(x, y));
+
+ p[0] = clamp(p[0], 0.0f, 1.0f);
+ p[1] = clamp(p[1], 0.0f, 1.0f);
+ p[2] = clamp(p[2], 0.0f, 1.0f);
+
+ int rc = (int)std::round(linear_to_srgb(p[0]) * 255.0f);
+ int gc = (int)std::round(linear_to_srgb(p[1]) * 255.0f);
+ int bc = (int)std::round(linear_to_srgb(p[2]) * 255.0f);
+
+ srgb_img.set_clipped(x, y, color_rgba(rc, gc, bc, 255));
+ }
+ }
+
+ {
+ const std::string filename(string_format("%s_linear_clamped_to_srgb.png", pBasename));
+ save_png(filename.c_str(), srgb_img);
+ printf("Wrote .PNG file %s\n", filename.c_str());
+ }
+
+ {
+ const std::string filename(string_format("%s_compressive_tonemapped.png", pBasename));
+ image compressive_tonemapped_img;
+
+ bool status = tonemap_image_compressive(compressive_tonemapped_img, hdr_img);
+ if (!status)
+ {
+ error_printf("basis_compressor::write_hdr_debug_images: tonemap_image_compressive() failed (invalid half-float input)\n");
+ }
+ else
+ {
+ save_png(filename.c_str(), compressive_tonemapped_img);
+ printf("Wrote .PNG file %s\n", filename.c_str());
+ }
+ }
+
+ image tonemapped_img;
+
+ for (int e = -5; e <= 5; e++)
+ {
+ const float scale = powf(2.0f, (float)e);
+
+ tonemap_image_reinhard(tonemapped_img, hdr_img, scale);
+
+ std::string filename(string_format("%s_reinhard_tonemapped_scale_%f.png", pBasename, scale));
+ save_png(filename.c_str(), tonemapped_img, cImageSaveIgnoreAlpha);
+ printf("Wrote .PNG file %s\n", filename.c_str());
+ }
+
+ return true;
+ }
+
bool basis_compressor::write_output_files_and_compute_stats()
{
debug_printf("basis_compressor::write_output_files_and_compute_stats\n");
const uint8_vec& comp_data = m_params.m_create_ktx2_file ? m_output_ktx2_file : m_basis_file.get_compressed_data();
- if (m_params.m_write_output_basis_files)
+ if (m_params.m_write_output_basis_or_ktx2_files)
{
const std::string& output_filename = m_params.m_out_filename;
@@ -1458,7 +2560,7 @@ namespace basisu
return false;
}
- if (m_params.m_status_output)
+ //if (m_params.m_status_output)
{
printf("Wrote output .basis/.ktx2 file \"%s\"\n", output_filename.c_str());
}
@@ -1485,7 +2587,7 @@ namespace basisu
m_basis_bits_per_texel = comp_size * 8.0f / total_texels;
- debug_printf(".basis file size: %u, LZ compressed file size: %u, %3.2f bits/texel\n",
+ debug_printf("Output file size: %u, LZ compressed file size: %u, %3.2f bits/texel\n",
(uint32_t)comp_data.size(),
(uint32_t)comp_size,
m_basis_bits_per_texel);
@@ -1495,191 +2597,324 @@ namespace basisu
if (m_params.m_validate_output_data)
{
- for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
+ if (m_params.m_hdr)
{
- const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
+ if (m_params.m_print_stats)
+ {
+ printf("ASTC/BC6H half float space error metrics (a piecewise linear approximation of log2 error):\n");
+ }
- if (m_params.m_compute_stats)
+ for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
{
- if (m_params.m_print_stats)
- printf("Slice: %u\n", slice_index);
+ const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
- image_stats& s = m_stats[slice_index];
+ if (m_params.m_compute_stats)
+ {
+ image_stats& s = m_stats[slice_index];
- // TODO: We used to output SSIM (during heavy encoder development), but this slowed down compression too much. We'll be adding it back.
+ if (m_params.m_print_stats)
+ {
+ printf("Slice: %u\n", slice_index);
+ }
- image_metrics em;
+ image_metrics im;
- // ---- .basis stats
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3);
- if (m_params.m_print_stats)
- em.print(".basis RGB Avg: ");
- s.m_basis_rgb_avg_psnr = em.m_psnr;
+ if (m_params.m_print_stats)
+ {
+ printf("\nASTC channels:\n");
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], i, 1, true);
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 4);
- if (m_params.m_print_stats)
- em.print(".basis RGBA Avg: ");
- s.m_basis_rgba_avg_psnr = em.m_psnr;
+ printf("%c: ", "RGB"[i]);
+ im.print_hp();
+ }
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 1);
- if (m_params.m_print_stats)
- em.print(".basis R Avg: ");
+ printf("BC6H channels:\n");
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], i, 1, true);
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 1, 1);
- if (m_params.m_print_stats)
- em.print(".basis G Avg: ");
+ printf("%c: ", "RGB"[i]);
+ im.print_hp();
+ }
+ }
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 2, 1);
- if (m_params.m_print_stats)
- em.print(".basis B Avg: ");
+ im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true);
+ s.m_basis_rgb_avg_psnr = (float)im.m_psnr;
- if (m_params.m_uastc)
- {
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 3, 1);
if (m_params.m_print_stats)
- em.print(".basis A Avg: ");
+ {
+ printf("\nASTC RGB: ");
+ im.print_hp();
+#if 0
+ // Validation
+ im.calc_half2(m_slice_images_hdr[slice_index], m_decoded_output_textures_astc_hdr_unpacked[slice_index], 0, 3, true);
+ printf("\nASTC RGB (Alt): ");
+ im.print_hp();
+#endif
+ }
- s.m_basis_a_avg_psnr = em.m_psnr;
+ im.calc_half(m_slice_images_hdr[slice_index], m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], 0, 3, true);
+ s.m_basis_rgb_avg_bc6h_psnr = (float)im.m_psnr;
+
+ if (m_params.m_print_stats)
+ {
+ printf("BC6H RGB: ");
+ im.print_hp();
+ printf("\n");
+ }
}
+
+ if (m_params.m_debug_images)
+ {
+ std::string out_basename;
+ if (m_params.m_out_filename.size())
+ string_get_filename(m_params.m_out_filename.c_str(), out_basename);
+ else if (m_params.m_source_filenames.size())
+ string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename);
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0);
- if (m_params.m_print_stats)
- em.print(".basis 709 Luma: ");
- s.m_basis_luma_709_psnr = static_cast<float>(em.m_psnr);
- s.m_basis_luma_709_ssim = static_cast<float>(em.m_ssim);
+ string_remove_extension(out_basename);
+ out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index);
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0, true, true);
- if (m_params.m_print_stats)
- em.print(".basis 601 Luma: ");
- s.m_basis_luma_601_psnr = static_cast<float>(em.m_psnr);
+ // Write BC6H .DDS file.
+ {
+ gpu_image bc6h_tex(m_decoded_output_textures[slice_index]);
+ bc6h_tex.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
+
+ std::string filename(out_basename + "_bc6h.dds");
+ write_compressed_texture_file(filename.c_str(), bc6h_tex, true);
+ printf("Wrote .DDS file %s\n", filename.c_str());
+ }
- if (m_slice_descs.size() == 1)
- {
- const uint32_t output_size = comp_size ? (uint32_t)comp_size : (uint32_t)comp_data.size();
- if (m_params.m_print_stats)
+ // Write ASTC .KTX/.astc files. ("astcenc -dh input.astc output.exr" to decode the astc file.)
+ {
+ gpu_image astc_tex(m_decoded_output_textures_astc_hdr[slice_index]);
+ astc_tex.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
+
+ std::string filename1(out_basename + "_astc.astc");
+ write_astc_file(filename1.c_str(), astc_tex.get_ptr(), 4, 4, slice_desc.m_orig_width, slice_desc.m_orig_height);
+ printf("Wrote .ASTC file %s\n", filename1.c_str());
+
+ std::string filename2(out_basename + "_astc.ktx");
+ write_compressed_texture_file(filename2.c_str(), astc_tex, true);
+ printf("Wrote .KTX file %s\n", filename2.c_str());
+ }
+
+ // Write unpacked ASTC image to .EXR
{
- debug_printf(".basis RGB PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_rgb_avg_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
- debug_printf(".basis Luma 709 PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_luma_709_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
+ imagef astc_img(m_decoded_output_textures_astc_hdr_unpacked[slice_index]);
+ astc_img.resize(slice_desc.m_orig_width, slice_desc.m_orig_height);
+
+ std::string filename(out_basename + "_unpacked_astc.exr");
+ write_exr(filename.c_str(), astc_img, 3, 0);
+ printf("Wrote .EXR file %s\n", filename.c_str());
}
+
+ // Write unpacked BC6H image to .EXR
+ {
+ imagef bc6h_img(m_decoded_output_textures_bc6h_hdr_unpacked[slice_index]);
+ bc6h_img.resize(slice_desc.m_orig_width, slice_desc.m_orig_height);
+
+ std::string filename(out_basename + "_unpacked_bc6h.exr");
+ write_exr(filename.c_str(), bc6h_img, 3, 0);
+ printf("Wrote .EXR file %s\n", filename.c_str());
+ }
+
+ // Write tonemapped/srgb images
+ write_hdr_debug_images((out_basename + "_source").c_str(), m_slice_images_hdr[slice_index], slice_desc.m_orig_width, slice_desc.m_orig_height);
+ write_hdr_debug_images((out_basename + "_unpacked_astc").c_str(), m_decoded_output_textures_astc_hdr_unpacked[slice_index], slice_desc.m_orig_width, slice_desc.m_orig_height);
+ write_hdr_debug_images((out_basename + "_unpacked_bc6h").c_str(), m_decoded_output_textures_bc6h_hdr_unpacked[slice_index], slice_desc.m_orig_width, slice_desc.m_orig_height);
}
+ }
+ }
+ else
+ {
+ for (uint32_t slice_index = 0; slice_index < m_slice_descs.size(); slice_index++)
+ {
+ const basisu_backend_slice_desc& slice_desc = m_slice_descs[slice_index];
- if (m_decoded_output_textures_unpacked_bc7[slice_index].get_width())
+ if (m_params.m_compute_stats)
{
- // ---- BC7 stats
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 3);
if (m_params.m_print_stats)
- em.print("BC7 RGB Avg: ");
- s.m_bc7_rgb_avg_psnr = em.m_psnr;
+ printf("Slice: %u\n", slice_index);
+
+ image_stats& s = m_stats[slice_index];
+
+ image_metrics em;
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 4);
+ // ---- .basis stats
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3);
if (m_params.m_print_stats)
- em.print("BC7 RGBA Avg: ");
- s.m_bc7_rgba_avg_psnr = em.m_psnr;
+ em.print(".basis RGB Avg: ");
+ s.m_basis_rgb_avg_psnr = (float)em.m_psnr;
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 1);
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 4);
if (m_params.m_print_stats)
- em.print("BC7 R Avg: ");
+ em.print(".basis RGBA Avg: ");
+ s.m_basis_rgba_avg_psnr = (float)em.m_psnr;
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 1, 1);
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 1);
if (m_params.m_print_stats)
- em.print("BC7 G Avg: ");
+ em.print(".basis R Avg: ");
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 2, 1);
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 1, 1);
if (m_params.m_print_stats)
- em.print("BC7 B Avg: ");
+ em.print(".basis G Avg: ");
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 2, 1);
+ if (m_params.m_print_stats)
+ em.print(".basis B Avg: ");
if (m_params.m_uastc)
{
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 3, 1);
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 3, 1);
if (m_params.m_print_stats)
- em.print("BC7 A Avg: ");
+ em.print(".basis A Avg: ");
- s.m_bc7_a_avg_psnr = em.m_psnr;
+ s.m_basis_a_avg_psnr = (float)em.m_psnr;
}
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0);
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0);
if (m_params.m_print_stats)
- em.print("BC7 709 Luma: ");
- s.m_bc7_luma_709_psnr = static_cast<float>(em.m_psnr);
- s.m_bc7_luma_709_ssim = static_cast<float>(em.m_ssim);
+ em.print(".basis 709 Luma: ");
+ s.m_basis_luma_709_psnr = static_cast<float>(em.m_psnr);
+ s.m_basis_luma_709_ssim = static_cast<float>(em.m_ssim);
- em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0, true, true);
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0, true, true);
if (m_params.m_print_stats)
- em.print("BC7 601 Luma: ");
- s.m_bc7_luma_601_psnr = static_cast<float>(em.m_psnr);
- }
+ em.print(".basis 601 Luma: ");
+ s.m_basis_luma_601_psnr = static_cast<float>(em.m_psnr);
- if (!m_params.m_uastc)
- {
- // ---- Nearly best possible ETC1S stats
- em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3);
- if (m_params.m_print_stats)
- em.print("Unquantized ETC1S RGB Avg: ");
- s.m_best_etc1s_rgb_avg_psnr = static_cast<float>(em.m_psnr);
+ if (m_slice_descs.size() == 1)
+ {
+ const uint32_t output_size = comp_size ? (uint32_t)comp_size : (uint32_t)comp_data.size();
+ if (m_params.m_print_stats)
+ {
+ debug_printf(".basis RGB PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_rgb_avg_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
+ debug_printf(".basis Luma 709 PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_luma_709_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
+ }
+ }
- em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0);
- if (m_params.m_print_stats)
- em.print("Unquantized ETC1S 709 Luma: ");
- s.m_best_etc1s_luma_709_psnr = static_cast<float>(em.m_psnr);
- s.m_best_etc1s_luma_709_ssim = static_cast<float>(em.m_ssim);
+ if (m_decoded_output_textures_unpacked_bc7[slice_index].get_width())
+ {
+ // ---- BC7 stats
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 3);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 RGB Avg: ");
+ s.m_bc7_rgb_avg_psnr = (float)em.m_psnr;
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 4);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 RGBA Avg: ");
+ s.m_bc7_rgba_avg_psnr = (float)em.m_psnr;
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 1);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 R Avg: ");
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 1, 1);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 G Avg: ");
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 2, 1);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 B Avg: ");
+
+ if (m_params.m_uastc)
+ {
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 3, 1);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 A Avg: ");
- em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0, true, true);
- if (m_params.m_print_stats)
- em.print("Unquantized ETC1S 601 Luma: ");
- s.m_best_etc1s_luma_601_psnr = static_cast<float>(em.m_psnr);
+ s.m_bc7_a_avg_psnr = (float)em.m_psnr;
+ }
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 709 Luma: ");
+ s.m_bc7_luma_709_psnr = static_cast<float>(em.m_psnr);
+ s.m_bc7_luma_709_ssim = static_cast<float>(em.m_ssim);
+
+ em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0, true, true);
+ //if (m_params.m_print_stats)
+ // em.print("BC7 601 Luma: ");
+ s.m_bc7_luma_601_psnr = static_cast<float>(em.m_psnr);
+ }
+
+ if (!m_params.m_uastc)
+ {
+ // ---- Nearly best possible ETC1S stats
+ em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3);
+ //if (m_params.m_print_stats)
+ // em.print("Unquantized ETC1S RGB Avg: ");
+ s.m_best_etc1s_rgb_avg_psnr = static_cast<float>(em.m_psnr);
+
+ em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0);
+ //if (m_params.m_print_stats)
+ // em.print("Unquantized ETC1S 709 Luma: ");
+ s.m_best_etc1s_luma_709_psnr = static_cast<float>(em.m_psnr);
+ s.m_best_etc1s_luma_709_ssim = static_cast<float>(em.m_ssim);
+
+ em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0, true, true);
+ //if (m_params.m_print_stats)
+ // em.print("Unquantized ETC1S 601 Luma: ");
+ s.m_best_etc1s_luma_601_psnr = static_cast<float>(em.m_psnr);
+ }
}
- }
- std::string out_basename;
- if (m_params.m_out_filename.size())
- string_get_filename(m_params.m_out_filename.c_str(), out_basename);
- else if (m_params.m_source_filenames.size())
- string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename);
+ std::string out_basename;
+ if (m_params.m_out_filename.size())
+ string_get_filename(m_params.m_out_filename.c_str(), out_basename);
+ else if (m_params.m_source_filenames.size())
+ string_get_filename(m_params.m_source_filenames[slice_desc.m_source_file_index].c_str(), out_basename);
- string_remove_extension(out_basename);
- out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index);
+ string_remove_extension(out_basename);
+ out_basename = "basis_debug_" + out_basename + string_format("_slice_%u", slice_index);
- if ((!m_params.m_uastc) && (m_frontend.get_params().m_debug_images))
- {
- // Write "best" ETC1S debug images
- if (!m_params.m_uastc)
+ if ((!m_params.m_uastc) && (m_frontend.get_params().m_debug_images))
{
- gpu_image best_etc1s_gpu_image(m_best_etc1s_images[slice_index]);
- best_etc1s_gpu_image.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
- write_compressed_texture_file((out_basename + "_best_etc1s.ktx").c_str(), best_etc1s_gpu_image);
+ // Write "best" ETC1S debug images
+ if (!m_params.m_uastc)
+ {
+ gpu_image best_etc1s_gpu_image(m_best_etc1s_images[slice_index]);
+ best_etc1s_gpu_image.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
+ write_compressed_texture_file((out_basename + "_best_etc1s.ktx").c_str(), best_etc1s_gpu_image, true);
- image best_etc1s_unpacked;
- best_etc1s_gpu_image.unpack(best_etc1s_unpacked);
- save_png(out_basename + "_best_etc1s.png", best_etc1s_unpacked);
+ image best_etc1s_unpacked;
+ best_etc1s_gpu_image.unpack(best_etc1s_unpacked);
+ save_png(out_basename + "_best_etc1s.png", best_etc1s_unpacked);
+ }
}
- }
- if (m_params.m_debug_images)
- {
- // Write decoded ETC1S/ASTC debug images
+ if (m_params.m_debug_images)
{
- gpu_image decoded_etc1s_or_astc(m_decoded_output_textures[slice_index]);
- decoded_etc1s_or_astc.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
- write_compressed_texture_file((out_basename + "_transcoded_etc1s_or_astc.ktx").c_str(), decoded_etc1s_or_astc);
+ // Write decoded ETC1S/ASTC debug images
+ {
+ gpu_image decoded_etc1s_or_astc(m_decoded_output_textures[slice_index]);
+ decoded_etc1s_or_astc.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
+ write_compressed_texture_file((out_basename + "_transcoded_etc1s_or_astc.ktx").c_str(), decoded_etc1s_or_astc, true);
- image temp(m_decoded_output_textures_unpacked[slice_index]);
- temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
- save_png(out_basename + "_transcoded_etc1s_or_astc.png", temp);
- }
+ image temp(m_decoded_output_textures_unpacked[slice_index]);
+ temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
+ save_png(out_basename + "_transcoded_etc1s_or_astc.png", temp);
+ }
- // Write decoded BC7 debug images
- if (m_decoded_output_textures_bc7[slice_index].get_pixel_width())
- {
- gpu_image decoded_bc7(m_decoded_output_textures_bc7[slice_index]);
- decoded_bc7.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
- write_compressed_texture_file((out_basename + "_transcoded_bc7.ktx").c_str(), decoded_bc7);
+ // Write decoded BC7 debug images
+ if (m_decoded_output_textures_bc7[slice_index].get_pixel_width())
+ {
+ gpu_image decoded_bc7(m_decoded_output_textures_bc7[slice_index]);
+ decoded_bc7.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
+ write_compressed_texture_file((out_basename + "_transcoded_bc7.ktx").c_str(), decoded_bc7, true);
- image temp(m_decoded_output_textures_unpacked_bc7[slice_index]);
- temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
- save_png(out_basename + "_transcoded_bc7.png", temp);
+ image temp(m_decoded_output_textures_unpacked_bc7[slice_index]);
+ temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
+ save_png(out_basename + "_transcoded_bc7.png", temp);
+ }
}
}
- }
+ } // if (m_params.m_hdr)
+
} // if (m_params.m_validate_output_data)
return true;
@@ -1727,10 +2962,27 @@ namespace basisu
}
static uint8_t g_ktx2_etc1s_nonalpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA3,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3F,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
- static uint8_t g_ktx2_etc1s_alpha_dfd[60] = { 0x3C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x38,0x0,0xA3,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3F,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x40,0x0,0x3F,0xF,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
+ static uint8_t g_ktx2_etc1s_alpha_dfd[60] = { 0x3C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x38,0x0,0xA3,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3F,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x40,0x0,0x3F,0xF,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
+
static uint8_t g_ktx2_uastc_nonalpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA6,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7F,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
- static uint8_t g_ktx2_uastc_alpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA6,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7F,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
-
+ static uint8_t g_ktx2_uastc_alpha_dfd[44] = { 0x2C,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x28,0x0,0xA6,0x1,0x2,0x0,0x3,0x3,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7F,0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xFF,0xFF,0xFF,0xFF };
+
+ // HDR TODO - what is the best Khronos DFD to use for UASTC HDR?
+ static uint8_t g_ktx2_uastc_hdr_nonalpha_dfd[44] =
+ {
+ 0x2C,0x0,0x0,0x0, // 0 totalSize
+ 0x0,0x0,0x0,0x0, // 1 descriptorType/vendorId
+ 0x2,0x0,0x28,0x0, // 2 descriptorBlockSize/versionNumber
+ 0xA7,0x1,0x1,0x0, // 3 flags, transferFunction, colorPrimaries, colorModel
+ 0x3,0x3,0x0,0x0, // 4 texelBlockDimension0-texelBlockDimension3
+ 0x10,0x0,0x0,0x0, // 5 bytesPlane0-bytesPlane3
+ 0x0,0x0,0x0,0x0, // 6 bytesPlane4-bytesPlane7
+ 0x0,0x0,0x7F,0x80, // 7 bitLength/bitOffset/channelType and Qualifer flags (KHR_DF_SAMPLE_DATATYPE_FLOAT etc.)
+ 0x0,0x0,0x0,0x0, // 8 samplePosition0-samplePosition3
+ 0x0,0x0,0x0,0x0, // 9 sampleLower (0.0)
+ 0x00, 0x00, 0x80, 0x3F // 10 sampleHigher (1.0)
+ };
+
void basis_compressor::get_dfd(uint8_vec &dfd, const basist::ktx2_header &header)
{
const uint8_t* pDFD;
@@ -1738,7 +2990,12 @@ namespace basisu
if (m_params.m_uastc)
{
- if (m_any_source_image_has_alpha)
+ if (m_params.m_hdr)
+ {
+ pDFD = g_ktx2_uastc_hdr_nonalpha_dfd;
+ dfd_len = sizeof(g_ktx2_uastc_hdr_nonalpha_dfd);
+ }
+ else if (m_any_source_image_has_alpha)
{
pDFD = g_ktx2_uastc_alpha_dfd;
dfd_len = sizeof(g_ktx2_uastc_alpha_dfd);
@@ -1772,10 +3029,18 @@ namespace basisu
dfd_bits &= ~(0xFF << 16);
- if (m_params.m_ktx2_srgb_transfer_func)
- dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_SRGB << 16);
- else
+ if (m_params.m_hdr)
+ {
+ // TODO: In HDR mode, always write linear for now.
dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_LINEAR << 16);
+ }
+ else
+ {
+ if (m_params.m_ktx2_srgb_transfer_func)
+ dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_SRGB << 16);
+ else
+ dfd_bits |= (basist::KTX2_KHR_DF_TRANSFER_LINEAR << 16);
+ }
basisu::write_le_dword(dfd.data() + 3 * sizeof(uint32_t), dfd_bits);
@@ -1850,7 +3115,12 @@ namespace basisu
header.m_pixel_width = base_width;
header.m_pixel_height = base_height;
header.m_face_count = total_faces;
- header.m_vk_format = basist::KTX2_VK_FORMAT_UNDEFINED;
+
+ if (m_params.m_hdr)
+ header.m_vk_format = basist::KTX2_FORMAT_UASTC_4x4_SFLOAT_BLOCK;
+ else
+ header.m_vk_format = basist::KTX2_VK_FORMAT_UNDEFINED;
+
header.m_type_size = 1;
header.m_level_count = total_levels;
header.m_layer_count = (total_layers > 1) ? total_layers : 0;
@@ -2061,7 +3331,8 @@ namespace basisu
if (bytes_needed_to_pad < 6)
bytes_needed_to_pad += 16;
- printf("WARNING: Due to a KTX2 validator bug related to mipPadding, we must insert a dummy key into the KTX2 file of %u bytes\n", bytes_needed_to_pad);
+ // Just add the padding. It's likely not necessary anymore, but can't really hurt.
+ //printf("WARNING: Due to a KTX2 validator bug related to mipPadding, we must insert a dummy key into the KTX2 file of %u bytes\n", bytes_needed_to_pad);
// We're not good - need to add a dummy key large enough to force file alignment so the mip level array gets aligned.
// We can't just add some bytes before the mip level array because ktx2check will see that as extra data in the file that shouldn't be there in ktxValidator::validateDataSize().
@@ -2258,18 +3529,34 @@ namespace basisu
return result;
}
- void* basis_compress(
- const basisu::vector<image>& source_images,
+ static void* basis_compress(
+ const basisu::vector<image> *pSource_images,
+ const basisu::vector<imagef> *pSource_images_hdr,
uint32_t flags_and_quality, float uastc_rdo_quality,
size_t* pSize,
image_stats* pStats)
{
+ assert((pSource_images != nullptr) || (pSource_images_hdr != nullptr));
+ assert(!((pSource_images != nullptr) && (pSource_images_hdr != nullptr)));
+
// Check input parameters
- if ((!source_images.size()) || (!pSize))
+ if (pSource_images)
{
- error_printf("basis_compress: Invalid parameter\n");
- assert(0);
- return nullptr;
+ if ((!pSource_images->size()) || (!pSize))
+ {
+ error_printf("basis_compress: Invalid parameter\n");
+ assert(0);
+ return nullptr;
+ }
+ }
+ else
+ {
+ if ((!pSource_images_hdr->size()) || (!pSize))
+ {
+ error_printf("basis_compress: Invalid parameter\n");
+ assert(0);
+ return nullptr;
+ }
}
*pSize = 0;
@@ -2287,40 +3574,70 @@ namespace basisu
comp_params.m_y_flip = (flags_and_quality & cFlagYFlip) != 0;
comp_params.m_debug = (flags_and_quality & cFlagDebug) != 0;
-
+ comp_params.m_debug_images = (flags_and_quality & cFlagDebugImages) != 0;
+
// Copy the largest mipmap level
- comp_params.m_source_images.resize(1);
- comp_params.m_source_images[0] = source_images[0];
+ if (pSource_images)
+ {
+ comp_params.m_source_images.resize(1);
+ comp_params.m_source_images[0] = (*pSource_images)[0];
+
+ // Copy the smaller mipmap levels, if any
+ if (pSource_images->size() > 1)
+ {
+ comp_params.m_source_mipmap_images.resize(1);
+ comp_params.m_source_mipmap_images[0].resize(pSource_images->size() - 1);
- // Copy the smaller mipmap levels, if any
- if (source_images.size() > 1)
+ for (uint32_t i = 1; i < pSource_images->size(); i++)
+ comp_params.m_source_mipmap_images[0][i - 1] = (*pSource_images)[i];
+ }
+ }
+ else
{
- comp_params.m_source_mipmap_images.resize(1);
- comp_params.m_source_mipmap_images[0].resize(source_images.size() - 1);
+ comp_params.m_source_images_hdr.resize(1);
+ comp_params.m_source_images_hdr[0] = (*pSource_images_hdr)[0];
- for (uint32_t i = 1; i < source_images.size(); i++)
- comp_params.m_source_mipmap_images[0][i - 1] = source_images[i];
+ // Copy the smaller mipmap levels, if any
+ if (pSource_images_hdr->size() > 1)
+ {
+ comp_params.m_source_mipmap_images_hdr.resize(1);
+ comp_params.m_source_mipmap_images_hdr[0].resize(pSource_images_hdr->size() - 1);
+
+ for (uint32_t i = 1; i < pSource_images->size(); i++)
+ comp_params.m_source_mipmap_images_hdr[0][i - 1] = (*pSource_images_hdr)[i];
+ }
}
comp_params.m_multithreading = (flags_and_quality & cFlagThreaded) != 0;
comp_params.m_use_opencl = (flags_and_quality & cFlagUseOpenCL) != 0;
- comp_params.m_write_output_basis_files = false;
+ comp_params.m_write_output_basis_or_ktx2_files = false;
comp_params.m_perceptual = (flags_and_quality & cFlagSRGB) != 0;
comp_params.m_mip_srgb = comp_params.m_perceptual;
comp_params.m_mip_gen = (flags_and_quality & (cFlagGenMipsWrap | cFlagGenMipsClamp)) != 0;
comp_params.m_mip_wrapping = (flags_and_quality & cFlagGenMipsWrap) != 0;
- comp_params.m_uastc = (flags_and_quality & cFlagUASTC) != 0;
- if (comp_params.m_uastc)
+ if ((pSource_images_hdr) || (flags_and_quality & cFlagHDR))
{
- comp_params.m_pack_uastc_flags = flags_and_quality & cPackUASTCLevelMask;
- comp_params.m_rdo_uastc = (flags_and_quality & cFlagUASTCRDO) != 0;
- comp_params.m_rdo_uastc_quality_scalar = uastc_rdo_quality;
+ // In UASTC HDR mode, the compressor will jam this to true anyway.
+ // And there's no need to set UASTC LDR or ETC1S options.
+ comp_params.m_uastc = true;
}
else
- comp_params.m_quality_level = basisu::maximum<uint32_t>(1, flags_and_quality & 255);
+ {
+ comp_params.m_uastc = (flags_and_quality & cFlagUASTC) != 0;
+ if (comp_params.m_uastc)
+ {
+ comp_params.m_pack_uastc_flags = flags_and_quality & cPackUASTCLevelMask;
+ comp_params.m_rdo_uastc = (flags_and_quality & cFlagUASTCRDO) != 0;
+ comp_params.m_rdo_uastc_quality_scalar = uastc_rdo_quality;
+ }
+ else
+ {
+ comp_params.m_quality_level = basisu::maximum<uint32_t>(1, flags_and_quality & 255);
+ }
+ }
comp_params.m_create_ktx2_file = (flags_and_quality & cFlagKTX2) != 0;
@@ -2337,6 +3654,15 @@ namespace basisu
comp_params.m_print_stats = (flags_and_quality & cFlagPrintStats) != 0;
comp_params.m_status_output = (flags_and_quality & cFlagPrintStatus) != 0;
+ if ((flags_and_quality & cFlagHDR) || (pSource_images_hdr))
+ {
+ comp_params.m_hdr = true;
+ comp_params.m_uastc_hdr_options.set_quality_level(flags_and_quality & cPackUASTCLevelMask);
+ }
+
+ if (flags_and_quality & cFlagHDRLDRImageSRGBToLinearConversion)
+ comp_params.m_hdr_ldr_srgb_to_linear_conversion = true;
+
// Create the compressor, initialize it, and process the input
basis_compressor comp;
if (!comp.init(comp_params))
@@ -2381,6 +3707,24 @@ namespace basisu
}
void* basis_compress(
+ const basisu::vector<image>& source_images,
+ uint32_t flags_and_quality, float uastc_rdo_quality,
+ size_t* pSize,
+ image_stats* pStats)
+ {
+ return basis_compress(&source_images, nullptr, flags_and_quality, uastc_rdo_quality, pSize, pStats);
+ }
+
+ void* basis_compress(
+ const basisu::vector<imagef>& source_images_hdr,
+ uint32_t flags_and_quality,
+ size_t* pSize,
+ image_stats* pStats)
+ {
+ return basis_compress(nullptr, &source_images_hdr, flags_and_quality, 0.0f, pSize, pStats);
+ }
+
+ void* basis_compress(
const uint8_t* pImageRGBA, uint32_t width, uint32_t height, uint32_t pitch_in_pixels,
uint32_t flags_and_quality, float uastc_rdo_quality,
size_t* pSize,
diff --git a/thirdparty/basis_universal/encoder/basisu_comp.h b/thirdparty/basis_universal/encoder/basisu_comp.h
index b6c9fef9e2..1cc75fc8a3 100644
--- a/thirdparty/basis_universal/encoder/basisu_comp.h
+++ b/thirdparty/basis_universal/encoder/basisu_comp.h
@@ -1,5 +1,5 @@
// basisu_comp.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,9 +18,10 @@
#include "basisu_basis_file.h"
#include "../transcoder/basisu_transcoder.h"
#include "basisu_uastc_enc.h"
+#include "basisu_astc_hdr_enc.h"
-#define BASISU_LIB_VERSION 116
-#define BASISU_LIB_VERSION_STRING "1.16"
+#define BASISU_LIB_VERSION 150
+#define BASISU_LIB_VERSION_STRING "1.50"
#ifndef BASISD_SUPPORT_KTX2
#error BASISD_SUPPORT_KTX2 is undefined
@@ -81,6 +82,8 @@ namespace basisu
m_basis_luma_601_psnr = 0.0f;
m_basis_luma_709_ssim = 0.0f;
+ m_basis_rgb_avg_bc6h_psnr = 0.0f;
+
m_bc7_rgb_avg_psnr = 0.0f;
m_bc7_rgba_avg_psnr = 0.0f;
m_bc7_a_avg_psnr = 0.0f;
@@ -100,7 +103,7 @@ namespace basisu
uint32_t m_width;
uint32_t m_height;
- // .basis compressed (ETC1S or UASTC statistics)
+ // .basis/.ktx2 compressed (LDR: ETC1S or UASTC statistics, HDR: transcoded BC6H statistics)
float m_basis_rgb_avg_psnr;
float m_basis_rgba_avg_psnr;
float m_basis_a_avg_psnr;
@@ -108,7 +111,10 @@ namespace basisu
float m_basis_luma_601_psnr;
float m_basis_luma_709_ssim;
- // BC7 statistics
+ // UASTC HDR only.
+ float m_basis_rgb_avg_bc6h_psnr;
+
+ // LDR: BC7 statistics
float m_bc7_rgb_avg_psnr;
float m_bc7_rgba_avg_psnr;
float m_bc7_a_avg_psnr;
@@ -116,7 +122,7 @@ namespace basisu
float m_bc7_luma_601_psnr;
float m_bc7_luma_709_ssim;
- // Highest achievable quality ETC1S statistics
+ // LDR: Highest achievable quality ETC1S statistics
float m_best_etc1s_rgb_avg_psnr;
float m_best_etc1s_luma_709_psnr;
float m_best_etc1s_luma_601_psnr;
@@ -256,7 +262,7 @@ namespace basisu
m_no_selector_rdo.clear();
m_selector_rdo_thresh.clear();
m_read_source_images.clear();
- m_write_output_basis_files.clear();
+ m_write_output_basis_or_ktx2_files.clear();
m_compression_level.clear();
m_compute_stats.clear();
m_print_stats.clear();
@@ -317,27 +323,38 @@ namespace basisu
m_validate_output_data.clear();
+ m_hdr_ldr_srgb_to_linear_conversion.clear();
+
+ m_hdr_favor_astc.clear();
+
m_pJob_pool = nullptr;
}
- // True to generate UASTC .basis file data, otherwise ETC1S.
+ // True to generate UASTC .basis/.KTX2 file data, otherwise ETC1S.
bool_param<false> m_uastc;
+ // Set m_hdr to true to switch to UASTC HDR mode.
+ bool_param<false> m_hdr;
+
bool_param<false> m_use_opencl;
- // If m_read_source_images is true, m_source_filenames (and optionally m_source_alpha_filenames) contains the filenames of PNG images to read.
- // Otherwise, the compressor processes the images in m_source_images.
+ // If m_read_source_images is true, m_source_filenames (and optionally m_source_alpha_filenames) contains the filenames of PNG etc. images to read.
+ // Otherwise, the compressor processes the images in m_source_images or m_source_images_hdr.
basisu::vector<std::string> m_source_filenames;
basisu::vector<std::string> m_source_alpha_filenames;
basisu::vector<image> m_source_images;
+ basisu::vector<imagef> m_source_images_hdr;
+
// Stores mipmaps starting from level 1. Level 0 is still stored in m_source_images, as usual.
// If m_source_mipmaps isn't empty, automatic mipmap generation isn't done. m_source_mipmaps.size() MUST equal m_source_images.size() or the compressor returns an error.
// The compressor applies the user-provided swizzling (in m_swizzle) to these images.
basisu::vector< basisu::vector<image> > m_source_mipmap_images;
+
+ basisu::vector< basisu::vector<imagef> > m_source_mipmap_images_hdr;
- // Filename of the output basis file
+ // Filename of the output basis/ktx2 file
std::string m_out_filename;
// The params are done this way so we can detect when the user has explictly changed them.
@@ -373,8 +390,8 @@ namespace basisu
// Read source images from m_source_filenames/m_source_alpha_filenames
bool_param<false> m_read_source_images;
- // Write the output basis file to disk using m_out_filename
- bool_param<false> m_write_output_basis_files;
+ // Write the output basis/ktx2 file to disk using m_out_filename
+ bool_param<false> m_write_output_basis_or_ktx2_files;
// Compute and display image metrics
bool_param<false> m_compute_stats;
@@ -382,15 +399,15 @@ namespace basisu
// Print stats to stdout, if m_compute_stats is true.
bool_param<true> m_print_stats;
- // Check to see if any input image has an alpha channel, if so then the output basis file will have alpha channels
+ // Check to see if any input image has an alpha channel, if so then the output basis/ktx2 file will have alpha channels
bool_param<true> m_check_for_alpha;
- // Always put alpha slices in the output basis file, even when the input doesn't have alpha
+ // Always put alpha slices in the output basis/ktx2 file, even when the input doesn't have alpha
bool_param<false> m_force_alpha;
bool_param<true> m_multithreading;
- // Split the R channel to RGB and the G channel to alpha, then write a basis file with alpha channels
- char m_swizzle[4];
+ // Split the R channel to RGB and the G channel to alpha, then write a basis/ktx2 file with alpha channels
+ uint8_t m_swizzle[4];
bool_param<false> m_renormalize;
@@ -448,8 +465,17 @@ namespace basisu
param<int> m_ktx2_zstd_supercompression_level;
bool_param<false> m_ktx2_srgb_transfer_func;
+ astc_hdr_codec_options m_uastc_hdr_options;
+
bool_param<false> m_validate_output_data;
+ // If true, LDR images (such as PNG) will be converted to normalized [0,1] linear light (via a sRGB->Linear conversion) and then processed as HDR.
+ // Otherwise, LDR images will be processed as HDR as-is.
+ bool_param<true> m_hdr_ldr_srgb_to_linear_conversion;
+
+ // If true, ASTC HDR quality is favored more than BC6H quality. Otherwise it's a rough balance.
+ bool_param<false> m_hdr_favor_astc;
+
job_pool *m_pJob_pool;
};
@@ -504,6 +530,7 @@ namespace basisu
opencl_context_ptr m_pOpenCL_context;
basisu::vector<image> m_slice_images;
+ basisu::vector<imagef> m_slice_images_hdr;
basisu::vector<image_stats> m_stats;
@@ -515,7 +542,9 @@ namespace basisu
uint32_t m_total_blocks;
basisu_frontend m_frontend;
+
pixel_block_vec m_source_blocks;
+ pixel_block_hdr_vec m_source_blocks_hdr;
basisu::vector<gpu_image> m_frontend_output_textures;
@@ -526,11 +555,17 @@ namespace basisu
basisu_file m_basis_file;
- basisu::vector<gpu_image> m_decoded_output_textures;
+ basisu::vector<gpu_image> m_decoded_output_textures; // BC6H in HDR mode
basisu::vector<image> m_decoded_output_textures_unpacked;
+
basisu::vector<gpu_image> m_decoded_output_textures_bc7;
basisu::vector<image> m_decoded_output_textures_unpacked_bc7;
+ basisu::vector<imagef> m_decoded_output_textures_bc6h_hdr_unpacked; // BC6H in HDR mode
+
+ basisu::vector<gpu_image> m_decoded_output_textures_astc_hdr;
+ basisu::vector<imagef> m_decoded_output_textures_astc_hdr_unpacked;
+
uint8_vec m_output_basis_file;
uint8_vec m_output_ktx2_file;
@@ -541,14 +576,21 @@ namespace basisu
bool m_opencl_failed;
+ void check_for_hdr_inputs();
+ bool sanity_check_input_params();
+ void clean_hdr_image(imagef& src_img);
+ bool read_dds_source_images();
bool read_source_images();
bool extract_source_blocks();
bool process_frontend();
bool extract_frontend_texture_data();
bool process_backend();
bool create_basis_file_and_transcode();
+ bool write_hdr_debug_images(const char* pBasename, const imagef& img, uint32_t width, uint32_t height);
bool write_output_files_and_compute_stats();
+ error_code encode_slices_to_uastc_hdr();
error_code encode_slices_to_uastc();
+ bool generate_mipmaps(const imagef& img, basisu::vector<imagef>& mips, bool has_alpha);
bool generate_mipmaps(const image &img, basisu::vector<image> &mips, bool has_alpha);
bool validate_texture_type_constraints();
bool validate_ktx2_constraints();
@@ -568,7 +610,8 @@ namespace basisu
//
// flags_and_quality: Combination of the above flags logically OR'd with the ETC1S or UASTC level, i.e. "cFlagSRGB | cFlagGenMipsClamp | cFlagThreaded | 128" or "cFlagSRGB | cFlagGenMipsClamp | cFlagUASTC | cFlagThreaded | cPackUASTCLevelDefault".
// In ETC1S mode, the lower 8-bits are the ETC1S quality level which ranges from [1,255] (higher=better quality/larger files)
- // In UASTC mode, the lower 8-bits are the UASTC pack level (see cPackUASTCLevelFastest, etc.). Fastest/lowest quality is 0, so be sure to set it correctly.
+ // In UASTC mode, the lower 8-bits are the UASTC LDR/HDR pack level (see cPackUASTCLevelFastest, etc.). Fastest/lowest quality is 0, so be sure to set it correctly. Valid values are [0,4] for both LDR/HDR.
+ // In UASTC mode, be sure to set this, otherwise it defaults to 0 (fastest/lowest quality).
//
// uastc_rdo_quality: Float UASTC RDO quality level (0=no change, higher values lower quality but increase compressibility, initially try .5-1.5)
//
@@ -594,20 +637,36 @@ namespace basisu
cFlagUASTCRDO = 1 << 18, // use RDO postprocessing when generating UASTC files (must set uastc_rdo_quality to the quality scalar)
cFlagPrintStats = 1 << 19, // print image stats to stdout
- cFlagPrintStatus = 1 << 20 // print status to stdout
+ cFlagPrintStatus = 1 << 20, // print status to stdout
+
+ cFlagHDR = 1 << 21, // Force encoder into HDR mode, even if source image is LDR.
+ cFlagHDRLDRImageSRGBToLinearConversion = 1 << 22, // In HDR mode, convert LDR source images to linear before encoding.
+
+ cFlagDebugImages = 1 << 23 // enable status output
};
// This function accepts an array of source images.
// If more than one image is provided, it's assumed the images form a mipmap pyramid and automatic mipmap generation is disabled.
- // Returns a pointer to the compressed .basis or .ktx2 file data. *pSize is the size of the compressed data. The returned block must be freed using basis_free_data().
+ // Returns a pointer to the compressed .basis or .ktx2 file data. *pSize is the size of the compressed data.
+ // Important: The returned block MUST be manually freed using basis_free_data().
// basisu_encoder_init() MUST be called first!
+ // LDR version. To compress the LDR source image as HDR: Use the cFlagHDR flag.
void* basis_compress(
const basisu::vector<image> &source_images,
uint32_t flags_and_quality, float uastc_rdo_quality,
size_t* pSize,
image_stats* pStats = nullptr);
- // This function only accepts a single source image.
+ // HDR-only version.
+ // Important: The returned block MUST be manually freed using basis_free_data().
+ void* basis_compress(
+ const basisu::vector<imagef>& source_images_hdr,
+ uint32_t flags_and_quality,
+ size_t* pSize,
+ image_stats* pStats = nullptr);
+
+ // This function only accepts a single LDR source image. It's just a wrapper for basis_compress() above.
+ // Important: The returned block MUST be manually freed using basis_free_data().
void* basis_compress(
const uint8_t* pImageRGBA, uint32_t width, uint32_t height, uint32_t pitch_in_pixels,
uint32_t flags_and_quality, float uastc_rdo_quality,
@@ -615,6 +674,7 @@ namespace basisu
image_stats* pStats = nullptr);
// Frees the dynamically allocated file data returned by basis_compress().
+ // This MUST be called on the pointer returned by basis_compress() when you're done with it.
void basis_free_data(void* p);
// Runs a short benchmark using synthetic image data to time OpenCL encoding vs. CPU encoding, with multithreading enabled.
diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp
index e87dd636a2..fff98e8301 100644
--- a/thirdparty/basis_universal/encoder/basisu_enc.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp
@@ -1,5 +1,5 @@
// basisu_enc.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -21,10 +21,20 @@
#include "jpgd.h"
#include "pvpngreader.h"
#include "basisu_opencl.h"
+#include "basisu_astc_hdr_enc.h"
#include <vector>
+#ifndef TINYEXR_USE_ZFP
+#define TINYEXR_USE_ZFP (1)
+#endif
+#include <tinyexr.h>
+
+#ifndef MINIZ_HEADER_FILE_ONLY
#define MINIZ_HEADER_FILE_ONLY
+#endif
+#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
+#endif
#include "basisu_miniz.h"
#if defined(_WIN32)
@@ -165,14 +175,14 @@ namespace basisu
bool g_library_initialized;
std::mutex g_encoder_init_mutex;
-
+
// Encoder library initialization (just call once at startup)
- void basisu_encoder_init(bool use_opencl, bool opencl_force_serialization)
+ bool basisu_encoder_init(bool use_opencl, bool opencl_force_serialization)
{
std::lock_guard<std::mutex> lock(g_encoder_init_mutex);
if (g_library_initialized)
- return;
+ return true;
detect_sse41();
@@ -189,7 +199,11 @@ namespace basisu
interval_timer::init(); // make sure interval_timer globals are initialized from main thread to avoid TSAN reports
+ astc_hdr_enc_init();
+ basist::bc6h_enc_init();
+
g_library_initialized = true;
+ return true;
}
void basisu_encoder_deinit()
@@ -316,6 +330,24 @@ namespace basisu
init();
return ticks * g_timer_freq;
}
+
+ float linear_to_srgb(float l)
+ {
+ assert(l >= 0.0f && l <= 1.0f);
+ if (l < .0031308f)
+ return saturate(l * 12.92f);
+ else
+ return saturate(1.055f * powf(l, 1.0f / 2.4f) - .055f);
+ }
+
+ float srgb_to_linear(float s)
+ {
+ assert(s >= 0.0f && s <= 1.0f);
+ if (s < .04045f)
+ return saturate(s * (1.0f / 12.92f));
+ else
+ return saturate(powf((s + .055f) * (1.0f / 1.055f), 2.4f));
+ }
const uint32_t MAX_32BIT_ALLOC_SIZE = 250000000;
@@ -336,7 +368,7 @@ namespace basisu
if (sizeof(void *) == sizeof(uint32_t))
{
- if ((w * h * n_chans) > MAX_32BIT_ALLOC_SIZE)
+ if (((uint64_t)w * h * n_chans) > MAX_32BIT_ALLOC_SIZE)
{
error_printf("Image \"%s\" is too large (%ux%u) to process in a 32-bit build!\n", pFilename, w, h);
@@ -371,6 +403,11 @@ namespace basisu
return true;
}
+ bool load_qoi(const char* pFilename, image& img)
+ {
+ return false;
+ }
+
bool load_png(const uint8_t *pBuf, size_t buf_size, image &img, const char *pFilename)
{
interval_timer tm;
@@ -433,11 +470,178 @@ namespace basisu
return load_png(pFilename, img);
if (strcasecmp(pExt, "tga") == 0)
return load_tga(pFilename, img);
+ if (strcasecmp(pExt, "qoi") == 0)
+ return load_qoi(pFilename, img);
if ( (strcasecmp(pExt, "jpg") == 0) || (strcasecmp(pExt, "jfif") == 0) || (strcasecmp(pExt, "jpeg") == 0) )
return load_jpg(pFilename, img);
return false;
}
+
+ static void convert_ldr_to_hdr_image(imagef &img, const image &ldr_img, bool ldr_srgb_to_linear)
+ {
+ img.resize(ldr_img.get_width(), ldr_img.get_height());
+
+ for (uint32_t y = 0; y < ldr_img.get_height(); y++)
+ {
+ for (uint32_t x = 0; x < ldr_img.get_width(); x++)
+ {
+ const color_rgba& c = ldr_img(x, y);
+
+ vec4F& d = img(x, y);
+ if (ldr_srgb_to_linear)
+ {
+ // TODO: Multiply by 100-200 nits?
+ d[0] = srgb_to_linear(c[0] * (1.0f / 255.0f));
+ d[1] = srgb_to_linear(c[1] * (1.0f / 255.0f));
+ d[2] = srgb_to_linear(c[2] * (1.0f / 255.0f));
+ }
+ else
+ {
+ d[0] = c[0] * (1.0f / 255.0f);
+ d[1] = c[1] * (1.0f / 255.0f);
+ d[2] = c[2] * (1.0f / 255.0f);
+ }
+ d[3] = c[3] * (1.0f / 255.0f);
+ }
+ }
+ }
+
+ bool load_image_hdr(const void* pMem, size_t mem_size, imagef& img, uint32_t width, uint32_t height, hdr_image_type img_type, bool ldr_srgb_to_linear)
+ {
+ if ((!pMem) || (!mem_size))
+ {
+ assert(0);
+ return false;
+ }
+
+ switch (img_type)
+ {
+ case hdr_image_type::cHITRGBAHalfFloat:
+ {
+ if (mem_size != width * height * sizeof(basist::half_float) * 4)
+ {
+ assert(0);
+ return false;
+ }
+
+ if ((!width) || (!height))
+ {
+ assert(0);
+ return false;
+ }
+
+ const basist::half_float* pSrc_image_h = static_cast<const basist::half_float *>(pMem);
+
+ img.resize(width, height);
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ const basist::half_float* pSrc_pixel = &pSrc_image_h[x * 4];
+
+ vec4F& dst = img(x, y);
+ dst[0] = basist::half_to_float(pSrc_pixel[0]);
+ dst[1] = basist::half_to_float(pSrc_pixel[1]);
+ dst[2] = basist::half_to_float(pSrc_pixel[2]);
+ dst[3] = basist::half_to_float(pSrc_pixel[3]);
+ }
+
+ pSrc_image_h += (width * 4);
+ }
+
+ break;
+ }
+ case hdr_image_type::cHITRGBAFloat:
+ {
+ if (mem_size != width * height * sizeof(float) * 4)
+ {
+ assert(0);
+ return false;
+ }
+
+ if ((!width) || (!height))
+ {
+ assert(0);
+ return false;
+ }
+
+ img.resize(width, height);
+ memcpy(img.get_ptr(), pMem, width * height * sizeof(float) * 4);
+
+ break;
+ }
+ case hdr_image_type::cHITPNGImage:
+ {
+ image ldr_img;
+ if (!load_png(static_cast<const uint8_t *>(pMem), mem_size, ldr_img))
+ return false;
+
+ convert_ldr_to_hdr_image(img, ldr_img, ldr_srgb_to_linear);
+ break;
+ }
+ case hdr_image_type::cHITEXRImage:
+ {
+ if (!read_exr(pMem, mem_size, img))
+ return false;
+
+ break;
+ }
+ case hdr_image_type::cHITHDRImage:
+ {
+ uint8_vec buf(mem_size);
+ memcpy(buf.get_ptr(), pMem, mem_size);
+
+ rgbe_header_info hdr;
+ if (!read_rgbe(buf, img, hdr))
+ return false;
+
+ break;
+ }
+ default:
+ assert(0);
+ return false;
+ }
+
+ return true;
+ }
+
+ bool load_image_hdr(const char* pFilename, imagef& img, bool ldr_srgb_to_linear)
+ {
+ std::string ext(string_get_extension(std::string(pFilename)));
+
+ if (ext.length() == 0)
+ return false;
+
+ const char* pExt = ext.c_str();
+
+ if (strcasecmp(pExt, "hdr") == 0)
+ {
+ rgbe_header_info rgbe_info;
+ if (!read_rgbe(pFilename, img, rgbe_info))
+ return false;
+ return true;
+ }
+
+ if (strcasecmp(pExt, "exr") == 0)
+ {
+ int n_chans = 0;
+ if (!read_exr(pFilename, img, n_chans))
+ return false;
+ return true;
+ }
+
+ // Try loading image as LDR, then optionally convert to linear light.
+ {
+ image ldr_img;
+ if (!load_image(pFilename, ldr_img))
+ return false;
+
+ convert_ldr_to_hdr_image(img, ldr_img, ldr_srgb_to_linear);
+ }
+
+ return true;
+ }
bool save_png(const char* pFilename, const image &img, uint32_t image_save_flags, uint32_t grayscale_comp)
{
@@ -559,6 +763,45 @@ namespace basisu
return true;
}
+ bool read_file_to_data(const char* pFilename, void *pData, size_t len)
+ {
+ assert(pData && len);
+ if ((!pData) || (!len))
+ return false;
+
+ FILE* pFile = nullptr;
+#ifdef _WIN32
+ fopen_s(&pFile, pFilename, "rb");
+#else
+ pFile = fopen(pFilename, "rb");
+#endif
+ if (!pFile)
+ return false;
+
+ fseek(pFile, 0, SEEK_END);
+#ifdef _WIN32
+ int64_t filesize = _ftelli64(pFile);
+#else
+ int64_t filesize = ftello(pFile);
+#endif
+
+ if ((filesize < 0) || ((size_t)filesize < len))
+ {
+ fclose(pFile);
+ return false;
+ }
+ fseek(pFile, 0, SEEK_SET);
+
+ if (fread(pData, 1, (size_t)len, pFile) != (size_t)len)
+ {
+ fclose(pFile);
+ return false;
+ }
+
+ fclose(pFile);
+ return true;
+ }
+
bool write_data_to_file(const char* pFilename, const void* pData, size_t len)
{
FILE* pFile = nullptr;
@@ -581,25 +824,7 @@ namespace basisu
return fclose(pFile) != EOF;
}
-
- float linear_to_srgb(float l)
- {
- assert(l >= 0.0f && l <= 1.0f);
- if (l < .0031308f)
- return saturate(l * 12.92f);
- else
- return saturate(1.055f * powf(l, 1.0f/2.4f) - .055f);
- }
-
- float srgb_to_linear(float s)
- {
- assert(s >= 0.0f && s <= 1.0f);
- if (s < .04045f)
- return saturate(s * (1.0f/12.92f));
- else
- return saturate(powf((s + .055f) * (1.0f/1.055f), 2.4f));
- }
-
+
bool image_resample(const image &src, image &dst, bool srgb,
const char *pFilter, float filter_scale,
bool wrapping,
@@ -747,6 +972,121 @@ namespace basisu
return true;
}
+ bool image_resample(const imagef& src, imagef& dst,
+ const char* pFilter, float filter_scale,
+ bool wrapping,
+ uint32_t first_comp, uint32_t num_comps)
+ {
+ assert((first_comp + num_comps) <= 4);
+
+ const int cMaxComps = 4;
+
+ const uint32_t src_w = src.get_width(), src_h = src.get_height();
+ const uint32_t dst_w = dst.get_width(), dst_h = dst.get_height();
+
+ if (maximum(src_w, src_h) > BASISU_RESAMPLER_MAX_DIMENSION)
+ {
+ printf("Image is too large!\n");
+ return false;
+ }
+
+ if (!src_w || !src_h || !dst_w || !dst_h)
+ return false;
+
+ if ((num_comps < 1) || (num_comps > cMaxComps))
+ return false;
+
+ if ((minimum(dst_w, dst_h) < 1) || (maximum(dst_w, dst_h) > BASISU_RESAMPLER_MAX_DIMENSION))
+ {
+ printf("Image is too large!\n");
+ return false;
+ }
+
+ if ((src_w == dst_w) && (src_h == dst_h))
+ {
+ dst = src;
+ return true;
+ }
+
+ std::vector<float> samples[cMaxComps];
+ Resampler* resamplers[cMaxComps];
+
+ resamplers[0] = new Resampler(src_w, src_h, dst_w, dst_h,
+ wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP, 1.0f, 0.0f, // no clamping
+ pFilter, nullptr, nullptr, filter_scale, filter_scale, 0, 0);
+ samples[0].resize(src_w);
+
+ for (uint32_t i = 1; i < num_comps; ++i)
+ {
+ resamplers[i] = new Resampler(src_w, src_h, dst_w, dst_h,
+ wrapping ? Resampler::BOUNDARY_WRAP : Resampler::BOUNDARY_CLAMP, 1.0f, 0.0f, // no clamping
+ pFilter, resamplers[0]->get_clist_x(), resamplers[0]->get_clist_y(), filter_scale, filter_scale, 0, 0);
+ samples[i].resize(src_w);
+ }
+
+ uint32_t dst_y = 0;
+
+ for (uint32_t src_y = 0; src_y < src_h; ++src_y)
+ {
+ const vec4F* pSrc = &src(0, src_y);
+
+ // Put source lines into resampler(s)
+ for (uint32_t x = 0; x < src_w; ++x)
+ {
+ for (uint32_t c = 0; c < num_comps; ++c)
+ {
+ const uint32_t comp_index = first_comp + c;
+ const float v = (*pSrc)[comp_index];
+
+ samples[c][x] = v;
+ }
+
+ pSrc++;
+ }
+
+ for (uint32_t c = 0; c < num_comps; ++c)
+ {
+ if (!resamplers[c]->put_line(&samples[c][0]))
+ {
+ for (uint32_t i = 0; i < num_comps; i++)
+ delete resamplers[i];
+ return false;
+ }
+ }
+
+ // Now retrieve any output lines
+ for (;;)
+ {
+ uint32_t c;
+ for (c = 0; c < num_comps; ++c)
+ {
+ const uint32_t comp_index = first_comp + c;
+
+ const float* pOutput_samples = resamplers[c]->get_line();
+ if (!pOutput_samples)
+ break;
+
+ vec4F* pDst = &dst(0, dst_y);
+
+ for (uint32_t x = 0; x < dst_w; x++)
+ {
+ (*pDst)[comp_index] = pOutput_samples[x];
+ pDst++;
+ }
+ }
+ if (c < num_comps)
+ break;
+
+ ++dst_y;
+ }
+ }
+
+ for (uint32_t i = 0; i < num_comps; ++i)
+ delete resamplers[i];
+
+ return true;
+ }
+
void canonical_huffman_calculate_minimum_redundancy(sym_freq *A, int num_syms)
{
// See the paper "In-Place Calculation of Minimum Redundancy Codes" by Moffat and Katajainen
@@ -1312,11 +1652,13 @@ namespace basisu
uint32_t a = max_index / num_syms, b = max_index % num_syms;
+ const uint32_t ofs = m_entries_picked.size();
+
m_entries_picked.push_back(a);
m_entries_picked.push_back(b);
for (uint32_t i = 0; i < num_syms; i++)
- if ((i != b) && (i != a))
+ if ((i != m_entries_picked[ofs + 1]) && (i != m_entries_picked[ofs]))
m_entries_to_do.push_back(i);
for (uint32_t i = 0; i < m_entries_to_do.size(); i++)
@@ -1372,6 +1714,235 @@ namespace basisu
}
return which_side;
}
+
+ void image_metrics::calc(const imagef& a, const imagef& b, uint32_t first_chan, uint32_t total_chans, bool avg_comp_error, bool log)
+ {
+ assert((first_chan < 4U) && (first_chan + total_chans <= 4U));
+
+ const uint32_t width = basisu::minimum(a.get_width(), b.get_width());
+ const uint32_t height = basisu::minimum(a.get_height(), b.get_height());
+
+ double max_e = -1e+30f;
+ double sum = 0.0f, sum_sqr = 0.0f;
+
+ m_has_neg = false;
+ m_any_abnormal = false;
+ m_hf_mag_overflow = false;
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ const vec4F& ca = a(x, y), &cb = b(x, y);
+
+ if (total_chans)
+ {
+ for (uint32_t c = 0; c < total_chans; c++)
+ {
+ float fa = ca[first_chan + c], fb = cb[first_chan + c];
+
+ if ((fabs(fa) > basist::MAX_HALF_FLOAT) || (fabs(fb) > basist::MAX_HALF_FLOAT))
+ m_hf_mag_overflow = true;
+
+ if ((fa < 0.0f) || (fb < 0.0f))
+ m_has_neg = true;
+
+ if (std::isinf(fa) || std::isinf(fb) || std::isnan(fa) || std::isnan(fb))
+ m_any_abnormal = true;
+
+ const double delta = fabs(fa - fb);
+ max_e = basisu::maximum<double>(max_e, delta);
+
+ if (log)
+ {
+ double log2_delta = log2f(basisu::maximum(0.0f, fa) + 1.0f) - log2f(basisu::maximum(0.0f, fb) + 1.0f);
+
+ sum += fabs(log2_delta);
+ sum_sqr += log2_delta * log2_delta;
+ }
+ else
+ {
+ sum += fabs(delta);
+ sum_sqr += delta * delta;
+ }
+ }
+ }
+ else
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ float fa = ca[c], fb = cb[c];
+
+ if ((fabs(fa) > basist::MAX_HALF_FLOAT) || (fabs(fb) > basist::MAX_HALF_FLOAT))
+ m_hf_mag_overflow = true;
+
+ if ((fa < 0.0f) || (fb < 0.0f))
+ m_has_neg = true;
+
+ if (std::isinf(fa) || std::isinf(fb) || std::isnan(fa) || std::isnan(fb))
+ m_any_abnormal = true;
+ }
+
+ double ca_l = get_luminance(ca), cb_l = get_luminance(cb);
+
+ double delta = fabs(ca_l - cb_l);
+ max_e = basisu::maximum(max_e, delta);
+
+ if (log)
+ {
+ double log2_delta = log2(basisu::maximum<double>(0.0f, ca_l) + 1.0f) - log2(basisu::maximum<double>(0.0f, cb_l) + 1.0f);
+
+ sum += fabs(log2_delta);
+ sum_sqr += log2_delta * log2_delta;
+ }
+ else
+ {
+ sum += delta;
+ sum_sqr += delta * delta;
+ }
+ }
+ }
+ }
+
+ m_max = (double)(max_e);
+
+ double total_values = (double)width * (double)height;
+ if (avg_comp_error)
+ total_values *= (double)clamp<uint32_t>(total_chans, 1, 4);
+
+ m_mean = (float)(sum / total_values);
+ m_mean_squared = (float)(sum_sqr / total_values);
+ m_rms = (float)sqrt(sum_sqr / total_values);
+
+ const double max_val = 1.0f;
+ m_psnr = m_rms ? (float)clamp<double>(log10(max_val / m_rms) * 20.0f, 0.0f, 1000.0f) : 1000.0f;
+ }
+
+ void image_metrics::calc_half(const imagef& a, const imagef& b, uint32_t first_chan, uint32_t total_chans, bool avg_comp_error)
+ {
+ assert(total_chans);
+ assert((first_chan < 4U) && (first_chan + total_chans <= 4U));
+
+ const uint32_t width = basisu::minimum(a.get_width(), b.get_width());
+ const uint32_t height = basisu::minimum(a.get_height(), b.get_height());
+
+ m_has_neg = false;
+ m_hf_mag_overflow = false;
+ m_any_abnormal = false;
+
+ uint_vec hist(65536);
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ const vec4F& ca = a(x, y), &cb = b(x, y);
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((ca[i] < 0.0f) || (cb[i] < 0.0f))
+ m_has_neg = true;
+
+ if ((fabs(ca[i]) > basist::MAX_HALF_FLOAT) || (fabs(cb[i]) > basist::MAX_HALF_FLOAT))
+ m_hf_mag_overflow = true;
+
+ if (std::isnan(ca[i]) || std::isnan(cb[i]) || std::isinf(ca[i]) || std::isinf(cb[i]))
+ m_any_abnormal = true;
+ }
+
+ int cah[4] = { basist::float_to_half(ca[0]), basist::float_to_half(ca[1]), basist::float_to_half(ca[2]), basist::float_to_half(ca[3]) };
+ int cbh[4] = { basist::float_to_half(cb[0]), basist::float_to_half(cb[1]), basist::float_to_half(cb[2]), basist::float_to_half(cb[3]) };
+
+ for (uint32_t c = 0; c < total_chans; c++)
+ hist[iabs(cah[first_chan + c] - cbh[first_chan + c]) & 65535]++;
+
+ } // x
+ } // y
+
+ m_max = 0;
+ double sum = 0.0f, sum2 = 0.0f;
+ for (uint32_t i = 0; i < 65536; i++)
+ {
+ if (hist[i])
+ {
+ m_max = basisu::maximum<double>(m_max, (double)i);
+ double v = (double)i * (double)hist[i];
+ sum += v;
+ sum2 += (double)i * v;
+ }
+ }
+
+ double total_values = (double)width * (double)height;
+ if (avg_comp_error)
+ total_values *= (double)clamp<uint32_t>(total_chans, 1, 4);
+
+ const float max_val = 65535.0f;
+ m_mean = (float)clamp<double>(sum / total_values, 0.0f, max_val);
+ m_mean_squared = (float)clamp<double>(sum2 / total_values, 0.0f, max_val * max_val);
+ m_rms = (float)sqrt(m_mean_squared);
+ m_psnr = m_rms ? (float)clamp<double>(log10(max_val / m_rms) * 20.0f, 0.0f, 1000.0f) : 1000.0f;
+ }
+
+ // Alt. variant, same as calc_half(), for validation.
+ void image_metrics::calc_half2(const imagef& a, const imagef& b, uint32_t first_chan, uint32_t total_chans, bool avg_comp_error)
+ {
+ assert(total_chans);
+ assert((first_chan < 4U) && (first_chan + total_chans <= 4U));
+
+ const uint32_t width = basisu::minimum(a.get_width(), b.get_width());
+ const uint32_t height = basisu::minimum(a.get_height(), b.get_height());
+
+ m_has_neg = false;
+ m_hf_mag_overflow = false;
+ m_any_abnormal = false;
+
+ double sum = 0.0f, sum2 = 0.0f;
+ m_max = 0;
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ const vec4F& ca = a(x, y), & cb = b(x, y);
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((ca[i] < 0.0f) || (cb[i] < 0.0f))
+ m_has_neg = true;
+
+ if ((fabs(ca[i]) > basist::MAX_HALF_FLOAT) || (fabs(cb[i]) > basist::MAX_HALF_FLOAT))
+ m_hf_mag_overflow = true;
+
+ if (std::isnan(ca[i]) || std::isnan(cb[i]) || std::isinf(ca[i]) || std::isinf(cb[i]))
+ m_any_abnormal = true;
+ }
+
+ int cah[4] = { basist::float_to_half(ca[0]), basist::float_to_half(ca[1]), basist::float_to_half(ca[2]), basist::float_to_half(ca[3]) };
+ int cbh[4] = { basist::float_to_half(cb[0]), basist::float_to_half(cb[1]), basist::float_to_half(cb[2]), basist::float_to_half(cb[3]) };
+
+ for (uint32_t c = 0; c < total_chans; c++)
+ {
+ int diff = iabs(cah[first_chan + c] - cbh[first_chan + c]);
+ if (diff)
+ m_max = std::max<double>(m_max, (double)diff);
+
+ sum += diff;
+ sum2 += squarei(cah[first_chan + c] - cbh[first_chan + c]);
+ }
+
+ } // x
+ } // y
+
+ double total_values = (double)width * (double)height;
+ if (avg_comp_error)
+ total_values *= (double)clamp<uint32_t>(total_chans, 1, 4);
+
+ const float max_val = 65535.0f;
+ m_mean = (float)clamp<double>(sum / total_values, 0.0f, max_val);
+ m_mean_squared = (float)clamp<double>(sum2 / total_values, 0.0f, max_val * max_val);
+ m_rms = (float)sqrt(m_mean_squared);
+ m_psnr = m_rms ? (float)clamp<double>(log10(max_val / m_rms) * 20.0f, 0.0f, 1000.0f) : 1000.0f;
+ }
void image_metrics::calc(const image &a, const image &b, uint32_t first_chan, uint32_t total_chans, bool avg_comp_error, bool use_601_luma)
{
@@ -1383,6 +1954,10 @@ namespace basisu
double hist[256];
clear_obj(hist);
+ m_has_neg = false;
+ m_any_abnormal = false;
+ m_hf_mag_overflow = false;
+
for (uint32_t y = 0; y < height; y++)
{
for (uint32_t x = 0; x < width; x++)
@@ -1410,7 +1985,7 @@ namespace basisu
{
if (hist[i])
{
- m_max = basisu::maximum<float>(m_max, (float)i);
+ m_max = basisu::maximum<double>(m_max, (double)i);
double v = i * hist[i];
sum += v;
sum2 += i * v;
@@ -1922,7 +2497,7 @@ namespace basisu
} while (pixels_remaining);
- assert((pDst - &input_line_buf[0]) == width * tga_bytes_per_pixel);
+ assert((pDst - &input_line_buf[0]) == (int)(width * tga_bytes_per_pixel));
pLine_data = &input_line_buf[0];
}
@@ -2052,6 +2627,808 @@ namespace basisu
return read_tga(&filedata[0], (uint32_t)filedata.size(), width, height, n_chans);
}
+ static inline void hdr_convert(const color_rgba& rgbe, vec4F& c)
+ {
+ if (rgbe[3] != 0)
+ {
+ float scale = ldexp(1.0f, rgbe[3] - 128 - 8);
+ c.set((float)rgbe[0] * scale, (float)rgbe[1] * scale, (float)rgbe[2] * scale, 1.0f);
+ }
+ else
+ {
+ c.set(0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ }
+
+ bool string_begins_with(const std::string& str, const char* pPhrase)
+ {
+ const size_t str_len = str.size();
+
+ const size_t phrase_len = strlen(pPhrase);
+ assert(phrase_len);
+
+ if (str_len >= phrase_len)
+ {
+#ifdef _MSC_VER
+ if (_strnicmp(pPhrase, str.c_str(), phrase_len) == 0)
+#else
+ if (strncasecmp(pPhrase, str.c_str(), phrase_len) == 0)
+#endif
+ return true;
+ }
+
+ return false;
+ }
+
+ // Radiance RGBE (.HDR) image reading.
+ // This code tries to preserve the original logic in Radiance's ray/src/common/color.c code:
+ // https://www.radiance-online.org/cgi-bin/viewcvs.cgi/ray/src/common/color.c?revision=2.26&view=markup&sortby=log
+ // Also see: https://flipcode.com/archives/HDR_Image_Reader.shtml.
+ // https://github.com/LuminanceHDR/LuminanceHDR/blob/master/src/Libpfs/io/rgbereader.cpp.
+ // https://radsite.lbl.gov/radiance/refer/filefmts.pdf
+ // Buggy readers:
+ // stb_image.h: appears to be a clone of rgbe.c, but with goto's (doesn't support old format files, doesn't support mixture of RLE/non-RLE scanlines)
+ // http://www.graphics.cornell.edu/~bjw/rgbe.html - rgbe.c/h
+ // http://www.graphics.cornell.edu/online/formats/rgbe/ - rgbe.c/.h - buggy
+ bool read_rgbe(const uint8_vec &filedata, imagef& img, rgbe_header_info& hdr_info)
+ {
+ hdr_info.clear();
+
+ const uint32_t MAX_SUPPORTED_DIM = 65536;
+
+ if (filedata.size() < 4)
+ return false;
+
+ // stb_image.h checks for the string "#?RADIANCE" or "#?RGBE" in the header.
+ // The original Radiance header code doesn't care about the specific string.
+ // opencv's reader only checks for "#?", so that's what we're going to do.
+ if ((filedata[0] != '#') || (filedata[1] != '?'))
+ return false;
+
+ //uint32_t width = 0, height = 0;
+ bool is_rgbe = false;
+ size_t cur_ofs = 0;
+
+ // Parse the lines until we encounter a blank line.
+ std::string cur_line;
+ for (; ; )
+ {
+ if (cur_ofs >= filedata.size())
+ return false;
+
+ const uint32_t HEADER_TOO_BIG_SIZE = 4096;
+ if (cur_ofs >= HEADER_TOO_BIG_SIZE)
+ {
+ // Header seems too large - something is likely wrong. Return failure.
+ return false;
+ }
+
+ uint8_t c = filedata[cur_ofs++];
+
+ if (c == '\n')
+ {
+ if (!cur_line.size())
+ break;
+
+ if ((cur_line[0] == '#') && (!string_begins_with(cur_line, "#?")) && (!hdr_info.m_program.size()))
+ {
+ cur_line.erase(0, 1);
+ while (cur_line.size() && (cur_line[0] == ' '))
+ cur_line.erase(0, 1);
+
+ hdr_info.m_program = cur_line;
+ }
+ else if (string_begins_with(cur_line, "EXPOSURE=") && (cur_line.size() > 9))
+ {
+ hdr_info.m_exposure = atof(cur_line.c_str() + 9);
+ hdr_info.m_has_exposure = true;
+ }
+ else if (string_begins_with(cur_line, "GAMMA=") && (cur_line.size() > 6))
+ {
+ hdr_info.m_exposure = atof(cur_line.c_str() + 6);
+ hdr_info.m_has_gamma = true;
+ }
+ else if (cur_line == "FORMAT=32-bit_rle_rgbe")
+ {
+ is_rgbe = true;
+ }
+
+ cur_line.resize(0);
+ }
+ else
+ cur_line.push_back((char)c);
+ }
+
+ if (!is_rgbe)
+ return false;
+
+ // Assume and require the final line to have the image's dimensions. We're not supporting flipping.
+ for (; ; )
+ {
+ if (cur_ofs >= filedata.size())
+ return false;
+ uint8_t c = filedata[cur_ofs++];
+ if (c == '\n')
+ break;
+ cur_line.push_back((char)c);
+ }
+
+ int comp[2] = { 1, 0 }; // y, x (major, minor)
+ int dir[2] = { -1, 1 }; // -1, 1, (major, minor), for y -1=up
+ uint32_t major_dim = 0, minor_dim = 0;
+
+ // Parse the dimension string, normally it'll be "-Y # +X #" (major, minor), rarely it differs
+ for (uint32_t d = 0; d < 2; d++) // 0=major, 1=minor
+ {
+ const bool is_neg_x = (strncmp(&cur_line[0], "-X ", 3) == 0);
+ const bool is_pos_x = (strncmp(&cur_line[0], "+X ", 3) == 0);
+ const bool is_x = is_neg_x || is_pos_x;
+
+ const bool is_neg_y = (strncmp(&cur_line[0], "-Y ", 3) == 0);
+ const bool is_pos_y = (strncmp(&cur_line[0], "+Y ", 3) == 0);
+ const bool is_y = is_neg_y || is_pos_y;
+
+ if (cur_line.size() < 3)
+ return false;
+
+ if (!is_x && !is_y)
+ return false;
+
+ comp[d] = is_x ? 0 : 1;
+ dir[d] = (is_neg_x || is_neg_y) ? -1 : 1;
+
+ uint32_t& dim = d ? minor_dim : major_dim;
+
+ cur_line.erase(0, 3);
+
+ while (cur_line.size())
+ {
+ char c = cur_line[0];
+ if (c != ' ')
+ break;
+ cur_line.erase(0, 1);
+ }
+
+ bool has_digits = false;
+ while (cur_line.size())
+ {
+ char c = cur_line[0];
+ cur_line.erase(0, 1);
+
+ if (c == ' ')
+ break;
+
+ if ((c < '0') || (c > '9'))
+ return false;
+
+ const uint32_t prev_dim = dim;
+ dim = dim * 10 + (c - '0');
+ if (dim < prev_dim)
+ return false;
+
+ has_digits = true;
+ }
+ if (!has_digits)
+ return false;
+
+ if ((dim < 1) || (dim > MAX_SUPPORTED_DIM))
+ return false;
+ }
+
+ // temp image: width=minor, height=major
+ img.resize(minor_dim, major_dim);
+
+ std::vector<color_rgba> temp_scanline(minor_dim);
+
+ // Read the scanlines.
+ for (uint32_t y = 0; y < major_dim; y++)
+ {
+ vec4F* pDst = &img(0, y);
+
+ if ((filedata.size() - cur_ofs) < 4)
+ return false;
+
+ // Determine if the line uses the new or old format. See the logic in color.c.
+ bool old_decrunch = false;
+ if ((minor_dim < 8) || (minor_dim > 0x7FFF))
+ {
+ // Line is too short or long; must be old format.
+ old_decrunch = true;
+ }
+ else if (filedata[cur_ofs] != 2)
+ {
+ // R is not 2, must be old format
+ old_decrunch = true;
+ }
+ else
+ {
+ // c[0]/red is 2.Check GB and E for validity.
+ color_rgba c;
+ memcpy(&c, &filedata[cur_ofs], 4);
+
+ if ((c[1] != 2) || (c[2] & 0x80))
+ {
+ // G isn't 2, or the high bit of B is set which is impossible (image's > 0x7FFF pixels can't get here). Use old format.
+ old_decrunch = true;
+ }
+ else
+ {
+ // Check B and E. If this isn't the minor_dim in network order, something is wrong. The pixel would also be denormalized, and invalid.
+ uint32_t w = (c[2] << 8) | c[3];
+ if (w != minor_dim)
+ return false;
+
+ cur_ofs += 4;
+ }
+ }
+
+ if (old_decrunch)
+ {
+ uint32_t rshift = 0, x = 0;
+
+ while (x < minor_dim)
+ {
+ if ((filedata.size() - cur_ofs) < 4)
+ return false;
+
+ color_rgba c;
+ memcpy(&c, &filedata[cur_ofs], 4);
+ cur_ofs += 4;
+
+ if ((c[0] == 1) && (c[1] == 1) && (c[2] == 1))
+ {
+ // We'll allow RLE matches to cross scanlines, but not on the very first pixel.
+ if ((!x) && (!y))
+ return false;
+
+ const uint32_t run_len = c[3] << rshift;
+ const vec4F run_color(pDst[-1]);
+
+ if ((x + run_len) > minor_dim)
+ return false;
+
+ for (uint32_t i = 0; i < run_len; i++)
+ *pDst++ = run_color;
+
+ rshift += 8;
+ x += run_len;
+ }
+ else
+ {
+ rshift = 0;
+
+ hdr_convert(c, *pDst);
+ pDst++;
+ x++;
+ }
+ }
+ continue;
+ }
+
+ // New format
+ for (uint32_t s = 0; s < 4; s++)
+ {
+ uint32_t x_ofs = 0;
+ while (x_ofs < minor_dim)
+ {
+ uint32_t num_remaining = minor_dim - x_ofs;
+
+ if (cur_ofs >= filedata.size())
+ return false;
+
+ uint8_t count = filedata[cur_ofs++];
+ if (count > 128)
+ {
+ count -= 128;
+ if (count > num_remaining)
+ return false;
+
+ if (cur_ofs >= filedata.size())
+ return false;
+ const uint8_t val = filedata[cur_ofs++];
+
+ for (uint32_t i = 0; i < count; i++)
+ temp_scanline[x_ofs + i][s] = val;
+
+ x_ofs += count;
+ }
+ else
+ {
+ if ((!count) || (count > num_remaining))
+ return false;
+
+ for (uint32_t i = 0; i < count; i++)
+ {
+ if (cur_ofs >= filedata.size())
+ return false;
+ const uint8_t val = filedata[cur_ofs++];
+
+ temp_scanline[x_ofs + i][s] = val;
+ }
+
+ x_ofs += count;
+ }
+ } // while (x_ofs < minor_dim)
+ } // c
+
+ // Convert all the RGBE pixels to float now
+ for (uint32_t x = 0; x < minor_dim; x++, pDst++)
+ hdr_convert(temp_scanline[x], *pDst);
+
+ assert((pDst - &img(0, y)) == (int)minor_dim);
+
+ } // y
+
+ // at here:
+ // img(width,height)=image pixels as read from file, x=minor axis, y=major axis
+ // width=minor axis dimension
+ // height=major axis dimension
+ // in file, pixels are emitted in minor order, them major (so major=scanlines in the file)
+
+ imagef final_img;
+ if (comp[0] == 0) // if major axis is X
+ final_img.resize(major_dim, minor_dim);
+ else // major axis is Y, minor is X
+ final_img.resize(minor_dim, major_dim);
+
+ // TODO: optimize the identity case
+ for (uint32_t major_iter = 0; major_iter < major_dim; major_iter++)
+ {
+ for (uint32_t minor_iter = 0; minor_iter < minor_dim; minor_iter++)
+ {
+ const vec4F& p = img(minor_iter, major_iter);
+
+ uint32_t dst_x = 0, dst_y = 0;
+
+ // is the minor dim output x?
+ if (comp[1] == 0)
+ {
+ // minor axis is x, major is y
+
+ // is minor axis (which is output x) flipped?
+ if (dir[1] < 0)
+ dst_x = minor_dim - 1 - minor_iter;
+ else
+ dst_x = minor_iter;
+
+ // is major axis (which is output y) flipped? -1=down in raster order, 1=up
+ if (dir[0] < 0)
+ dst_y = major_iter;
+ else
+ dst_y = major_dim - 1 - major_iter;
+ }
+ else
+ {
+ // minor axis is output y, major is output x
+
+ // is minor axis (which is output y) flipped?
+ if (dir[1] < 0)
+ dst_y = minor_iter;
+ else
+ dst_y = minor_dim - 1 - minor_iter;
+
+ // is major axis (which is output x) flipped?
+ if (dir[0] < 0)
+ dst_x = major_dim - 1 - major_iter;
+ else
+ dst_x = major_iter;
+ }
+
+ final_img(dst_x, dst_y) = p;
+ }
+ }
+
+ final_img.swap(img);
+
+ return true;
+ }
+
+ bool read_rgbe(const char* pFilename, imagef& img, rgbe_header_info& hdr_info)
+ {
+ uint8_vec filedata;
+ if (!read_file_to_vec(pFilename, filedata))
+ return false;
+ return read_rgbe(filedata, img, hdr_info);
+ }
+
+ static uint8_vec& append_string(uint8_vec& buf, const char* pStr)
+ {
+ const size_t str_len = strlen(pStr);
+ if (!str_len)
+ return buf;
+
+ const size_t ofs = buf.size();
+ buf.resize(ofs + str_len);
+ memcpy(&buf[ofs], pStr, str_len);
+
+ return buf;
+ }
+
+ static uint8_vec& append_string(uint8_vec& buf, const std::string& str)
+ {
+ if (!str.size())
+ return buf;
+ return append_string(buf, str.c_str());
+ }
+
+ static inline void float2rgbe(color_rgba &rgbe, const vec4F &c)
+ {
+ const float red = c[0], green = c[1], blue = c[2];
+ assert(red >= 0.0f && green >= 0.0f && blue >= 0.0f);
+
+ const float max_v = basisu::maximumf(basisu::maximumf(red, green), blue);
+
+ if (max_v < 1e-32f)
+ rgbe.clear();
+ else
+ {
+ int e;
+ const float scale = frexp(max_v, &e) * 256.0f / max_v;
+ rgbe[0] = (uint8_t)(clamp<int>((int)(red * scale), 0, 255));
+ rgbe[1] = (uint8_t)(clamp<int>((int)(green * scale), 0, 255));
+ rgbe[2] = (uint8_t)(clamp<int>((int)(blue * scale), 0, 255));
+ rgbe[3] = (uint8_t)(e + 128);
+ }
+ }
+
+ const bool RGBE_FORCE_RAW = false;
+ const bool RGBE_FORCE_OLD_CRUNCH = false; // note must readers (particularly stb_image.h's) don't properly support this, when they should
+
+ bool write_rgbe(uint8_vec &file_data, imagef& img, rgbe_header_info& hdr_info)
+ {
+ if (!img.get_width() || !img.get_height())
+ return false;
+
+ const uint32_t width = img.get_width(), height = img.get_height();
+
+ file_data.resize(0);
+ file_data.reserve(1024 + img.get_width() * img.get_height() * 4);
+
+ append_string(file_data, "#?RADIANCE\n");
+
+ if (hdr_info.m_has_exposure)
+ append_string(file_data, string_format("EXPOSURE=%g\n", hdr_info.m_exposure));
+
+ if (hdr_info.m_has_gamma)
+ append_string(file_data, string_format("GAMMA=%g\n", hdr_info.m_gamma));
+
+ append_string(file_data, "FORMAT=32-bit_rle_rgbe\n\n");
+ append_string(file_data, string_format("-Y %u +X %u\n", height, width));
+
+ if (((width < 8) || (width > 0x7FFF)) || (RGBE_FORCE_RAW))
+ {
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ color_rgba rgbe;
+ float2rgbe(rgbe, img(x, y));
+ append_vector(file_data, (const uint8_t *)&rgbe, sizeof(rgbe));
+ }
+ }
+ }
+ else if (RGBE_FORCE_OLD_CRUNCH)
+ {
+ for (uint32_t y = 0; y < height; y++)
+ {
+ int prev_r = -1, prev_g = -1, prev_b = -1, prev_e = -1;
+ uint32_t cur_run_len = 0;
+
+ for (uint32_t x = 0; x < width; x++)
+ {
+ color_rgba rgbe;
+ float2rgbe(rgbe, img(x, y));
+
+ if ((rgbe[0] == prev_r) && (rgbe[1] == prev_g) && (rgbe[2] == prev_b) && (rgbe[3] == prev_e))
+ {
+ if (++cur_run_len == 255)
+ {
+ // this ensures rshift stays 0, it's lame but this path is only for testing readers
+ color_rgba f(1, 1, 1, cur_run_len - 1);
+ append_vector(file_data, (const uint8_t*)&f, sizeof(f));
+ append_vector(file_data, (const uint8_t*)&rgbe, sizeof(rgbe));
+ cur_run_len = 0;
+ }
+ }
+ else
+ {
+ if (cur_run_len > 0)
+ {
+ color_rgba f(1, 1, 1, cur_run_len);
+ append_vector(file_data, (const uint8_t*)&f, sizeof(f));
+
+ cur_run_len = 0;
+ }
+
+ append_vector(file_data, (const uint8_t*)&rgbe, sizeof(rgbe));
+
+ prev_r = rgbe[0];
+ prev_g = rgbe[1];
+ prev_b = rgbe[2];
+ prev_e = rgbe[3];
+ }
+ } // x
+
+ if (cur_run_len > 0)
+ {
+ color_rgba f(1, 1, 1, cur_run_len);
+ append_vector(file_data, (const uint8_t*)&f, sizeof(f));
+ }
+ } // y
+ }
+ else
+ {
+ uint8_vec temp[4];
+ for (uint32_t c = 0; c < 4; c++)
+ temp[c].resize(width);
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ color_rgba rgbe(2, 2, width >> 8, width & 0xFF);
+ append_vector(file_data, (const uint8_t*)&rgbe, sizeof(rgbe));
+
+ for (uint32_t x = 0; x < width; x++)
+ {
+ float2rgbe(rgbe, img(x, y));
+
+ for (uint32_t c = 0; c < 4; c++)
+ temp[c][x] = rgbe[c];
+ }
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ int raw_ofs = -1;
+
+ uint32_t x = 0;
+ while (x < width)
+ {
+ const uint32_t num_bytes_remaining = width - x;
+ const uint32_t max_run_len = basisu::minimum<uint32_t>(num_bytes_remaining, 127);
+ const uint8_t cur_byte = temp[c][x];
+
+ uint32_t run_len = 1;
+ while (run_len < max_run_len)
+ {
+ if (temp[c][x + run_len] != cur_byte)
+ break;
+ run_len++;
+ }
+
+ const uint32_t cost_to_keep_raw = ((raw_ofs != -1) ? 0 : 1) + run_len; // 0 or 1 bytes to start a raw run, then the repeated bytes issued as raw
+ const uint32_t cost_to_take_run = 2 + 1; // 2 bytes to issue the RLE, then 1 bytes to start whatever follows it (raw or RLE)
+
+ if ((run_len >= 3) && (cost_to_take_run < cost_to_keep_raw))
+ {
+ file_data.push_back((uint8_t)(128 + run_len));
+ file_data.push_back(cur_byte);
+
+ x += run_len;
+ raw_ofs = -1;
+ }
+ else
+ {
+ if (raw_ofs < 0)
+ {
+ raw_ofs = (int)file_data.size();
+ file_data.push_back(0);
+ }
+
+ if (++file_data[raw_ofs] == 128)
+ raw_ofs = -1;
+
+ file_data.push_back(cur_byte);
+
+ x++;
+ }
+ } // x
+
+ } // c
+ } // y
+ }
+
+ return true;
+ }
+
+ bool write_rgbe(const char* pFilename, imagef& img, rgbe_header_info& hdr_info)
+ {
+ uint8_vec file_data;
+ if (!write_rgbe(file_data, img, hdr_info))
+ return false;
+ return write_vec_to_file(pFilename, file_data);
+ }
+
+ bool read_exr(const char* pFilename, imagef& img, int& n_chans)
+ {
+ n_chans = 0;
+
+ int width = 0, height = 0;
+ float* out_rgba = nullptr;
+ const char* err = nullptr;
+
+ int status = LoadEXRWithLayer(&out_rgba, &width, &height, pFilename, nullptr, &err);
+ n_chans = 4;
+ if (status != 0)
+ {
+ error_printf("Failed loading .EXR image \"%s\"! (TinyEXR error: %s)\n", pFilename, err ? err : "?");
+ FreeEXRErrorMessage(err);
+ free(out_rgba);
+ return false;
+ }
+
+ const uint32_t MAX_SUPPORTED_DIM = 65536;
+ if ((width < 1) || (height < 1) || (width > (int)MAX_SUPPORTED_DIM) || (height > (int)MAX_SUPPORTED_DIM))
+ {
+ error_printf("Invalid dimensions of .EXR image \"%s\"!\n", pFilename);
+ free(out_rgba);
+ return false;
+ }
+
+ img.resize(width, height);
+
+ if (n_chans == 1)
+ {
+ const float* pSrc = out_rgba;
+ vec4F* pDst = img.get_ptr();
+
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < width; x++)
+ {
+ (*pDst)[0] = pSrc[0];
+ (*pDst)[1] = pSrc[1];
+ (*pDst)[2] = pSrc[2];
+ (*pDst)[3] = 1.0f;
+
+ pSrc += 4;
+ ++pDst;
+ }
+ }
+ }
+ else
+ {
+ memcpy(img.get_ptr(), out_rgba, sizeof(float) * 4 * img.get_total_pixels());
+ }
+
+ free(out_rgba);
+ return true;
+ }
+
+ bool read_exr(const void* pMem, size_t mem_size, imagef& img)
+ {
+ float* out_rgba = nullptr;
+ int width = 0, height = 0;
+ const char* pErr = nullptr;
+ int res = LoadEXRFromMemory(&out_rgba, &width, &height, (const uint8_t*)pMem, mem_size, &pErr);
+ if (res < 0)
+ {
+ error_printf("Failed loading .EXR image from memory! (TinyEXR error: %s)\n", pErr ? pErr : "?");
+ FreeEXRErrorMessage(pErr);
+ free(out_rgba);
+ return false;
+ }
+
+ img.resize(width, height);
+ memcpy(img.get_ptr(), out_rgba, width * height * sizeof(float) * 4);
+ free(out_rgba);
+
+ return true;
+ }
+
+ bool write_exr(const char* pFilename, imagef& img, uint32_t n_chans, uint32_t flags)
+ {
+ assert((n_chans == 1) || (n_chans == 3) || (n_chans == 4));
+
+ const bool linear_hint = (flags & WRITE_EXR_LINEAR_HINT) != 0,
+ store_float = (flags & WRITE_EXR_STORE_FLOATS) != 0,
+ no_compression = (flags & WRITE_EXR_NO_COMPRESSION) != 0;
+
+ const uint32_t width = img.get_width(), height = img.get_height();
+ assert(width && height);
+
+ if (!width || !height)
+ return false;
+
+ float_vec layers[4];
+ float* image_ptrs[4];
+ for (uint32_t c = 0; c < n_chans; c++)
+ {
+ layers[c].resize(width * height);
+ image_ptrs[c] = layers[c].get_ptr();
+ }
+
+ // ABGR
+ int chan_order[4] = { 3, 2, 1, 0 };
+
+ if (n_chans == 1)
+ {
+ // Y
+ chan_order[0] = 0;
+ }
+ else if (n_chans == 3)
+ {
+ // BGR
+ chan_order[0] = 2;
+ chan_order[1] = 1;
+ chan_order[2] = 0;
+ }
+ else if (n_chans != 4)
+ {
+ assert(0);
+ return false;
+ }
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ const vec4F& p = img(x, y);
+
+ for (uint32_t c = 0; c < n_chans; c++)
+ layers[c][x + y * width] = p[chan_order[c]];
+ } // x
+ } // y
+
+ EXRHeader header;
+ InitEXRHeader(&header);
+
+ EXRImage image;
+ InitEXRImage(&image);
+
+ image.num_channels = n_chans;
+ image.images = (unsigned char**)image_ptrs;
+ image.width = width;
+ image.height = height;
+
+ header.num_channels = n_chans;
+
+ header.channels = (EXRChannelInfo*)calloc(header.num_channels, sizeof(EXRChannelInfo));
+
+ // Must be (A)BGR order, since most of EXR viewers expect this channel order.
+ for (uint32_t i = 0; i < n_chans; i++)
+ {
+ char c = 'Y';
+ if (n_chans == 3)
+ c = "BGR"[i];
+ else if (n_chans == 4)
+ c = "ABGR"[i];
+
+ header.channels[i].name[0] = c;
+ header.channels[i].name[1] = '\0';
+
+ header.channels[i].p_linear = linear_hint;
+ }
+
+ header.pixel_types = (int*)calloc(header.num_channels, sizeof(int));
+ header.requested_pixel_types = (int*)calloc(header.num_channels, sizeof(int));
+
+ if (!no_compression)
+ header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
+
+ for (int i = 0; i < header.num_channels; i++)
+ {
+ // pixel type of input image
+ header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
+
+ // pixel type of output image to be stored in .EXR
+ header.requested_pixel_types[i] = store_float ? TINYEXR_PIXELTYPE_FLOAT : TINYEXR_PIXELTYPE_HALF;
+ }
+
+ const char* pErr_msg = nullptr;
+
+ int ret = SaveEXRImageToFile(&image, &header, pFilename, &pErr_msg);
+ if (ret != TINYEXR_SUCCESS)
+ {
+ error_printf("Save EXR err: %s\n", pErr_msg);
+ FreeEXRErrorMessage(pErr_msg);
+ }
+
+ free(header.channels);
+ free(header.pixel_types);
+ free(header.requested_pixel_types);
+
+ return (ret == TINYEXR_SUCCESS);
+ }
+
void image::debug_text(uint32_t x_ofs, uint32_t y_ofs, uint32_t scale_x, uint32_t scale_y, const color_rgba& fg, const color_rgba* pBG, bool alpha_only, const char* pFmt, ...)
{
char buf[2048];
@@ -2103,5 +3480,206 @@ namespace basisu
}
}
}
-
+
+ // Very basic global Reinhard tone mapping, output converted to sRGB with no dithering, alpha is carried through unchanged.
+ // Only used for debugging/development.
+ void tonemap_image_reinhard(image &ldr_img, const imagef &hdr_img, float exposure)
+ {
+ uint32_t width = hdr_img.get_width(), height = hdr_img.get_height();
+
+ ldr_img.resize(width, height);
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ vec4F c(hdr_img(x, y));
+
+ for (uint32_t t = 0; t < 3; t++)
+ {
+ if (c[t] <= 0.0f)
+ {
+ c[t] = 0.0f;
+ }
+ else
+ {
+ c[t] *= exposure;
+ c[t] = c[t] / (1.0f + c[t]);
+ }
+ }
+
+ c.clamp(0.0f, 1.0f);
+
+ c[0] = linear_to_srgb(c[0]) * 255.0f;
+ c[1] = linear_to_srgb(c[1]) * 255.0f;
+ c[2] = linear_to_srgb(c[2]) * 255.0f;
+ c[3] = c[3] * 255.0f;
+
+ color_rgba& o = ldr_img(x, y);
+
+ o[0] = (uint8_t)std::round(c[0]);
+ o[1] = (uint8_t)std::round(c[1]);
+ o[2] = (uint8_t)std::round(c[2]);
+ o[3] = (uint8_t)std::round(c[3]);
+ }
+ }
+ }
+
+ bool tonemap_image_compressive(image& dst_img, const imagef& hdr_test_img)
+ {
+ const uint32_t width = hdr_test_img.get_width();
+ const uint32_t height = hdr_test_img.get_height();
+
+ uint16_vec orig_half_img(width * 3 * height);
+ uint16_vec half_img(width * 3 * height);
+
+ int max_shift = 32;
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ const vec4F& p = hdr_test_img(x, y);
+
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ if (p[i] < 0.0f)
+ return false;
+ if (p[i] > basist::MAX_HALF_FLOAT)
+ return false;
+
+ uint32_t h = basist::float_to_half(p[i]);
+ //uint32_t orig_h = h;
+
+ orig_half_img[(x + y * width) * 3 + i] = (uint16_t)h;
+
+ // Rotate sign bit into LSB
+ //h = rot_left16((uint16_t)h, 1);
+ //assert(rot_right16((uint16_t)h, 1) == orig_h);
+ h <<= 1;
+
+ half_img[(x + y * width) * 3 + i] = (uint16_t)h;
+
+ // Determine # of leading zero bits, ignoring the sign bit
+ if (h)
+ {
+ int lz = clz(h) - 16;
+ assert(lz >= 0 && lz <= 16);
+
+ assert((h << lz) <= 0xFFFF);
+
+ max_shift = basisu::minimum<int>(max_shift, lz);
+ }
+ } // i
+ } // x
+ } // y
+
+ //printf("tonemap_image_compressive: Max leading zeros: %i\n", max_shift);
+
+ uint32_t high_hist[256];
+ clear_obj(high_hist);
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ uint16_t& hf = half_img[(x + y * width) * 3 + i];
+
+ assert(((uint32_t)hf << max_shift) <= 65535);
+
+ hf <<= max_shift;
+
+ uint32_t h = (uint8_t)(hf >> 8);
+ high_hist[h]++;
+ }
+ } // x
+ } // y
+
+ uint32_t total_vals_used = 0;
+ int remap_old_to_new[256];
+ for (uint32_t i = 0; i < 256; i++)
+ remap_old_to_new[i] = -1;
+
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ if (high_hist[i] != 0)
+ {
+ remap_old_to_new[i] = total_vals_used;
+ total_vals_used++;
+ }
+ }
+
+ assert(total_vals_used >= 1);
+
+ //printf("tonemap_image_compressive: Total used high byte values: %u, unused: %u\n", total_vals_used, 256 - total_vals_used);
+
+ bool val_used[256];
+ clear_obj(val_used);
+
+ int remap_new_to_old[256];
+ for (uint32_t i = 0; i < 256; i++)
+ remap_new_to_old[i] = -1;
+ BASISU_NOTE_UNUSED(remap_new_to_old);
+
+ int prev_c = -1;
+ BASISU_NOTE_UNUSED(prev_c);
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ if (remap_old_to_new[i] >= 0)
+ {
+ int c;
+ if (total_vals_used <= 1)
+ c = remap_old_to_new[i];
+ else
+ {
+ c = (remap_old_to_new[i] * 255 + ((total_vals_used - 1) / 2)) / (total_vals_used - 1);
+
+ assert(c > prev_c);
+ }
+
+ assert(!val_used[c]);
+
+ remap_new_to_old[c] = i;
+
+ remap_old_to_new[i] = c;
+ prev_c = c;
+
+ //printf("%u ", c);
+
+ val_used[c] = true;
+ }
+ } // i
+ //printf("\n");
+
+ dst_img.resize(width, height);
+
+ for (uint32_t y = 0; y < height; y++)
+ {
+ for (uint32_t x = 0; x < width; x++)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ uint16_t& v16 = half_img[(x + y * width) * 3 + c];
+
+ uint32_t hb = v16 >> 8;
+ //uint32_t lb = v16 & 0xFF;
+
+ assert(remap_old_to_new[hb] != -1);
+ assert(remap_old_to_new[hb] <= 255);
+ assert(remap_new_to_old[remap_old_to_new[hb]] == (int)hb);
+
+ hb = remap_old_to_new[hb];
+
+ //v16 = (uint16_t)((hb << 8) | lb);
+
+ dst_img(x, y)[c] = (uint8_t)hb;
+ }
+ } // x
+ } // y
+
+ return true;
+ }
+
} // namespace basisu
diff --git a/thirdparty/basis_universal/encoder/basisu_enc.h b/thirdparty/basis_universal/encoder/basisu_enc.h
index 0efeaa461f..780605e7b8 100644
--- a/thirdparty/basis_universal/encoder/basisu_enc.h
+++ b/thirdparty/basis_universal/encoder/basisu_enc.h
@@ -1,5 +1,5 @@
// basisu_enc.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -48,7 +48,8 @@ namespace basisu
// Encoder library initialization.
// This function MUST be called before encoding anything!
- void basisu_encoder_init(bool use_opencl = false, bool opencl_force_serialization = false);
+ // Returns false if library initialization fails.
+ bool basisu_encoder_init(bool use_opencl = false, bool opencl_force_serialization = false);
void basisu_encoder_deinit();
// basisu_kernels_sse.cpp - will be a no-op and g_cpu_supports_sse41 will always be false unless compiled with BASISU_SUPPORT_SSE=1
@@ -70,6 +71,18 @@ namespace basisu
return (uint8_t)((i & 0xFFFFFF00U) ? (~(i >> 31)) : i);
}
+ inline int left_shift32(int val, int shift)
+ {
+ assert((shift >= 0) && (shift < 32));
+ return static_cast<int>(static_cast<uint32_t>(val) << shift);
+ }
+
+ inline uint32_t left_shift32(uint32_t val, int shift)
+ {
+ assert((shift >= 0) && (shift < 32));
+ return val << shift;
+ }
+
inline int32_t clampi(int32_t value, int32_t low, int32_t high)
{
if (value < low)
@@ -130,6 +143,31 @@ namespace basisu
return bits;
}
+
+ // Open interval
+ inline int bounds_check(int v, int l, int h) { (void)v; (void)l; (void)h; assert(v >= l && v < h); return v; }
+ inline uint32_t bounds_check(uint32_t v, uint32_t l, uint32_t h) { (void)v; (void)l; (void)h; assert(v >= l && v < h); return v; }
+
+ // Closed interval
+ inline int bounds_check_incl(int v, int l, int h) { (void)v; (void)l; (void)h; assert(v >= l && v <= h); return v; }
+ inline uint32_t bounds_check_incl(uint32_t v, uint32_t l, uint32_t h) { (void)v; (void)l; (void)h; assert(v >= l && v <= h); return v; }
+
+ inline uint32_t clz(uint32_t x)
+ {
+ if (!x)
+ return 32;
+
+ uint32_t n = 0;
+ while ((x & 0x80000000) == 0)
+ {
+ x <<= 1u;
+ n++;
+ }
+
+ return n;
+ }
+
+ bool string_begins_with(const std::string& str, const char* pPhrase);
// Hashing
@@ -268,6 +306,7 @@ namespace basisu
public:
enum { num_elements = N };
+ typedef T scalar_type;
inline vec() { }
inline vec(eZero) { set_zero(); }
@@ -291,6 +330,7 @@ namespace basisu
inline bool operator<(const vec &rhs) const { for (uint32_t i = 0; i < N; i++) { if (m_v[i] < rhs.m_v[i]) return true; else if (m_v[i] != rhs.m_v[i]) return false; } return false; }
inline void set_zero() { for (uint32_t i = 0; i < N; i++) m_v[i] = 0; }
+ inline void clear() { set_zero(); }
template <uint32_t OtherN, typename OtherT>
inline vec &set(const vec<OtherN, OtherT> &other)
@@ -391,7 +431,7 @@ namespace basisu
inline T distance(const vec &other) const { return static_cast<T>(sqrt(squared_distance(other))); }
inline double distance_d(const vec& other) const { return sqrt(squared_distance_d(other)); }
- inline vec &normalize_in_place() { T len = length(); if (len != 0.0f) *this *= (1.0f / len); return *this; }
+ inline vec &normalize_in_place() { T len = length(); if (len != 0.0f) *this *= (1.0f / len); return *this; }
inline vec &clamp(T l, T h)
{
@@ -722,7 +762,7 @@ namespace basisu
void job_thread(uint32_t index);
};
- // Simple 32-bit color class
+ // Simple 64-bit color class
class color_rgba_i16
{
@@ -1116,7 +1156,9 @@ namespace basisu
{
std::string result(s);
for (size_t i = 0; i < result.size(); i++)
- result[i] = (char)tolower((int)result[i]);
+ {
+ result[i] = (char)tolower((uint8_t)(result[i]));
+ }
return result;
}
@@ -1408,7 +1450,7 @@ namespace basisu
size_t get_total_training_vecs() const { return m_training_vecs.size(); }
const array_of_weighted_training_vecs &get_training_vecs() const { return m_training_vecs; }
- array_of_weighted_training_vecs &get_training_vecs() { return m_training_vecs; }
+ array_of_weighted_training_vecs &get_training_vecs() { return m_training_vecs; }
void retrieve(basisu::vector< basisu::vector<uint32_t> > &codebook) const
{
@@ -1437,36 +1479,36 @@ namespace basisu
}
void retrieve(uint32_t max_clusters, basisu::vector<uint_vec> &codebook) const
- {
+ {
uint_vec node_stack;
- node_stack.reserve(512);
+ node_stack.reserve(512);
- codebook.resize(0);
- codebook.reserve(max_clusters);
+ codebook.resize(0);
+ codebook.reserve(max_clusters);
- uint32_t node_index = 0;
+ uint32_t node_index = 0;
- while (true)
- {
- const tsvq_node& cur = m_nodes[node_index];
+ while (true)
+ {
+ const tsvq_node& cur = m_nodes[node_index];
- if (cur.is_leaf() || ((2 + cur.m_codebook_index) > (int)max_clusters))
- {
- codebook.resize(codebook.size() + 1);
- codebook.back() = cur.m_training_vecs;
+ if (cur.is_leaf() || ((2 + cur.m_codebook_index) > (int)max_clusters))
+ {
+ codebook.resize(codebook.size() + 1);
+ codebook.back() = cur.m_training_vecs;
- if (node_stack.empty())
- break;
+ if (node_stack.empty())
+ break;
- node_index = node_stack.back();
- node_stack.pop_back();
- continue;
- }
+ node_index = node_stack.back();
+ node_stack.pop_back();
+ continue;
+ }
- node_stack.push_back(cur.m_right_index);
- node_index = cur.m_left_index;
- }
- }
+ node_stack.push_back(cur.m_right_index);
+ node_index = cur.m_left_index;
+ }
+ }
bool generate(uint32_t max_size)
{
@@ -2319,6 +2361,14 @@ namespace basisu
m_total_bits = 0;
}
+ inline void restart()
+ {
+ m_bytes.resize(0);
+ m_bit_buffer = 0;
+ m_bit_buffer_size = 0;
+ m_total_bits = 0;
+ }
+
inline const uint8_vec &get_bytes() const { return m_bytes; }
inline uint64_t get_total_bits() const { return m_total_bits; }
@@ -2920,11 +2970,11 @@ namespace basisu
inline const color_rgba *get_ptr() const { return &m_pixels[0]; }
inline color_rgba *get_ptr() { return &m_pixels[0]; }
- bool has_alpha() const
+ bool has_alpha(uint32_t channel = 3) const
{
for (uint32_t y = 0; y < m_height; ++y)
for (uint32_t x = 0; x < m_width; ++x)
- if ((*this)(x, y).a < 255)
+ if ((*this)(x, y)[channel] < 255)
return true;
return false;
@@ -3130,6 +3180,31 @@ namespace basisu
return *this;
}
+ imagef& crop_dup_borders(uint32_t w, uint32_t h)
+ {
+ const uint32_t orig_w = m_width, orig_h = m_height;
+
+ crop(w, h);
+
+ if (orig_w && orig_h)
+ {
+ if (m_width > orig_w)
+ {
+ for (uint32_t x = orig_w; x < m_width; x++)
+ for (uint32_t y = 0; y < m_height; y++)
+ set_clipped(x, y, get_clamped(minimum(x, orig_w - 1U), minimum(y, orig_h - 1U)));
+ }
+
+ if (m_height > orig_h)
+ {
+ for (uint32_t y = orig_h; y < m_height; y++)
+ for (uint32_t x = 0; x < m_width; x++)
+ set_clipped(x, y, get_clamped(minimum(x, orig_w - 1U), minimum(y, orig_h - 1U)));
+ }
+ }
+ return *this;
+ }
+
inline const vec4F &operator() (uint32_t x, uint32_t y) const { assert(x < m_width && y < m_height); return m_pixels[x + y * m_pitch]; }
inline vec4F &operator() (uint32_t x, uint32_t y) { assert(x < m_width && y < m_height); return m_pixels[x + y * m_pitch]; }
@@ -3213,19 +3288,128 @@ namespace basisu
inline const vec4F *get_ptr() const { return &m_pixels[0]; }
inline vec4F *get_ptr() { return &m_pixels[0]; }
+
+ bool clean_astc_hdr_pixels(float highest_mag)
+ {
+ bool status = true;
+ bool nan_msg = false;
+ bool inf_msg = false;
+ bool neg_zero_msg = false;
+ bool neg_msg = false;
+ bool clamp_msg = false;
+
+ for (uint32_t iy = 0; iy < m_height; iy++)
+ {
+ for (uint32_t ix = 0; ix < m_width; ix++)
+ {
+ vec4F& c = (*this)(ix, iy);
+
+ for (uint32_t s = 0; s < 4; s++)
+ {
+ float &p = c[s];
+ union { float f; uint32_t u; } x; x.f = p;
+
+ if ((std::isnan(p)) || (std::isinf(p)) || (x.u == 0x80000000))
+ {
+ if (std::isnan(p))
+ {
+ if (!nan_msg)
+ {
+ fprintf(stderr, "One or more pixels was NaN, setting to 0.\n");
+ nan_msg = true;
+ }
+ }
+
+ if (std::isinf(p))
+ {
+ if (!inf_msg)
+ {
+ fprintf(stderr, "One or more pixels was INF, setting to 0.\n");
+ inf_msg = true;
+ }
+ }
+
+ if (x.u == 0x80000000)
+ {
+ if (!neg_zero_msg)
+ {
+ fprintf(stderr, "One or more pixels was -0, setting them to 0.\n");
+ neg_zero_msg = true;
+ }
+ }
+
+ p = 0.0f;
+ status = false;
+ }
+ else
+ {
+ //const float o = p;
+ if (p < 0.0f)
+ {
+ p = 0.0f;
+
+ if (!neg_msg)
+ {
+ fprintf(stderr, "One or more pixels was negative -- setting these pixel components to 0 because ASTC HDR doesn't support signed values.\n");
+ neg_msg = true;
+ }
+
+ status = false;
+ }
+
+ if (p > highest_mag)
+ {
+ p = highest_mag;
+
+ if (!clamp_msg)
+ {
+ fprintf(stderr, "One or more pixels had to be clamped to %f.\n", highest_mag);
+ clamp_msg = true;
+ }
+
+ status = false;
+ }
+ }
+ }
+ }
+ }
+
+ return status;
+ }
+
+ imagef& flip_y()
+ {
+ for (uint32_t y = 0; y < m_height / 2; ++y)
+ for (uint32_t x = 0; x < m_width; ++x)
+ std::swap((*this)(x, y), (*this)(x, m_height - 1 - y));
+
+ return *this;
+ }
private:
uint32_t m_width, m_height, m_pitch; // all in pixels
vec4F_vec m_pixels;
};
+ // REC 709 coefficients
+ const float REC_709_R = 0.212656f, REC_709_G = 0.715158f, REC_709_B = 0.072186f;
+
+ inline float get_luminance(const vec4F &c)
+ {
+ return c[0] * REC_709_R + c[1] * REC_709_G + c[2] * REC_709_B;
+ }
+
+ float linear_to_srgb(float l);
+ float srgb_to_linear(float s);
+
// Image metrics
class image_metrics
{
public:
// TODO: Add ssim
- float m_max, m_mean, m_mean_squared, m_rms, m_psnr, m_ssim;
+ double m_max, m_mean, m_mean_squared, m_rms, m_psnr, m_ssim;
+ bool m_has_neg, m_hf_mag_overflow, m_any_abnormal;
image_metrics()
{
@@ -3240,10 +3424,17 @@ namespace basisu
m_rms = 0;
m_psnr = 0;
m_ssim = 0;
+ m_has_neg = false;
+ m_hf_mag_overflow = false;
+ m_any_abnormal = false;
}
- void print(const char *pPrefix = nullptr) { printf("%sMax: %3.0f Mean: %3.3f RMS: %3.3f PSNR: %2.3f dB\n", pPrefix ? pPrefix : "", m_max, m_mean, m_rms, m_psnr); }
+ void print(const char *pPrefix = nullptr) { printf("%sMax: %3.3f Mean: %3.3f RMS: %3.3f PSNR: %2.3f dB\n", pPrefix ? pPrefix : "", m_max, m_mean, m_rms, m_psnr); }
+ void print_hp(const char* pPrefix = nullptr) { printf("%sMax: %3.6f Mean: %3.6f RMS: %3.6f PSNR: %2.6f dB, Any Neg: %u, Half float overflow: %u, Any NaN/Inf: %u\n", pPrefix ? pPrefix : "", m_max, m_mean, m_rms, m_psnr, m_has_neg, m_hf_mag_overflow, m_any_abnormal); }
+ void calc(const imagef& a, const imagef& b, uint32_t first_chan = 0, uint32_t total_chans = 0, bool avg_comp_error = true, bool log = false);
+ void calc_half(const imagef& a, const imagef& b, uint32_t first_chan, uint32_t total_chans, bool avg_comp_error);
+ void calc_half2(const imagef& a, const imagef& b, uint32_t first_chan, uint32_t total_chans, bool avg_comp_error);
void calc(const image &a, const image &b, uint32_t first_chan = 0, uint32_t total_chans = 0, bool avg_comp_error = true, bool use_601_luma = false);
};
@@ -3256,6 +3447,8 @@ namespace basisu
bool load_tga(const char* pFilename, image& img);
inline bool load_tga(const std::string &filename, image &img) { return load_tga(filename.c_str(), img); }
+ bool load_qoi(const char* pFilename, image& img);
+
bool load_jpg(const char *pFilename, image& img);
inline bool load_jpg(const std::string &filename, image &img) { return load_jpg(filename.c_str(), img); }
@@ -3263,9 +3456,64 @@ namespace basisu
bool load_image(const char* pFilename, image& img);
inline bool load_image(const std::string &filename, image &img) { return load_image(filename.c_str(), img); }
+ // Supports .HDR and most (but not all) .EXR's (see TinyEXR).
+ bool load_image_hdr(const char* pFilename, imagef& img, bool ldr_srgb_to_linear = true);
+ inline bool load_image_hdr(const std::string& filename, imagef& img, bool ldr_srgb_to_linear = true) { return load_image_hdr(filename.c_str(), img, ldr_srgb_to_linear); }
+
+ enum class hdr_image_type
+ {
+ cHITRGBAHalfFloat = 0,
+ cHITRGBAFloat = 1,
+ cHITPNGImage = 2,
+ cHITEXRImage = 3,
+ cHITHDRImage = 4
+ };
+
+ bool load_image_hdr(const void* pMem, size_t mem_size, imagef& img, uint32_t width, uint32_t height, hdr_image_type img_type, bool ldr_srgb_to_linear);
+
uint8_t *read_tga(const uint8_t *pBuf, uint32_t buf_size, int &width, int &height, int &n_chans);
uint8_t *read_tga(const char *pFilename, int &width, int &height, int &n_chans);
+ struct rgbe_header_info
+ {
+ std::string m_program;
+
+ // Note no validation is done, either gamma or exposure may be 0.
+ double m_gamma;
+ bool m_has_gamma;
+
+ double m_exposure; // watts/steradian/m^2.
+ bool m_has_exposure;
+
+ void clear()
+ {
+ m_program.clear();
+ m_gamma = 1.0f;
+ m_has_gamma = false;
+ m_exposure = 1.0f;
+ m_has_exposure = false;
+ }
+ };
+
+ bool read_rgbe(const uint8_vec& filedata, imagef& img, rgbe_header_info& hdr_info);
+ bool read_rgbe(const char* pFilename, imagef& img, rgbe_header_info &hdr_info);
+
+ bool write_rgbe(uint8_vec& file_data, imagef& img, rgbe_header_info& hdr_info);
+ bool write_rgbe(const char* pFilename, imagef& img, rgbe_header_info& hdr_info);
+
+ bool read_exr(const char* pFilename, imagef& img, int& n_chans);
+ bool read_exr(const void* pMem, size_t mem_size, imagef& img);
+
+ enum
+ {
+ WRITE_EXR_LINEAR_HINT = 1, // hint for lossy comp. methods: exr_perceptual_treatment_t, logarithmic or linear, defaults to logarithmic
+ WRITE_EXR_STORE_FLOATS = 2, // use 32-bit floats, otherwise it uses half floats
+ WRITE_EXR_NO_COMPRESSION = 4 // no compression, otherwise it uses ZIP compression (16 scanlines per block)
+ };
+
+ // Supports 1 (Y), 3 (RGB), or 4 (RGBA) channel images.
+ bool write_exr(const char* pFilename, imagef& img, uint32_t n_chans, uint32_t flags);
+
enum
{
cImageSaveGrayscale = 1,
@@ -3276,19 +3524,22 @@ namespace basisu
inline bool save_png(const std::string &filename, const image &img, uint32_t image_save_flags = 0, uint32_t grayscale_comp = 0) { return save_png(filename.c_str(), img, image_save_flags, grayscale_comp); }
bool read_file_to_vec(const char* pFilename, uint8_vec& data);
-
+ bool read_file_to_data(const char* pFilename, void *pData, size_t len);
+
bool write_data_to_file(const char* pFilename, const void* pData, size_t len);
inline bool write_vec_to_file(const char* pFilename, const uint8_vec& v) { return v.size() ? write_data_to_file(pFilename, &v[0], v.size()) : write_data_to_file(pFilename, "", 0); }
-
- float linear_to_srgb(float l);
- float srgb_to_linear(float s);
-
+
bool image_resample(const image &src, image &dst, bool srgb = false,
const char *pFilter = "lanczos4", float filter_scale = 1.0f,
bool wrapping = false,
uint32_t first_comp = 0, uint32_t num_comps = 4);
+ bool image_resample(const imagef& src, imagef& dst,
+ const char* pFilter = "lanczos4", float filter_scale = 1.0f,
+ bool wrapping = false,
+ uint32_t first_comp = 0, uint32_t num_comps = 4);
+
// Timing
typedef uint64_t timer_ticks;
@@ -3319,6 +3570,8 @@ namespace basisu
bool m_started, m_stopped;
};
+ inline double get_interval_timer() { return interval_timer::ticks_to_secs(interval_timer::get_ticks()); }
+
// 2D array
template<typename T>
@@ -3372,8 +3625,8 @@ namespace basisu
inline const T &operator[] (uint32_t i) const { return m_values[i]; }
inline T &operator[] (uint32_t i) { return m_values[i]; }
- inline const T &at_clamped(int x, int y) const { return (*this)(clamp<int>(x, 0, m_width), clamp<int>(y, 0, m_height)); }
- inline T &at_clamped(int x, int y) { return (*this)(clamp<int>(x, 0, m_width), clamp<int>(y, 0, m_height)); }
+ inline const T &at_clamped(int x, int y) const { return (*this)(clamp<int>(x, 0, m_width - 1), clamp<int>(y, 0, m_height - 1)); }
+ inline T &at_clamped(int x, int y) { return (*this)(clamp<int>(x, 0, m_width - 1), clamp<int>(y, 0, m_height - 1)); }
void clear()
{
@@ -3450,7 +3703,327 @@ namespace basisu
}
};
typedef basisu::vector<pixel_block> pixel_block_vec;
-
+
+ struct pixel_block_hdr
+ {
+ vec4F m_pixels[cPixelBlockHeight][cPixelBlockWidth]; // [y][x]
+
+ inline const vec4F& operator() (uint32_t x, uint32_t y) const { assert((x < cPixelBlockWidth) && (y < cPixelBlockHeight)); return m_pixels[y][x]; }
+ inline vec4F& operator() (uint32_t x, uint32_t y) { assert((x < cPixelBlockWidth) && (y < cPixelBlockHeight)); return m_pixels[y][x]; }
+
+ inline const vec4F* get_ptr() const { return &m_pixels[0][0]; }
+ inline vec4F* get_ptr() { return &m_pixels[0][0]; }
+
+ inline void clear() { clear_obj(*this); }
+
+ inline bool operator== (const pixel_block& rhs) const
+ {
+ return memcmp(m_pixels, rhs.m_pixels, sizeof(m_pixels)) == 0;
+ }
+ };
+ typedef basisu::vector<pixel_block_hdr> pixel_block_hdr_vec;
+
+ void tonemap_image_reinhard(image& ldr_img, const imagef& hdr_img, float exposure);
+ bool tonemap_image_compressive(image& dst_img, const imagef& hdr_test_img);
+
+ // Intersection
+ enum eClear { cClear = 0 };
+ enum eInitExpand { cInitExpand = 0 };
+
+ template<typename vector_type>
+ class ray
+ {
+ public:
+ typedef vector_type vector_t;
+ typedef typename vector_type::scalar_type scalar_type;
+
+ inline ray() { }
+ inline ray(eClear) { clear(); }
+ inline ray(const vector_type& origin, const vector_type& direction) : m_origin(origin), m_direction(direction) { }
+
+ inline void clear()
+ {
+ m_origin.clear();
+ m_direction.clear();
+ }
+
+ inline const vector_type& get_origin(void) const { return m_origin; }
+ inline void set_origin(const vector_type& origin) { m_origin = origin; }
+
+ inline const vector_type& get_direction(void) const { return m_direction; }
+ inline void set_direction(const vector_type& direction) { m_direction = direction; }
+
+ inline void set_endpoints(const vector_type& start, const vector_type& end)
+ {
+ m_origin = start;
+
+ m_direction = end - start;
+ m_direction.normalize_in_place();
+ }
+
+ inline vector_type eval(scalar_type t) const
+ {
+ return m_origin + m_direction * t;
+ }
+
+ private:
+ vector_type m_origin;
+ vector_type m_direction;
+ };
+
+ typedef ray<vec2F> ray2F;
+ typedef ray<vec3F> ray3F;
+
+ template<typename T>
+ class vec_interval
+ {
+ public:
+ enum { N = T::num_elements };
+ typedef typename T::scalar_type scalar_type;
+
+ inline vec_interval(const T& v) { m_bounds[0] = v; m_bounds[1] = v; }
+ inline vec_interval(const T& low, const T& high) { m_bounds[0] = low; m_bounds[1] = high; }
+
+ inline vec_interval() { }
+ inline vec_interval(eClear) { clear(); }
+ inline vec_interval(eInitExpand) { init_expand(); }
+
+ inline void clear() { m_bounds[0].clear(); m_bounds[1].clear(); }
+
+ inline void init_expand()
+ {
+ m_bounds[0].set(1e+30f, 1e+30f, 1e+30f);
+ m_bounds[1].set(-1e+30f, -1e+30f, -1e+30f);
+ }
+
+ inline vec_interval expand(const T& p)
+ {
+ for (uint32_t c = 0; c < N; c++)
+ {
+ if (p[c] < m_bounds[0][c])
+ m_bounds[0][c] = p[c];
+
+ if (p[c] > m_bounds[1][c])
+ m_bounds[1][c] = p[c];
+ }
+
+ return *this;
+ }
+
+ inline const T& operator[] (uint32_t i) const { assert(i < 2); return m_bounds[i]; }
+ inline T& operator[] (uint32_t i) { assert(i < 2); return m_bounds[i]; }
+
+ const T& get_low() const { return m_bounds[0]; }
+ T& get_low() { return m_bounds[0]; }
+
+ const T& get_high() const { return m_bounds[1]; }
+ T& get_high() { return m_bounds[1]; }
+
+ scalar_type get_dim(uint32_t axis) const { return m_bounds[1][axis] - m_bounds[0][axis]; }
+
+ bool contains(const T& p) const
+ {
+ const T& low = get_low(), high = get_high();
+
+ for (uint32_t i = 0; i < N; i++)
+ {
+ if (p[i] < low[i])
+ return false;
+
+ if (p[i] > high[i])
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ T m_bounds[2];
+ };
+
+ typedef vec_interval<vec1F> vec_interval1F;
+ typedef vec_interval<vec2F> vec_interval2F;
+ typedef vec_interval<vec3F> vec_interval3F;
+ typedef vec_interval<vec4F> vec_interval4F;
+
+ typedef vec_interval2F aabb2F;
+ typedef vec_interval3F aabb3F;
+
+ namespace intersection
+ {
+ enum result
+ {
+ cBackfacing = -1,
+ cFailure = 0,
+ cSuccess,
+ cParallel,
+ cInside,
+ };
+
+ // Returns cInside, cSuccess, or cFailure.
+ // Algorithm: Graphics Gems 1
+ template<typename vector_type, typename scalar_type, typename ray_type, typename aabb_type>
+ result ray_aabb(vector_type& coord, scalar_type& t, const ray_type& ray, const aabb_type& box)
+ {
+ enum
+ {
+ cNumDim = vector_type::num_elements,
+ cRight = 0,
+ cLeft = 1,
+ cMiddle = 2
+ };
+
+ bool inside = true;
+ int quadrant[cNumDim];
+ scalar_type candidate_plane[cNumDim];
+
+ for (int i = 0; i < cNumDim; i++)
+ {
+ if (ray.get_origin()[i] < box[0][i])
+ {
+ quadrant[i] = cLeft;
+ candidate_plane[i] = box[0][i];
+ inside = false;
+ }
+ else if (ray.get_origin()[i] > box[1][i])
+ {
+ quadrant[i] = cRight;
+ candidate_plane[i] = box[1][i];
+ inside = false;
+ }
+ else
+ {
+ quadrant[i] = cMiddle;
+ }
+ }
+
+ if (inside)
+ {
+ coord = ray.get_origin();
+ t = 0.0f;
+ return cInside;
+ }
+
+ scalar_type max_t[cNumDim];
+ for (int i = 0; i < cNumDim; i++)
+ {
+ if ((quadrant[i] != cMiddle) && (ray.get_direction()[i] != 0.0f))
+ max_t[i] = (candidate_plane[i] - ray.get_origin()[i]) / ray.get_direction()[i];
+ else
+ max_t[i] = -1.0f;
+ }
+
+ int which_plane = 0;
+ for (int i = 1; i < cNumDim; i++)
+ if (max_t[which_plane] < max_t[i])
+ which_plane = i;
+
+ if (max_t[which_plane] < 0.0f)
+ return cFailure;
+
+ for (int i = 0; i < cNumDim; i++)
+ {
+ if (i != which_plane)
+ {
+ coord[i] = ray.get_origin()[i] + max_t[which_plane] * ray.get_direction()[i];
+
+ if ((coord[i] < box[0][i]) || (coord[i] > box[1][i]))
+ return cFailure;
+ }
+ else
+ {
+ coord[i] = candidate_plane[i];
+ }
+
+ assert(coord[i] >= box[0][i] && coord[i] <= box[1][i]);
+ }
+
+ t = max_t[which_plane];
+ return cSuccess;
+ }
+
+ template<typename vector_type, typename scalar_type, typename ray_type, typename aabb_type>
+ result ray_aabb(bool& started_within, vector_type& coord, scalar_type& t, const ray_type& ray, const aabb_type& box)
+ {
+ if (!box.contains(ray.get_origin()))
+ {
+ started_within = false;
+ return ray_aabb(coord, t, ray, box);
+ }
+
+ started_within = true;
+
+ typename vector_type::T diag_dist = box.diagonal_length() * 1.5f;
+ ray_type outside_ray(ray.eval(diag_dist), -ray.get_direction());
+
+ result res(ray_aabb(coord, t, outside_ray, box));
+ if (res != cSuccess)
+ return res;
+
+ t = basisu::maximum(0.0f, diag_dist - t);
+ return cSuccess;
+ }
+
+ } // intersect
+
+ // This float->half conversion matches how "F32TO16" works on Intel GPU's.
+ // Input cannot be negative, Inf or Nan.
+ inline basist::half_float float_to_half_non_neg_no_nan_inf(float val)
+ {
+ union { float f; int32_t i; uint32_t u; } fi = { val };
+ const int flt_m = fi.i & 0x7FFFFF, flt_e = (fi.i >> 23) & 0xFF;
+ int e = 0, m = 0;
+
+ assert(((fi.i >> 31) == 0) && (flt_e != 0xFF));
+
+ // not zero or denormal
+ if (flt_e != 0)
+ {
+ int new_exp = flt_e - 127;
+ if (new_exp > 15)
+ e = 31;
+ else if (new_exp < -14)
+ m = lrintf((1 << 24) * fabsf(fi.f));
+ else
+ {
+ e = new_exp + 15;
+ m = lrintf(flt_m * (1.0f / ((float)(1 << 13))));
+ }
+ }
+
+ assert((0 <= m) && (m <= 1024));
+ if (m == 1024)
+ {
+ e++;
+ m = 0;
+ }
+
+ assert((e >= 0) && (e <= 31));
+ assert((m >= 0) && (m <= 1023));
+
+ basist::half_float result = (basist::half_float)((e << 10) | m);
+ return result;
+ }
+
+ // Supports positive and denormals only. No NaN or Inf.
+ inline float fast_half_to_float_pos_not_inf_or_nan(basist::half_float h)
+ {
+ assert(!basist::half_is_signed(h) && !basist::is_half_inf_or_nan(h));
+
+ union fu32
+ {
+ uint32_t u;
+ float f;
+ };
+
+ static const fu32 K = { 0x77800000 };
+
+ fu32 o;
+ o.u = h << 13;
+ o.f *= K.f;
+
+ return o.f;
+ }
+
} // namespace basisu
diff --git a/thirdparty/basis_universal/encoder/basisu_etc.cpp b/thirdparty/basis_universal/encoder/basisu_etc.cpp
index f8bd0f12e5..ba1c14231d 100644
--- a/thirdparty/basis_universal/encoder/basisu_etc.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_etc.cpp
@@ -1,5 +1,5 @@
// basis_etc.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_etc.h b/thirdparty/basis_universal/encoder/basisu_etc.h
index 208f2aac1b..5c44bd4812 100644
--- a/thirdparty/basis_universal/encoder/basisu_etc.h
+++ b/thirdparty/basis_universal/encoder/basisu_etc.h
@@ -1,5 +1,5 @@
// basis_etc.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_frontend.cpp b/thirdparty/basis_universal/encoder/basisu_frontend.cpp
index 1f30a33c70..750f706aa5 100644
--- a/thirdparty/basis_universal/encoder/basisu_frontend.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_frontend.cpp
@@ -1,5 +1,5 @@
// basisu_frontend.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -2347,6 +2347,7 @@ namespace basisu
continue;
uint64_t overall_best_err = 0;
+ (void)overall_best_err;
uint64_t total_err[4][4][4];
clear_obj(total_err);
diff --git a/thirdparty/basis_universal/encoder/basisu_frontend.h b/thirdparty/basis_universal/encoder/basisu_frontend.h
index cda73f3984..69fc8d8ec5 100644
--- a/thirdparty/basis_universal/encoder/basisu_frontend.h
+++ b/thirdparty/basis_universal/encoder/basisu_frontend.h
@@ -1,5 +1,5 @@
// basisu_frontend.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp b/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp
index dec769d5ac..342446b8fd 100644
--- a/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp
@@ -1,5 +1,5 @@
// basisu_gpu_texture.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,13 +15,15 @@
#include "basisu_gpu_texture.h"
#include "basisu_enc.h"
#include "basisu_pvrtc1_4.h"
-#if BASISU_USE_ASTC_DECOMPRESS
-#include "basisu_astc_decomp.h"
-#endif
+#include "3rdparty/android_astc_decomp.h"
#include "basisu_bc7enc.h"
+#include "../transcoder/basisu_astc_hdr_core.h"
namespace basisu
{
+ //------------------------------------------------------------------------------------------------
+ // ETC2 EAC
+
void unpack_etc2_eac(const void *pBlock_bits, color_rgba *pPixels)
{
static_assert(sizeof(eac_a8_block) == 8, "sizeof(eac_a8_block) == 8");
@@ -56,6 +58,8 @@ namespace basisu
pPixels[15].a = clamp255(base + pTable[pBlock->get_selector(3, 3, selector_bits)] * mul);
}
+ //------------------------------------------------------------------------------------------------
+ // BC1
struct bc1_block
{
enum { cTotalEndpointBytes = 2, cTotalSelectorBytes = 4 };
@@ -274,6 +278,9 @@ namespace basisu
return used_punchthrough;
}
+ //------------------------------------------------------------------------------------------------
+ // BC3-5
+
struct bc4_block
{
enum { cBC4SelectorBits = 3, cTotalSelectorBytes = 6, cMaxSelectorValues = 8 };
@@ -372,7 +379,8 @@ namespace basisu
unpack_bc4(pBlock_bits, &pPixels[0].r, sizeof(color_rgba));
unpack_bc4((const uint8_t *)pBlock_bits + sizeof(bc4_block), &pPixels[0].g, sizeof(color_rgba));
}
-
+
+ //------------------------------------------------------------------------------------------------
// ATC isn't officially documented, so I'm assuming these references:
// http://www.guildsoftware.com/papers/2012.Converting.DXTC.to.ATC.pdf
// https://github.com/Triang3l/S3TConv/blob/master/s3tconv_atitc.c
@@ -426,6 +434,7 @@ namespace basisu
}
}
+ //------------------------------------------------------------------------------------------------
// BC7 mode 0-7 decompression.
// Instead of one monster routine to unpack all the BC7 modes, we're lumping the 3 subset, 2 subset, 1 subset, and dual plane modes together into simple shared routines.
@@ -742,6 +751,255 @@ namespace basisu
return false;
}
+ static inline int bc6h_sign_extend(int val, int bits)
+ {
+ assert((bits >= 1) && (bits < 32));
+ assert((val >= 0) && (val < (1 << bits)));
+ return (val << (32 - bits)) >> (32 - bits);
+ }
+
+ static inline int bc6h_apply_delta(int base, int delta, int num_bits, int is_signed)
+ {
+ int bitmask = ((1 << num_bits) - 1);
+ int v = (base + delta) & bitmask;
+ return is_signed ? bc6h_sign_extend(v, num_bits) : v;
+ }
+
+ static int bc6h_dequantize(int val, int bits, int is_signed)
+ {
+ int result;
+ if (is_signed)
+ {
+ if (bits >= 16)
+ result = val;
+ else
+ {
+ int s_flag = 0;
+ if (val < 0)
+ {
+ s_flag = 1;
+ val = -val;
+ }
+
+ if (val == 0)
+ result = 0;
+ else if (val >= ((1 << (bits - 1)) - 1))
+ result = 0x7FFF;
+ else
+ result = ((val << 15) + 0x4000) >> (bits - 1);
+
+ if (s_flag)
+ result = -result;
+ }
+ }
+ else
+ {
+ if (bits >= 15)
+ result = val;
+ else if (!val)
+ result = 0;
+ else if (val == ((1 << bits) - 1))
+ result = 0xFFFF;
+ else
+ result = ((val << 16) + 0x8000) >> bits;
+ }
+ return result;
+ }
+
+ static inline int bc6h_interpolate(int a, int b, const uint8_t* pWeights, int index)
+ {
+ return (a * (64 - (int)pWeights[index]) + b * (int)pWeights[index] + 32) >> 6;
+ }
+
+ static inline basist::half_float bc6h_convert_to_half(int val, int is_signed)
+ {
+ if (!is_signed)
+ {
+ // scale by 31/64
+ return (basist::half_float)((val * 31) >> 6);
+ }
+
+ // scale by 31/32
+ val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5;
+
+ int s = 0;
+ if (val < 0)
+ {
+ s = 0x8000;
+ val = -val;
+ }
+
+ return (basist::half_float)(s | val);
+ }
+
+ static inline uint32_t bc6h_get_bits(uint32_t num_bits, uint64_t& l, uint64_t& h, uint32_t& total_bits)
+ {
+ assert((num_bits) && (num_bits <= 63));
+
+ uint32_t v = (uint32_t)(l & ((1U << num_bits) - 1U));
+
+ l >>= num_bits;
+ l |= (h << (64U - num_bits));
+ h >>= num_bits;
+
+ total_bits += num_bits;
+ assert(total_bits <= 128);
+
+ return v;
+ }
+
+ static inline uint32_t bc6h_reverse_bits(uint32_t v, uint32_t num_bits)
+ {
+ uint32_t res = 0;
+ for (uint32_t i = 0; i < num_bits; i++)
+ {
+ uint32_t bit = (v & (1u << i)) != 0u;
+ res |= (bit << (num_bits - 1u - i));
+ }
+ return res;
+ }
+
+ static inline uint64_t bc6h_read_le_qword(const void* p)
+ {
+ const uint8_t* pSrc = static_cast<const uint8_t*>(p);
+ return ((uint64_t)read_le_dword(pSrc)) | (((uint64_t)read_le_dword(pSrc + sizeof(uint32_t))) << 32U);
+ }
+
+ bool unpack_bc6h(const void* pSrc_block, void* pDst_block, bool is_signed, uint32_t dest_pitch_in_halfs)
+ {
+ assert(dest_pitch_in_halfs >= 4 * 3);
+
+ const uint32_t MAX_SUBSETS = 2, MAX_COMPS = 3;
+
+ const uint8_t* pSrc = static_cast<const uint8_t*>(pSrc_block);
+ basist::half_float* pDst = static_cast<basist::half_float*>(pDst_block);
+
+ uint64_t blo = bc6h_read_le_qword(pSrc), bhi = bc6h_read_le_qword(pSrc + sizeof(uint64_t));
+
+ // Unpack mode
+ const int mode = basist::g_bc6h_mode_lookup[blo & 31];
+ if (mode < 0)
+ {
+ for (int y = 0; y < 4; y++)
+ {
+ memset(pDst, 0, sizeof(basist::half_float) * 4);
+ pDst += dest_pitch_in_halfs;
+ }
+ return false;
+ }
+
+ // Skip mode bits
+ uint32_t total_bits_read = 0;
+ bc6h_get_bits((mode < 2) ? 2 : 5, blo, bhi, total_bits_read);
+
+ assert(mode < (int)basist::NUM_BC6H_MODES);
+
+ const uint32_t num_subsets = (mode >= 10) ? 1 : 2;
+ const bool is_mode_9_or_10 = (mode == 9) || (mode == 10);
+
+ // Unpack endpoint components
+ int comps[MAX_SUBSETS][MAX_COMPS][2] = { { { 0 } } }; // [subset][comp][l/h]
+ int part_index = 0;
+
+ uint32_t layout_index = 0;
+ while (layout_index < basist::MAX_BC6H_LAYOUT_INDEX)
+ {
+ const basist::bc6h_bit_layout& layout = basist::g_bc6h_bit_layouts[mode][layout_index];
+
+ if (layout.m_comp < 0)
+ break;
+
+ const int subset = layout.m_index >> 1, lh_index = layout.m_index & 1;
+ assert((layout.m_comp == 3) || ((subset >= 0) && (subset < (int)MAX_SUBSETS)));
+
+ const int last_bit = layout.m_last_bit, first_bit = layout.m_first_bit;
+ assert(last_bit >= 0);
+
+ int& res = (layout.m_comp == 3) ? part_index : comps[subset][layout.m_comp][lh_index];
+
+ if (first_bit < 0)
+ {
+ res |= (bc6h_get_bits(1, blo, bhi, total_bits_read) << last_bit);
+ }
+ else
+ {
+ const int total_bits = iabs(last_bit - first_bit) + 1;
+ const int bit_shift = basisu::minimum(first_bit, last_bit);
+
+ int b = bc6h_get_bits(total_bits, blo, bhi, total_bits_read);
+
+ if (last_bit < first_bit)
+ b = bc6h_reverse_bits(b, total_bits);
+
+ res |= (b << bit_shift);
+ }
+
+ layout_index++;
+ }
+ assert(layout_index != basist::MAX_BC6H_LAYOUT_INDEX);
+
+ // Sign extend/dequantize endpoints
+ const int num_sig_bits = basist::g_bc6h_mode_sig_bits[mode][0];
+ if (is_signed)
+ {
+ for (uint32_t comp = 0; comp < 3; comp++)
+ comps[0][comp][0] = bc6h_sign_extend(comps[0][comp][0], num_sig_bits);
+ }
+
+ if (is_signed || !is_mode_9_or_10)
+ {
+ for (uint32_t subset = 0; subset < num_subsets; subset++)
+ for (uint32_t comp = 0; comp < 3; comp++)
+ for (uint32_t lh = (subset ? 0 : 1); lh < 2; lh++)
+ comps[subset][comp][lh] = bc6h_sign_extend(comps[subset][comp][lh], basist::g_bc6h_mode_sig_bits[mode][1 + comp]);
+ }
+
+ if (!is_mode_9_or_10)
+ {
+ for (uint32_t subset = 0; subset < num_subsets; subset++)
+ for (uint32_t comp = 0; comp < 3; comp++)
+ for (uint32_t lh = (subset ? 0 : 1); lh < 2; lh++)
+ comps[subset][comp][lh] = bc6h_apply_delta(comps[0][comp][0], comps[subset][comp][lh], num_sig_bits, is_signed);
+ }
+
+ for (uint32_t subset = 0; subset < num_subsets; subset++)
+ for (uint32_t comp = 0; comp < 3; comp++)
+ for (uint32_t lh = 0; lh < 2; lh++)
+ comps[subset][comp][lh] = bc6h_dequantize(comps[subset][comp][lh], num_sig_bits, is_signed);
+
+ // Now unpack weights and output texels
+ const int weight_bits = (mode >= 10) ? 4 : 3;
+ const uint8_t* pWeights = (mode >= 10) ? basist::g_bc6h_weight4 : basist::g_bc6h_weight3;
+
+ dest_pitch_in_halfs -= 4 * 3;
+
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ int subset = (num_subsets == 1) ? ((x | y) ? 0 : 0x80) : basist::g_bc6h_2subset_patterns[part_index][y][x];
+ const int num_bits = weight_bits + ((subset & 0x80) ? -1 : 0);
+
+ subset &= 1;
+
+ const int weight_index = bc6h_get_bits(num_bits, blo, bhi, total_bits_read);
+
+ pDst[0] = bc6h_convert_to_half(bc6h_interpolate(comps[subset][0][0], comps[subset][0][1], pWeights, weight_index), is_signed);
+ pDst[1] = bc6h_convert_to_half(bc6h_interpolate(comps[subset][1][0], comps[subset][1][1], pWeights, weight_index), is_signed);
+ pDst[2] = bc6h_convert_to_half(bc6h_interpolate(comps[subset][2][0], comps[subset][2][1], pWeights, weight_index), is_signed);
+
+ pDst += 3;
+ }
+
+ pDst += dest_pitch_in_halfs;
+ }
+
+ assert(total_bits_read == 128);
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------
+ // FXT1 (for fun, and because some modern Intel parts support it, and because a subset is like BC1)
+
struct fxt1_block
{
union
@@ -901,6 +1159,9 @@ namespace basisu
return true;
}
+ //------------------------------------------------------------------------------------------------
+ // PVRTC2 (non-interpolated, hard_flag=1 modulation=0 subset only!)
+
struct pvrtc2_block
{
uint8_t m_modulation[4];
@@ -1015,6 +1276,9 @@ namespace basisu
return true;
}
+ //------------------------------------------------------------------------------------------------
+ // ETC2 EAC R11 or RG11
+
struct etc2_eac_r11
{
uint64_t m_base : 8;
@@ -1085,13 +1349,16 @@ namespace basisu
unpack_etc2_eac_r(pBlock, pPixels, c);
}
}
-
+
+ //------------------------------------------------------------------------------------------------
+ // UASTC
+
void unpack_uastc(const void* p, color_rgba* pPixels)
{
basist::unpack_uastc(*static_cast<const basist::uastc_block*>(p), (basist::color32 *)pPixels, false);
}
-
- // Unpacks to RGBA, R, RG, or A
+
+ // Unpacks to RGBA, R, RG, or A. LDR GPU texture formats only.
bool unpack_block(texture_format fmt, const void* pBlock, color_rgba* pPixels)
{
switch (fmt)
@@ -1150,14 +1417,24 @@ namespace basisu
unpack_etc2_eac(pBlock, pPixels);
break;
}
- case texture_format::cASTC4x4:
+ case texture_format::cBC6HSigned:
+ case texture_format::cBC6HUnsigned:
+ case texture_format::cASTC_HDR_4x4:
+ case texture_format::cUASTC_HDR_4x4:
+ {
+ // Can't unpack HDR blocks in unpack_block() because it returns 32bpp pixel data.
+ assert(0);
+ return false;
+ }
+ case texture_format::cASTC_LDR_4x4:
{
-#if BASISU_USE_ASTC_DECOMPRESS
const bool astc_srgb = false;
- basisu_astc::astc::decompress(reinterpret_cast<uint8_t*>(pPixels), static_cast<const uint8_t*>(pBlock), astc_srgb, 4, 4);
-#else
- memset(pPixels, 255, 16 * sizeof(color_rgba));
-#endif
+ bool status = basisu_astc::astc::decompress_ldr(reinterpret_cast<uint8_t*>(pPixels), static_cast<const uint8_t*>(pBlock), astc_srgb, 4, 4);
+ assert(status);
+
+ if (!status)
+ return false;
+
break;
}
case texture_format::cATC_RGB:
@@ -1206,6 +1483,66 @@ namespace basisu
return true;
}
+ bool unpack_block_hdr(texture_format fmt, const void* pBlock, vec4F* pPixels)
+ {
+ switch (fmt)
+ {
+ case texture_format::cASTC_HDR_4x4:
+ case texture_format::cUASTC_HDR_4x4:
+ {
+#if 1
+ bool status = basisu_astc::astc::decompress_hdr(&pPixels[0][0], (uint8_t*)pBlock, 4, 4);
+ assert(status);
+ if (!status)
+ return false;
+#else
+ basist::half_float half_block[16][4];
+
+ astc_helpers::log_astc_block log_blk;
+ if (!astc_helpers::unpack_block(pBlock, log_blk, 4, 4))
+ return false;
+ if (!astc_helpers::decode_block(log_blk, half_block, 4, 4, astc_helpers::cDecodeModeHDR16))
+ return false;
+
+ for (uint32_t p = 0; p < 16; p++)
+ {
+ pPixels[p][0] = basist::half_to_float(half_block[p][0]);
+ pPixels[p][1] = basist::half_to_float(half_block[p][1]);
+ pPixels[p][2] = basist::half_to_float(half_block[p][2]);
+ pPixels[p][3] = basist::half_to_float(half_block[p][3]);
+ }
+
+ //memset(pPixels, 0, sizeof(vec4F) * 16);
+#endif
+ return true;
+ }
+ case texture_format::cBC6HSigned:
+ case texture_format::cBC6HUnsigned:
+ {
+ basist::half_float half_block[16][3];
+
+ unpack_bc6h(pBlock, half_block, fmt == texture_format::cBC6HSigned);
+
+ for (uint32_t p = 0; p < 16; p++)
+ {
+ pPixels[p][0] = basist::half_to_float(half_block[p][0]);
+ pPixels[p][1] = basist::half_to_float(half_block[p][1]);
+ pPixels[p][2] = basist::half_to_float(half_block[p][2]);
+ pPixels[p][3] = 1.0f;
+ }
+
+ return true;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ assert(0);
+ return false;
+ }
+
bool gpu_image::unpack(image& img) const
{
img.resize(get_pixel_width(), get_pixel_height());
@@ -1252,7 +1589,48 @@ namespace basisu
return success;
}
+
+ bool gpu_image::unpack_hdr(imagef& img) const
+ {
+ if ((m_fmt != texture_format::cASTC_HDR_4x4) &&
+ (m_fmt != texture_format::cUASTC_HDR_4x4) &&
+ (m_fmt != texture_format::cBC6HUnsigned) &&
+ (m_fmt != texture_format::cBC6HSigned))
+ {
+ // Can't call on LDR images, at least currently. (Could unpack the LDR data and convert to float.)
+ assert(0);
+ return false;
+ }
+
+ img.resize(get_pixel_width(), get_pixel_height());
+ img.set_all(vec4F(0.0f));
+
+ if (!img.get_width() || !img.get_height())
+ return true;
+
+ assert((m_block_width <= cMaxBlockSize) && (m_block_height <= cMaxBlockSize));
+ vec4F pixels[cMaxBlockSize * cMaxBlockSize];
+ clear_obj(pixels);
+
+ bool success = true;
+
+ for (uint32_t by = 0; by < m_blocks_y; by++)
+ {
+ for (uint32_t bx = 0; bx < m_blocks_x; bx++)
+ {
+ const void* pBlock = get_block_ptr(bx, by);
+
+ if (!unpack_block_hdr(m_fmt, pBlock, pixels))
+ success = false;
+
+ img.set_block_clipped(pixels, bx * m_block_width, by * m_block_height, m_block_width, m_block_height);
+ } // bx
+ } // by
+
+ return success;
+ }
+ // KTX1 texture file writing
static const uint8_t g_ktx_file_id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
// KTX/GL enums
@@ -1273,6 +1651,8 @@ namespace basisu
KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278,
KTX_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C,
KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D,
+ KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E,
+ KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F,
KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00,
KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02,
KTX_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0,
@@ -1319,6 +1699,7 @@ namespace basisu
uint32_t width = 0, height = 0, total_levels = 0;
basisu::texture_format fmt = texture_format::cInvalidTextureFormat;
+ // Sanity check the input
if (cubemap_flag)
{
if ((gpu_images.size() % 6) != 0)
@@ -1327,7 +1708,7 @@ namespace basisu
return false;
}
}
-
+
for (uint32_t array_index = 0; array_index < gpu_images.size(); array_index++)
{
const gpu_image_vec &levels = gpu_images[array_index];
@@ -1426,6 +1807,18 @@ namespace basisu
base_internal_fmt = KTX_RGBA;
break;
}
+ case texture_format::cBC6HSigned:
+ {
+ internal_fmt = KTX_COMPRESSED_RGB_BPTC_SIGNED_FLOAT;
+ base_internal_fmt = KTX_RGBA;
+ break;
+ }
+ case texture_format::cBC6HUnsigned:
+ {
+ internal_fmt = KTX_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
+ base_internal_fmt = KTX_RGBA;
+ break;
+ }
case texture_format::cBC7:
{
internal_fmt = KTX_COMPRESSED_RGBA_BPTC_UNORM;
@@ -1443,7 +1836,10 @@ namespace basisu
base_internal_fmt = KTX_RGBA;
break;
}
- case texture_format::cASTC4x4:
+ // We use different enums for HDR vs. LDR ASTC, but internally they are both just ASTC.
+ case texture_format::cASTC_LDR_4x4:
+ case texture_format::cASTC_HDR_4x4:
+ case texture_format::cUASTC_HDR_4x4: // UASTC_HDR is just HDR-only ASTC
{
internal_fmt = KTX_COMPRESSED_RGBA_ASTC_4x4_KHR;
base_internal_fmt = KTX_RGBA;
@@ -1496,17 +1892,17 @@ namespace basisu
return false;
}
}
-
+
ktx_header header;
header.clear();
memcpy(&header.m_identifier, g_ktx_file_id, sizeof(g_ktx_file_id));
header.m_endianness = KTX_ENDIAN;
-
+
header.m_pixelWidth = width;
header.m_pixelHeight = height;
-
+
header.m_glTypeSize = 1;
-
+
header.m_glInternalFormat = internal_fmt;
header.m_glBaseInternalFormat = base_internal_fmt;
@@ -1517,12 +1913,12 @@ namespace basisu
header.m_numberOfMipmapLevels = total_levels;
header.m_numberOfFaces = cubemap_flag ? 6 : 1;
- append_vector(ktx_data, (uint8_t *)&header, sizeof(header));
+ append_vector(ktx_data, (uint8_t*)&header, sizeof(header));
for (uint32_t level_index = 0; level_index < total_levels; level_index++)
{
uint32_t img_size = gpu_images[0][level_index].get_size_in_bytes();
-
+
if ((header.m_numberOfFaces == 1) || (header.m_numberOfArrayElements > 1))
{
img_size = img_size * header.m_numberOfFaces * maximum<uint32_t>(1, header.m_numberOfArrayElements);
@@ -1531,9 +1927,10 @@ namespace basisu
assert(img_size && ((img_size & 3) == 0));
packed_uint<4> packed_img_size(img_size);
- append_vector(ktx_data, (uint8_t *)&packed_img_size, sizeof(packed_img_size));
+ append_vector(ktx_data, (uint8_t*)&packed_img_size, sizeof(packed_img_size));
uint32_t bytes_written = 0;
+ (void)bytes_written;
for (uint32_t array_index = 0; array_index < maximum<uint32_t>(1, header.m_numberOfArrayElements); array_index++)
{
@@ -1541,11 +1938,11 @@ namespace basisu
{
const gpu_image& img = gpu_images[cubemap_flag ? (array_index * 6 + face_index) : array_index][level_index];
- append_vector(ktx_data, (uint8_t *)img.get_ptr(), img.get_size_in_bytes());
-
+ append_vector(ktx_data, (uint8_t*)img.get_ptr(), img.get_size_in_bytes());
+
bytes_written += img.get_size_in_bytes();
}
-
+
} // array_index
} // level_index
@@ -1553,7 +1950,58 @@ namespace basisu
return true;
}
- bool write_compressed_texture_file(const char* pFilename, const basisu::vector<gpu_image_vec>& g, bool cubemap_flag)
+ bool does_dds_support_format(texture_format fmt)
+ {
+ switch (fmt)
+ {
+ case texture_format::cBC1_NV:
+ case texture_format::cBC1_AMD:
+ case texture_format::cBC1:
+ case texture_format::cBC3:
+ case texture_format::cBC4:
+ case texture_format::cBC5:
+ case texture_format::cBC6HSigned:
+ case texture_format::cBC6HUnsigned:
+ case texture_format::cBC7:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ // Only supports the basic DirectX BC texture formats.
+ // gpu_images array is: [face/layer][mipmap level]
+ // For cubemap arrays, # of face/layers must be a multiple of 6.
+ // Accepts 2D, 2D mipmapped, 2D array, 2D array mipmapped
+ // and cubemap, cubemap mipmapped, and cubemap array mipmapped.
+ bool write_dds_file(uint8_vec &dds_data, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format)
+ {
+ return false;
+ }
+
+ bool write_dds_file(const char* pFilename, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format)
+ {
+ uint8_vec dds_data;
+
+ if (!write_dds_file(dds_data, gpu_images, cubemap_flag, use_srgb_format))
+ return false;
+
+ if (!write_vec_to_file(pFilename, dds_data))
+ {
+ fprintf(stderr, "write_dds_file: Failed writing DDS file data\n");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool read_uncompressed_dds_file(const char* pFilename, basisu::vector<image> &ldr_mips, basisu::vector<imagef>& hdr_mips)
+ {
+ return false;
+ }
+
+ bool write_compressed_texture_file(const char* pFilename, const basisu::vector<gpu_image_vec>& g, bool cubemap_flag, bool use_srgb_format)
{
std::string extension(string_tolower(string_get_extension(pFilename)));
@@ -1570,8 +2018,8 @@ namespace basisu
}
else if (extension == "dds")
{
- // TODO
- return false;
+ if (!write_dds_file(filedata, g, cubemap_flag, use_srgb_format))
+ return false;
}
else
{
@@ -1583,11 +2031,18 @@ namespace basisu
return basisu::write_vec_to_file(pFilename, filedata);
}
- bool write_compressed_texture_file(const char* pFilename, const gpu_image& g)
+ bool write_compressed_texture_file(const char* pFilename, const gpu_image_vec& g, bool use_srgb_format)
+ {
+ basisu::vector<gpu_image_vec> a;
+ a.push_back(g);
+ return write_compressed_texture_file(pFilename, a, false, use_srgb_format);
+ }
+
+ bool write_compressed_texture_file(const char* pFilename, const gpu_image& g, bool use_srgb_format)
{
basisu::vector<gpu_image_vec> v;
enlarge_vector(v, 1)->push_back(g);
- return write_compressed_texture_file(pFilename, v, false);
+ return write_compressed_texture_file(pFilename, v, false, use_srgb_format);
}
//const uint32_t OUT_FILE_MAGIC = 'TEXC';
@@ -1626,5 +2081,49 @@ namespace basisu
return fclose(pFile) != EOF;
}
+
+ // The .astc texture format is readable using ARM's astcenc, AMD Compressonator, and other engines/tools. It oddly doesn't support mipmaps, limiting
+ // its usefulness/relevance.
+ // https://github.com/ARM-software/astc-encoder/blob/main/Docs/FileFormat.md
+ bool write_astc_file(const char* pFilename, const void* pBlocks, uint32_t block_width, uint32_t block_height, uint32_t dim_x, uint32_t dim_y)
+ {
+ assert(pBlocks && (block_width >= 4) && (block_height >= 4) && (dim_x > 0) && (dim_y > 0));
+
+ uint8_vec file_data;
+ file_data.push_back(0x13);
+ file_data.push_back(0xAB);
+ file_data.push_back(0xA1);
+ file_data.push_back(0x5C);
+
+ file_data.push_back((uint8_t)block_width);
+ file_data.push_back((uint8_t)block_height);
+ file_data.push_back(1);
+
+ file_data.push_back((uint8_t)dim_x);
+ file_data.push_back((uint8_t)(dim_x >> 8));
+ file_data.push_back((uint8_t)(dim_x >> 16));
+
+ file_data.push_back((uint8_t)dim_y);
+ file_data.push_back((uint8_t)(dim_y >> 8));
+ file_data.push_back((uint8_t)(dim_y >> 16));
+
+ file_data.push_back((uint8_t)1);
+ file_data.push_back((uint8_t)0);
+ file_data.push_back((uint8_t)0);
+
+ const uint32_t num_blocks_x = (dim_x + block_width - 1) / block_width;
+ const uint32_t num_blocks_y = (dim_y + block_height - 1) / block_height;
+
+ const uint32_t total_bytes = num_blocks_x * num_blocks_y * 16;
+
+ const size_t cur_size = file_data.size();
+
+ file_data.resize(cur_size + total_bytes);
+
+ memcpy(&file_data[cur_size], pBlocks, total_bytes);
+
+ return write_vec_to_file(pFilename, file_data);
+ }
+
} // basisu
diff --git a/thirdparty/basis_universal/encoder/basisu_gpu_texture.h b/thirdparty/basis_universal/encoder/basisu_gpu_texture.h
index 619926f5f9..67c2a2bc5e 100644
--- a/thirdparty/basis_universal/encoder/basisu_gpu_texture.h
+++ b/thirdparty/basis_universal/encoder/basisu_gpu_texture.h
@@ -1,5 +1,5 @@
// basisu_gpu_texture.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ namespace basisu
}
inline texture_format get_format() const { return m_fmt; }
+ inline bool is_hdr() const { return is_hdr_texture_format(m_fmt); }
// Width/height in pixels
inline uint32_t get_pixel_width() const { return m_width; }
@@ -100,9 +101,13 @@ namespace basisu
m_blocks.resize(m_blocks_x * m_blocks_y * m_qwords_per_block);
}
+ // Unpacks LDR textures only.
bool unpack(image& img) const;
+
+ // Unpacks HDR textures only.
+ bool unpack_hdr(imagef& img) const;
- void override_dimensions(uint32_t w, uint32_t h)
+ inline void override_dimensions(uint32_t w, uint32_t h)
{
m_width = w;
m_height = h;
@@ -116,39 +121,50 @@ namespace basisu
typedef basisu::vector<gpu_image> gpu_image_vec;
- // KTX file writing
-
+ // KTX1 file writing
bool create_ktx_texture_file(uint8_vec &ktx_data, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag);
-
- bool write_compressed_texture_file(const char *pFilename, const basisu::vector<gpu_image_vec>& g, bool cubemap_flag);
- inline bool write_compressed_texture_file(const char *pFilename, const gpu_image_vec &g)
- {
- basisu::vector<gpu_image_vec> a;
- a.push_back(g);
- return write_compressed_texture_file(pFilename, a, false);
- }
+ bool does_dds_support_format(texture_format fmt);
+ bool write_dds_file(uint8_vec& dds_data, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format);
+ bool write_dds_file(const char* pFilename, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format);
+
+ // Currently reads 2D 32bpp RGBA, 16-bit HALF RGBA, or 32-bit FLOAT RGBA, with or without mipmaps. No tex arrays or cubemaps, yet.
+ bool read_uncompressed_dds_file(const char* pFilename, basisu::vector<image>& ldr_mips, basisu::vector<imagef>& hdr_mips);
- bool write_compressed_texture_file(const char *pFilename, const gpu_image &g);
+ // Supports DDS and KTX
+ bool write_compressed_texture_file(const char *pFilename, const basisu::vector<gpu_image_vec>& g, bool cubemap_flag, bool use_srgb_format);
+ bool write_compressed_texture_file(const char* pFilename, const gpu_image_vec& g, bool use_srgb_format);
+ bool write_compressed_texture_file(const char *pFilename, const gpu_image &g, bool use_srgb_format);
bool write_3dfx_out_file(const char* pFilename, const gpu_image& gi);
// GPU texture block unpacking
+ // For ETC1, use in basisu_etc.h: bool unpack_etc1(const etc_block& block, color_rgba *pDst, bool preserve_alpha)
void unpack_etc2_eac(const void *pBlock_bits, color_rgba *pPixels);
bool unpack_bc1(const void *pBlock_bits, color_rgba *pPixels, bool set_alpha);
void unpack_bc4(const void *pBlock_bits, uint8_t *pPixels, uint32_t stride);
bool unpack_bc3(const void *pBlock_bits, color_rgba *pPixels);
void unpack_bc5(const void *pBlock_bits, color_rgba *pPixels);
bool unpack_bc7_mode6(const void *pBlock_bits, color_rgba *pPixels);
- bool unpack_bc7(const void* pBlock_bits, color_rgba* pPixels);
+ bool unpack_bc7(const void* pBlock_bits, color_rgba* pPixels); // full format
+ bool unpack_bc6h(const void* pSrc_block, void* pDst_block, bool is_signed, uint32_t dest_pitch_in_halfs = 4 * 3); // full format, outputs HALF values, RGB texels only (not RGBA)
void unpack_atc(const void* pBlock_bits, color_rgba* pPixels);
+ // We only support CC_MIXED non-alpha blocks here because that's the only mode the transcoder uses at the moment.
bool unpack_fxt1(const void* p, color_rgba* pPixels);
+ // PVRTC2 is currently limited to only what our transcoder outputs (non-interpolated, hard_flag=1 modulation=0). In this mode, PVRTC2 looks much like BC1/ATC.
bool unpack_pvrtc2(const void* p, color_rgba* pPixels);
void unpack_etc2_eac_r(const void *p, color_rgba* pPixels, uint32_t c);
void unpack_etc2_eac_rg(const void* p, color_rgba* pPixels);
-
+
// unpack_block() is primarily intended to unpack texture data created by the transcoder.
- // For some texture formats (like ETC2 RGB, PVRTC2, FXT1) it's not a complete implementation.
+ // For some texture formats (like ETC2 RGB, PVRTC2, FXT1) it's not yet a complete implementation.
+ // Unpacks LDR texture formats only.
bool unpack_block(texture_format fmt, const void *pBlock, color_rgba *pPixels);
-
+
+ // Unpacks HDR texture formats only.
+ bool unpack_block_hdr(texture_format fmt, const void* pBlock, vec4F* pPixels);
+
+ bool write_astc_file(const char* pFilename, const void* pBlocks, uint32_t block_width, uint32_t block_height, uint32_t dim_x, uint32_t dim_y);
+
} // namespace basisu
+
diff --git a/thirdparty/basis_universal/encoder/basisu_kernels_declares.h b/thirdparty/basis_universal/encoder/basisu_kernels_declares.h
index b03e2ea6e8..9b85a594ee 100644
--- a/thirdparty/basis_universal/encoder/basisu_kernels_declares.h
+++ b/thirdparty/basis_universal/encoder/basisu_kernels_declares.h
@@ -1,5 +1,5 @@
// basisu_kernels_declares.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_kernels_imp.h b/thirdparty/basis_universal/encoder/basisu_kernels_imp.h
index dcf1ce069a..123862b1dd 100644
--- a/thirdparty/basis_universal/encoder/basisu_kernels_imp.h
+++ b/thirdparty/basis_universal/encoder/basisu_kernels_imp.h
@@ -1,5 +1,5 @@
// basisu_kernels_imp.h - Do not directly include
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_kernels_sse.cpp b/thirdparty/basis_universal/encoder/basisu_kernels_sse.cpp
index 4f15a5a12b..36a493d7ed 100644
--- a/thirdparty/basis_universal/encoder/basisu_kernels_sse.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_kernels_sse.cpp
@@ -1,5 +1,5 @@
// basisu_kernels_sse.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -22,22 +22,6 @@
#include <intrin.h>
#endif
-#if !defined(_MSC_VER)
- #if __AVX__ || __AVX2__ || __AVX512F__
- #error Please check your compiler options
- #endif
-
- #if CPPSPMD_SSE2
- #if __SSE4_1__ || __SSE3__ || __SSE4_2__ || __SSSE3__
- #error SSE4.1/SSE3/SSE4.2/SSSE3 cannot be enabled to use this file
- #endif
- #else
- #if !__SSE4_1__ || !__SSE3__ || !__SSSE3__
- #error Please check your compiler options
- #endif
- #endif
-#endif
-
#include "cppspmd_sse.h"
#include "cppspmd_type_aliases.h"
diff --git a/thirdparty/basis_universal/encoder/basisu_miniz.h b/thirdparty/basis_universal/encoder/basisu_miniz.h
index 18de997232..dab38f9f92 100644
--- a/thirdparty/basis_universal/encoder/basisu_miniz.h
+++ b/thirdparty/basis_universal/encoder/basisu_miniz.h
@@ -3,7 +3,7 @@
Forked from the public domain/unlicense version at: https://code.google.com/archive/p/miniz/
- Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+ Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -1973,7 +1973,7 @@ static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahe
(TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) );
if (!probe_len)
{
- *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break;
+ *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); break;
}
else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len)
{
@@ -2101,7 +2101,7 @@ static mz_bool tdefl_compress_fast(tdefl_compressor *d)
total_lz_bytes += cur_match_len;
lookahead_pos += cur_match_len;
- dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE);
+ dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE);
cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;
MZ_ASSERT(lookahead_size >= cur_match_len);
lookahead_size -= cur_match_len;
@@ -2129,7 +2129,7 @@ static mz_bool tdefl_compress_fast(tdefl_compressor *d)
d->m_huff_count[0][lit]++;
lookahead_pos++;
- dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE);
+ dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE);
cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
lookahead_size--;
@@ -2283,7 +2283,7 @@ static mz_bool tdefl_compress_normal(tdefl_compressor *d)
d->m_lookahead_pos += len_to_move;
MZ_ASSERT(d->m_lookahead_size >= len_to_move);
d->m_lookahead_size -= len_to_move;
- d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE);
+ d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE);
// Check if it's time to flush the current LZ codes to the internal output buffer.
if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||
( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) )
diff --git a/thirdparty/basis_universal/encoder/basisu_opencl.cpp b/thirdparty/basis_universal/encoder/basisu_opencl.cpp
index 81e3090a26..e0611c18ee 100644
--- a/thirdparty/basis_universal/encoder/basisu_opencl.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_opencl.cpp
@@ -1,5 +1,5 @@
// basisu_opencl.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_opencl.h b/thirdparty/basis_universal/encoder/basisu_opencl.h
index 4194a08418..2546a18dab 100644
--- a/thirdparty/basis_universal/encoder/basisu_opencl.h
+++ b/thirdparty/basis_universal/encoder/basisu_opencl.h
@@ -1,5 +1,5 @@
// basisu_opencl.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Note: Undefine or set BASISU_SUPPORT_OPENCL to 0 to completely OpenCL support.
//
diff --git a/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.cpp b/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.cpp
index 596fc197e6..4bf9516f90 100644
--- a/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.cpp
@@ -1,5 +1,5 @@
// basisu_pvrtc1_4.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.h b/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.h
index db6985a439..a9fe6b27aa 100644
--- a/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.h
+++ b/thirdparty/basis_universal/encoder/basisu_pvrtc1_4.h
@@ -1,5 +1,5 @@
// basisu_pvrtc1_4.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -231,7 +231,18 @@ namespace basisu
inline void set_to_black()
{
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
memset(m_blocks.get_ptr(), 0, m_blocks.size_in_bytes());
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#endif
}
inline bool get_block_uses_transparent_modulation(uint32_t bx, uint32_t by) const
diff --git a/thirdparty/basis_universal/encoder/basisu_resample_filters.cpp b/thirdparty/basis_universal/encoder/basisu_resample_filters.cpp
index 597cb3f618..46cd837376 100644
--- a/thirdparty/basis_universal/encoder/basisu_resample_filters.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_resample_filters.cpp
@@ -1,5 +1,5 @@
// basisu_resampler_filters.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_resampler.cpp b/thirdparty/basis_universal/encoder/basisu_resampler.cpp
index f4cedf0031..a00c63335d 100644
--- a/thirdparty/basis_universal/encoder/basisu_resampler.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_resampler.cpp
@@ -1,5 +1,5 @@
// basisu_resampler.cpp
-// Copyright (C) 2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_resampler.h b/thirdparty/basis_universal/encoder/basisu_resampler.h
index dc0978caeb..ac1ef73d7f 100644
--- a/thirdparty/basis_universal/encoder/basisu_resampler.h
+++ b/thirdparty/basis_universal/encoder/basisu_resampler.h
@@ -1,5 +1,5 @@
// basisu_resampler.h
-// Copyright (C) 2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_resampler_filters.h b/thirdparty/basis_universal/encoder/basisu_resampler_filters.h
index 0ebb51c334..4d66ac2c70 100644
--- a/thirdparty/basis_universal/encoder/basisu_resampler_filters.h
+++ b/thirdparty/basis_universal/encoder/basisu_resampler_filters.h
@@ -1,5 +1,5 @@
// basisu_resampler_filters.h
-// Copyright (C) 2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_ssim.cpp b/thirdparty/basis_universal/encoder/basisu_ssim.cpp
index cceb400b88..608ce937fc 100644
--- a/thirdparty/basis_universal/encoder/basisu_ssim.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_ssim.cpp
@@ -1,5 +1,5 @@
// basisu_ssim.cpp
-// Copyright (C) 2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_ssim.h b/thirdparty/basis_universal/encoder/basisu_ssim.h
index 986ca3bbdf..51cd2d78fd 100644
--- a/thirdparty/basis_universal/encoder/basisu_ssim.h
+++ b/thirdparty/basis_universal/encoder/basisu_ssim.h
@@ -1,5 +1,5 @@
// basisu_ssim.h
-// Copyright (C) 2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/basisu_uastc_enc.cpp b/thirdparty/basis_universal/encoder/basisu_uastc_enc.cpp
index 271bbc6f1d..51f6e979d4 100644
--- a/thirdparty/basis_universal/encoder/basisu_uastc_enc.cpp
+++ b/thirdparty/basis_universal/encoder/basisu_uastc_enc.cpp
@@ -1,5 +1,5 @@
// basisu_uastc_enc.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,11 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "basisu_uastc_enc.h"
-
-#if BASISU_USE_ASTC_DECOMPRESS
-#include "basisu_astc_decomp.h"
-#endif
-
+#include "3rdparty/android_astc_decomp.h"
#include "basisu_gpu_texture.h"
#include "basisu_bc7enc.h"
@@ -384,6 +380,7 @@ namespace basisu
}
uint32_t total_endpoint_bits = 0;
+ (void)total_endpoint_bits;
for (uint32_t i = 0; i < total_tq_values; i++)
{
@@ -428,6 +425,8 @@ namespace basisu
#endif
uint32_t total_weight_bits = 0;
+ (void)total_weight_bits;
+
const uint32_t plane_shift = (total_planes == 2) ? 1 : 0;
for (uint32_t i = 0; i < 16 * total_planes; i++)
{
@@ -3175,6 +3174,7 @@ namespace basisu
const bool favor_bc7_error = !favor_uastc_error && ((flags & cPackUASTCFavorBC7Error) != 0);
//const bool etc1_perceptual = true;
+ // TODO: This uses 64KB of stack space!
uastc_encode_results results[MAX_ENCODE_RESULTS];
level = clampi(level, cPackUASTCLevelFastest, cPackUASTCLevelVerySlow);
@@ -3567,7 +3567,6 @@ namespace basisu
success = basist::unpack_uastc(temp_block, (basist::color32 *)temp_block_unpacked, false);
VALIDATE(success);
-#if BASISU_USE_ASTC_DECOMPRESS
// Now round trip to packed ASTC and back, then decode to pixels.
uint32_t astc_data[4];
@@ -3580,7 +3579,7 @@ namespace basisu
}
color_rgba decoded_astc_block[4][4];
- success = basisu_astc::astc::decompress((uint8_t*)decoded_astc_block, (uint8_t*)&astc_data, false, 4, 4);
+ success = basisu_astc::astc::decompress_ldr((uint8_t*)decoded_astc_block, (uint8_t*)&astc_data, false, 4, 4);
VALIDATE(success);
for (uint32_t y = 0; y < 4; y++)
@@ -3595,7 +3594,6 @@ namespace basisu
VALIDATE(temp_block_unpacked[y][x].c[3] == decoded_uastc_block[y][x].a);
}
}
-#endif
}
#endif
@@ -3789,8 +3787,9 @@ namespace basisu
{
uint64_t m_sel;
uint32_t m_ofs;
+ uint32_t m_pad; // avoid implicit padding for selector_bitsequence_hash
selector_bitsequence() { }
- selector_bitsequence(uint32_t bit_ofs, uint64_t sel) : m_sel(sel), m_ofs(bit_ofs) { }
+ selector_bitsequence(uint32_t bit_ofs, uint64_t sel) : m_sel(sel), m_ofs(bit_ofs), m_pad(0) { }
bool operator== (const selector_bitsequence& other) const
{
return (m_ofs == other.m_ofs) && (m_sel == other.m_sel);
@@ -3811,7 +3810,7 @@ namespace basisu
{
std::size_t operator()(selector_bitsequence const& s) const noexcept
{
- return static_cast<std::size_t>(hash_hsieh((uint8_t *)&s, sizeof(s)) ^ s.m_sel);
+ return hash_hsieh((const uint8_t*)&s, sizeof(s));
}
};
diff --git a/thirdparty/basis_universal/encoder/basisu_uastc_enc.h b/thirdparty/basis_universal/encoder/basisu_uastc_enc.h
index ba39a558b3..54d39380e6 100644
--- a/thirdparty/basis_universal/encoder/basisu_uastc_enc.h
+++ b/thirdparty/basis_universal/encoder/basisu_uastc_enc.h
@@ -1,5 +1,5 @@
// basisu_uastc_enc.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/cppspmd_flow.h b/thirdparty/basis_universal/encoder/cppspmd_flow.h
index f6930476aa..93934173c4 100644
--- a/thirdparty/basis_universal/encoder/cppspmd_flow.h
+++ b/thirdparty/basis_universal/encoder/cppspmd_flow.h
@@ -1,7 +1,7 @@
// Do not include this header directly.
// Control flow functionality in common between all the headers.
//
-// Copyright 2020-2021 Binomial LLC
+// Copyright 2020-2024 Binomial LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/cppspmd_math.h b/thirdparty/basis_universal/encoder/cppspmd_math.h
index e7b3202b8e..3032df865f 100644
--- a/thirdparty/basis_universal/encoder/cppspmd_math.h
+++ b/thirdparty/basis_universal/encoder/cppspmd_math.h
@@ -1,6 +1,6 @@
// Do not include this header directly.
//
-// Copyright 2020-2021 Binomial LLC
+// Copyright 2020-2024 Binomial LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -646,7 +646,7 @@ CPPSPMD_FORCE_INLINE vint spmd_kernel::count_set_bits(vint x)
{
vint v = x - (VUINT_SHIFT_RIGHT(x, 1) & 0x55555555);
vint v1 = (v & 0x33333333) + (VUINT_SHIFT_RIGHT(v, 2) & 0x33333333);
- return VUINT_SHIFT_RIGHT(((v1 + VUINT_SHIFT_RIGHT(v1, 4) & 0xF0F0F0F) * 0x1010101), 24);
+ return VUINT_SHIFT_RIGHT(((v1 + (VUINT_SHIFT_RIGHT(v1, 4) & 0xF0F0F0F)) * 0x1010101), 24);
}
CPPSPMD_FORCE_INLINE vint cmple_epu16(const vint &a, const vint &b)
diff --git a/thirdparty/basis_universal/encoder/cppspmd_math_declares.h b/thirdparty/basis_universal/encoder/cppspmd_math_declares.h
index cdb6447b62..f76c9b7e38 100644
--- a/thirdparty/basis_universal/encoder/cppspmd_math_declares.h
+++ b/thirdparty/basis_universal/encoder/cppspmd_math_declares.h
@@ -1,7 +1,7 @@
// Do not include this header directly.
// This header defines shared struct spmd_kernel helpers.
//
-// Copyright 2020-2021 Binomial LLC
+// Copyright 2020-2024 Binomial LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/cppspmd_sse.h b/thirdparty/basis_universal/encoder/cppspmd_sse.h
index 4c61bab7b1..79dfa1561a 100644
--- a/thirdparty/basis_universal/encoder/cppspmd_sse.h
+++ b/thirdparty/basis_universal/encoder/cppspmd_sse.h
@@ -450,7 +450,7 @@ struct spmd_kernel
CPPSPMD_FORCE_INLINE explicit operator vint() const;
private:
- vbool& operator=(const vbool&);
+ //vbool& operator=(const vbool&);
};
friend vbool operator!(const vbool& v);
@@ -481,7 +481,7 @@ struct spmd_kernel
CPPSPMD_FORCE_INLINE explicit vfloat(int value) : m_value(_mm_set1_ps((float)value)) { }
private:
- vfloat& operator=(const vfloat&);
+ //vfloat& operator=(const vfloat&);
};
CPPSPMD_FORCE_INLINE vfloat& store(vfloat& dst, const vfloat& src)
@@ -514,7 +514,7 @@ struct spmd_kernel
float* m_pValue;
private:
- float_lref& operator=(const float_lref&);
+ //float_lref& operator=(const float_lref&);
};
CPPSPMD_FORCE_INLINE const float_lref& store(const float_lref& dst, const vfloat& src)
@@ -561,7 +561,7 @@ struct spmd_kernel
float* m_pValue;
private:
- float_vref& operator=(const float_vref&);
+ //float_vref& operator=(const float_vref&);
};
// Varying ref to varying float
@@ -571,7 +571,7 @@ struct spmd_kernel
vfloat* m_pValue;
private:
- vfloat_vref& operator=(const vfloat_vref&);
+ //vfloat_vref& operator=(const vfloat_vref&);
};
// Varying ref to varying int
@@ -581,7 +581,7 @@ struct spmd_kernel
vint* m_pValue;
private:
- vint_vref& operator=(const vint_vref&);
+ //vint_vref& operator=(const vint_vref&);
};
CPPSPMD_FORCE_INLINE const float_vref& store(const float_vref& dst, const vfloat& src);
@@ -624,7 +624,7 @@ struct spmd_kernel
int* m_pValue;
private:
- int_lref& operator=(const int_lref&);
+ //int_lref& operator=(const int_lref&);
};
CPPSPMD_FORCE_INLINE const int_lref& store(const int_lref& dst, const vint& src)
@@ -663,7 +663,7 @@ struct spmd_kernel
int16_t* m_pValue;
private:
- int16_lref& operator=(const int16_lref&);
+ //int16_lref& operator=(const int16_lref&);
};
CPPSPMD_FORCE_INLINE const int16_lref& store(const int16_lref& dst, const vint& src)
@@ -720,7 +720,7 @@ struct spmd_kernel
const int* m_pValue;
private:
- cint_lref& operator=(const cint_lref&);
+ //cint_lref& operator=(const cint_lref&);
};
CPPSPMD_FORCE_INLINE vint load(const cint_lref& src)
@@ -742,7 +742,7 @@ struct spmd_kernel
int* m_pValue;
private:
- int_vref& operator=(const int_vref&);
+ //int_vref& operator=(const int_vref&);
};
// Varying ref to constant ints
@@ -752,7 +752,7 @@ struct spmd_kernel
const int* m_pValue;
private:
- cint_vref& operator=(const cint_vref&);
+ //cint_vref& operator=(const cint_vref&);
};
// Varying int
@@ -810,7 +810,7 @@ struct spmd_kernel
}
private:
- vint& operator=(const vint&);
+ //vint& operator=(const vint&);
};
// Load/store linear int
@@ -1206,7 +1206,7 @@ struct spmd_kernel
CPPSPMD_FORCE_INLINE vint load_all(const vint_vref& src)
{
// TODO: There's surely a better way
- __m128i k;
+ __m128i k = _mm_setzero_si128();
k = insert_x(k, ((int*)(&src.m_pValue[extract_x(src.m_vindex)]))[0]);
k = insert_y(k, ((int*)(&src.m_pValue[extract_y(src.m_vindex)]))[1]);
@@ -1261,7 +1261,7 @@ struct spmd_kernel
}
private:
- lint& operator=(const lint&);
+ //lint& operator=(const lint&);
};
CPPSPMD_FORCE_INLINE lint& store_all(lint& dst, const lint& src)
diff --git a/thirdparty/basis_universal/encoder/cppspmd_type_aliases.h b/thirdparty/basis_universal/encoder/cppspmd_type_aliases.h
index 0dfb28b88f..2600481239 100644
--- a/thirdparty/basis_universal/encoder/cppspmd_type_aliases.h
+++ b/thirdparty/basis_universal/encoder/cppspmd_type_aliases.h
@@ -1,7 +1,7 @@
// cppspmd_type_aliases.h
// Do not include this file directly
//
-// Copyright 2020-2021 Binomial LLC
+// Copyright 2020-2024 Binomial LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/encoder/pvpngreader.cpp b/thirdparty/basis_universal/encoder/pvpngreader.cpp
index 46639f2796..6b32f66cbe 100644
--- a/thirdparty/basis_universal/encoder/pvpngreader.cpp
+++ b/thirdparty/basis_universal/encoder/pvpngreader.cpp
@@ -163,7 +163,7 @@ public:
{
if ((sizeof(size_t) == sizeof(uint32_t)) && (new_size > 0x7FFFFFFFUL))
return 0;
- m_buf.resize(new_size);
+ m_buf.resize((size_t)new_size);
}
memcpy(&m_buf[(size_t)m_ofs], pBuf, len);
@@ -178,11 +178,11 @@ public:
return 0;
uint64_t max_bytes = minimum<uint64_t>(len, m_buf.size() - m_ofs);
- memcpy(pBuf, &m_buf[(size_t)m_ofs], max_bytes);
+ memcpy(pBuf, &m_buf[(size_t)m_ofs], (size_t)max_bytes);
m_ofs += max_bytes;
- return max_bytes;
+ return (size_t)max_bytes;
}
};
@@ -249,11 +249,11 @@ public:
return 0;
uint64_t max_bytes = minimum<uint64_t>(len, m_buf_size - m_ofs);
- memcpy(pBuf, &m_pBuf[(size_t)m_ofs], max_bytes);
+ memcpy(pBuf, &m_pBuf[(size_t)m_ofs], (size_t)max_bytes);
m_ofs += max_bytes;
- return max_bytes;
+ return (size_t)max_bytes;
}
};
@@ -1626,8 +1626,8 @@ int png_decoder::png_decode_start()
if (m_ihdr.m_ilace_type == 1)
{
- int i;
- uint32_t total_lines, lines_processed;
+ //int i;
+ //uint32_t total_lines, lines_processed;
m_adam7_pass_size_x[0] = adam7_pass_size(m_ihdr.m_width, 0, 8);
m_adam7_pass_size_x[1] = adam7_pass_size(m_ihdr.m_width, 4, 8);
@@ -1651,10 +1651,12 @@ int png_decoder::png_decode_start()
m_pass_y_left = 0;
+#if 0
total_lines = lines_processed = 0;
for (i = 0; i < 7; i++)
total_lines += m_adam7_pass_size_y[i];
+#endif
for (; ; )
{
@@ -1675,7 +1677,7 @@ int png_decoder::png_decode_start()
}
}
- lines_processed++;
+ //lines_processed++;
}
m_adam7_decoded_flag = TRUE;
diff --git a/modules/basis_universal/patches/external-jpgd.patch b/thirdparty/basis_universal/patches/external-jpgd.patch
index 7a805d00cb..7a805d00cb 100644
--- a/modules/basis_universal/patches/external-jpgd.patch
+++ b/thirdparty/basis_universal/patches/external-jpgd.patch
diff --git a/thirdparty/basis_universal/patches/external-tinyexr.patch b/thirdparty/basis_universal/patches/external-tinyexr.patch
new file mode 100644
index 0000000000..665af13300
--- /dev/null
+++ b/thirdparty/basis_universal/patches/external-tinyexr.patch
@@ -0,0 +1,23 @@
+diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp
+index 6c0ac0ad370..2bf486a0287 100644
+--- a/thirdparty/basis_universal/encoder/basisu_enc.cpp
++++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp
+@@ -27,7 +27,7 @@
+ #ifndef TINYEXR_USE_ZFP
+ #define TINYEXR_USE_ZFP (1)
+ #endif
+-#include "3rdparty/tinyexr.h"
++#include <tinyexr.h>
+
+ #ifndef MINIZ_HEADER_FILE_ONLY
+ #define MINIZ_HEADER_FILE_ONLY
+@@ -3257,7 +3257,8 @@ namespace basisu
+ float* out_rgba = nullptr;
+ const char* err = nullptr;
+
+- int status = LoadEXRWithLayer(&out_rgba, &width, &height, pFilename, nullptr, &err, &n_chans);
++ int status = LoadEXRWithLayer(&out_rgba, &width, &height, pFilename, nullptr, &err);
++ n_chans = 4;
+ if (status != 0)
+ {
+ error_printf("Failed loading .EXR image \"%s\"! (TinyEXR error: %s)\n", pFilename, err ? err : "?");
diff --git a/thirdparty/basis_universal/patches/remove-tinydds-qoi.patch b/thirdparty/basis_universal/patches/remove-tinydds-qoi.patch
new file mode 100644
index 0000000000..a4d176602d
--- /dev/null
+++ b/thirdparty/basis_universal/patches/remove-tinydds-qoi.patch
@@ -0,0 +1,446 @@
+diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp
+index 2bf486a0287..fff98e83014 100644
+--- a/thirdparty/basis_universal/encoder/basisu_enc.cpp
++++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp
+@@ -37,9 +37,6 @@
+ #endif
+ #include "basisu_miniz.h"
+
+-#define QOI_IMPLEMENTATION
+-#include "3rdparty/qoi.h"
+-
+ #if defined(_WIN32)
+ // For QueryPerformanceCounter/QueryPerformanceFrequency
+ #define WIN32_LEAN_AND_MEAN
+@@ -408,16 +405,7 @@ namespace basisu
+
+ bool load_qoi(const char* pFilename, image& img)
+ {
+- qoi_desc desc;
+- clear_obj(desc);
+-
+- void* p = qoi_read(pFilename, &desc, 4);
+- if (!p)
+- return false;
+-
+- img.grant_ownership(static_cast<color_rgba *>(p), desc.width, desc.height);
+-
+- return true;
++ return false;
+ }
+
+ bool load_png(const uint8_t *pBuf, size_t buf_size, image &img, const char *pFilename)
+diff --git a/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp b/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp
+index 000869a5337..342446b8fd4 100644
+--- a/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp
++++ b/thirdparty/basis_universal/encoder/basisu_gpu_texture.cpp
+@@ -19,9 +19,6 @@
+ #include "basisu_bc7enc.h"
+ #include "../transcoder/basisu_astc_hdr_core.h"
+
+-#define TINYDDS_IMPLEMENTATION
+-#include "3rdparty/tinydds.h"
+-
+ namespace basisu
+ {
+ //------------------------------------------------------------------------------------------------
+@@ -1979,208 +1976,8 @@ namespace basisu
+ // Accepts 2D, 2D mipmapped, 2D array, 2D array mipmapped
+ // and cubemap, cubemap mipmapped, and cubemap array mipmapped.
+ bool write_dds_file(uint8_vec &dds_data, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format)
+- {
+- if (!gpu_images.size())
+- {
+- assert(0);
+- return false;
+- }
+-
+- // Sanity check the input
+- uint32_t slices = 1;
+- if (cubemap_flag)
+- {
+- if ((gpu_images.size() % 6) != 0)
+- {
+- assert(0);
+- return false;
+- }
+- slices = gpu_images.size() / 6;
+- }
+- else
+- {
+- slices = gpu_images.size();
+- }
+-
+- uint32_t width = 0, height = 0, total_levels = 0;
+- basisu::texture_format fmt = texture_format::cInvalidTextureFormat;
+-
+- // Sanity check the input for consistent # of dimensions and mip levels
+- for (uint32_t array_index = 0; array_index < gpu_images.size(); array_index++)
+- {
+- const gpu_image_vec& levels = gpu_images[array_index];
+-
+- if (!levels.size())
+- {
+- // Empty mip chain
+- assert(0);
+- return false;
+- }
+-
+- if (!array_index)
+- {
+- width = levels[0].get_pixel_width();
+- height = levels[0].get_pixel_height();
+- total_levels = (uint32_t)levels.size();
+- fmt = levels[0].get_format();
+- }
+- else
+- {
+- if ((width != levels[0].get_pixel_width()) ||
+- (height != levels[0].get_pixel_height()) ||
+- (total_levels != levels.size()))
+- {
+- // All cubemap/texture array faces must be the same dimension
+- assert(0);
+- return false;
+- }
+- }
+-
+- for (uint32_t level_index = 0; level_index < levels.size(); level_index++)
+- {
+- if (level_index)
+- {
+- if ((levels[level_index].get_pixel_width() != maximum<uint32_t>(1, levels[0].get_pixel_width() >> level_index)) ||
+- (levels[level_index].get_pixel_height() != maximum<uint32_t>(1, levels[0].get_pixel_height() >> level_index)))
+- {
+- // Malformed mipmap chain
+- assert(0);
+- return false;
+- }
+- }
+-
+- if (fmt != levels[level_index].get_format())
+- {
+- // All input textures must use the same GPU format
+- assert(0);
+- return false;
+- }
+- }
+- }
+-
+- // No mipmap levels
+- if (!total_levels)
+- {
+- assert(0);
+- return false;
+- }
+-
+- // Create the DDS mipmap level data
+- uint8_vec mipmaps[32];
+-
+- // See https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-file-layout-for-cubic-environment-maps
+- // DDS cubemap organization is cubemap face 0 followed by all mips, then cubemap face 1 followed by all mips, etc.
+- // Unfortunately tinydds.h's writer doesn't handle this case correctly, so we work around it here.
+- // This also applies with 2D texture arrays, too. RenderDoc and ddsview (DirectXTex) views each type (cubemap array and 2D texture array) correctly.
+- // Also see "Using Texture Arrays in Direct3D 10/11":
+- // https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide
+- for (uint32_t array_index = 0; array_index < gpu_images.size(); array_index++)
+- {
+- const gpu_image_vec& levels = gpu_images[array_index];
+-
+- for (uint32_t level_index = 0; level_index < levels.size(); level_index++)
+- {
+- append_vector(mipmaps[0], (uint8_t*)levels[level_index].get_ptr(), levels[level_index].get_size_in_bytes());
+-
+- } // level_index
+- } // array_index
+-
+-#if 0
+- // This organization, required by tinydds.h's API, is wrong.
+- {
+- for (uint32_t array_index = 0; array_index < gpu_images.size(); array_index++)
+- {
+- const gpu_image_vec& levels = gpu_images[array_index];
+-
+- for (uint32_t level_index = 0; level_index < levels.size(); level_index++)
+- {
+- append_vector(mipmaps[level_index], (uint8_t*)levels[level_index].get_ptr(), levels[level_index].get_size_in_bytes());
+-
+- } // level_index
+- } // array_index
+- }
+-#endif
+-
+- // Write DDS file using tinydds
+- TinyDDS_WriteCallbacks cbs;
+- cbs.error = [](void* user, char const* msg) { BASISU_NOTE_UNUSED(user); fprintf(stderr, "tinydds: %s\n", msg); };
+- cbs.alloc = [](void* user, size_t size) -> void* { BASISU_NOTE_UNUSED(user); return malloc(size); };
+- cbs.free = [](void* user, void* memory) { BASISU_NOTE_UNUSED(user); free(memory); };
+- cbs.write = [](void* user, void const* buffer, size_t byteCount) { BASISU_NOTE_UNUSED(user); uint8_vec* pVec = (uint8_vec*)user; append_vector(*pVec, (const uint8_t*)buffer, byteCount); };
+-
+- uint32_t mipmap_sizes[32];
+- const void* mipmap_ptrs[32];
+-
+- clear_obj(mipmap_sizes);
+- clear_obj(mipmap_ptrs);
+-
+- assert(total_levels < 32);
+- for (uint32_t i = 0; i < total_levels; i++)
+- {
+- mipmap_sizes[i] = mipmaps[i].size_in_bytes();
+- mipmap_ptrs[i] = mipmaps[i].get_ptr();
+- }
+-
+- // Select tinydds texture format
+- uint32_t tinydds_fmt = 0;
+-
+- switch (fmt)
+- {
+- case texture_format::cBC1_NV:
+- case texture_format::cBC1_AMD:
+- case texture_format::cBC1:
+- tinydds_fmt = use_srgb_format ? TDDS_BC1_RGBA_SRGB_BLOCK : TDDS_BC1_RGBA_UNORM_BLOCK;
+- break;
+- case texture_format::cBC3:
+- tinydds_fmt = use_srgb_format ? TDDS_BC3_SRGB_BLOCK : TDDS_BC3_UNORM_BLOCK;
+- break;
+- case texture_format::cBC4:
+- tinydds_fmt = TDDS_BC4_UNORM_BLOCK;
+- break;
+- case texture_format::cBC5:
+- tinydds_fmt = TDDS_BC5_UNORM_BLOCK;
+- break;
+- case texture_format::cBC6HSigned:
+- tinydds_fmt = TDDS_BC6H_SFLOAT_BLOCK;
+- break;
+- case texture_format::cBC6HUnsigned:
+- tinydds_fmt = TDDS_BC6H_UFLOAT_BLOCK;
+- break;
+- case texture_format::cBC7:
+- tinydds_fmt = use_srgb_format ? TDDS_BC7_SRGB_BLOCK : TDDS_BC7_UNORM_BLOCK;
+- break;
+- default:
+- {
+- fprintf(stderr, "Warning: Unsupported format in write_dds_file().\n");
+- return false;
+- }
+- }
+-
+- // DirectXTex's DDSView doesn't handle odd sizes textures correctly. RenderDoc loads them fine, however.
+- // Trying to work around this here results in invalid mipmaps.
+- //width = (width + 3) & ~3;
+- //height = (height + 3) & ~3;
+-
+- bool status = TinyDDS_WriteImage(&cbs,
+- &dds_data,
+- width,
+- height,
+- 1,
+- slices,
+- total_levels,
+- (TinyDDS_Format)tinydds_fmt,
+- cubemap_flag,
+- true,
+- mipmap_sizes,
+- mipmap_ptrs);
+-
+- if (!status)
+- {
+- fprintf(stderr, "write_dds_file: Failed creating DDS file\n");
+- return false;
+- }
+-
+- return true;
++ {
++ return false;
+ }
+
+ bool write_dds_file(const char* pFilename, const basisu::vector<gpu_image_vec>& gpu_images, bool cubemap_flag, bool use_srgb_format)
+@@ -2201,188 +1998,6 @@ namespace basisu
+
+ bool read_uncompressed_dds_file(const char* pFilename, basisu::vector<image> &ldr_mips, basisu::vector<imagef>& hdr_mips)
+ {
+- const uint32_t MAX_IMAGE_DIM = 16384;
+-
+- TinyDDS_Callbacks cbs;
+-
+- cbs.errorFn = [](void* user, char const* msg) { BASISU_NOTE_UNUSED(user); fprintf(stderr, "tinydds: %s\n", msg); };
+- cbs.allocFn = [](void* user, size_t size) -> void* { BASISU_NOTE_UNUSED(user); return malloc(size); };
+- cbs.freeFn = [](void* user, void* memory) { BASISU_NOTE_UNUSED(user); free(memory); };
+- cbs.readFn = [](void* user, void* buffer, size_t byteCount) -> size_t { return (size_t)fread(buffer, 1, byteCount, (FILE*)user); };
+-
+-#ifdef _MSC_VER
+- cbs.seekFn = [](void* user, int64_t ofs) -> bool { return _fseeki64((FILE*)user, ofs, SEEK_SET) == 0; };
+- cbs.tellFn = [](void* user) -> int64_t { return _ftelli64((FILE*)user); };
+-#else
+- cbs.seekFn = [](void* user, int64_t ofs) -> bool { return fseek((FILE*)user, (long)ofs, SEEK_SET) == 0; };
+- cbs.tellFn = [](void* user) -> int64_t { return (int64_t)ftell((FILE*)user); };
+-#endif
+-
+- FILE* pFile = fopen_safe(pFilename, "rb");
+- if (!pFile)
+- {
+- error_printf("Can't open .DDS file \"%s\"\n", pFilename);
+- return false;
+- }
+-
+- // These are the formats AMD Compressonator supports in its UI.
+- enum dds_fmt
+- {
+- cRGBA32,
+- cRGBA_HALF,
+- cRGBA_FLOAT
+- };
+-
+- bool status = false;
+- dds_fmt fmt = cRGBA32;
+- uint32_t width = 0, height = 0;
+- bool hdr_flag = false;
+- TinyDDS_Format tfmt = TDDS_UNDEFINED;
+-
+- TinyDDS_ContextHandle ctx = TinyDDS_CreateContext(&cbs, pFile);
+- if (!ctx)
+- goto failure;
+-
+- status = TinyDDS_ReadHeader(ctx);
+- if (!status)
+- {
+- error_printf("Failed parsing DDS header in file \"%s\"\n", pFilename);
+- goto failure;
+- }
+-
+- if ((!TinyDDS_Is2D(ctx)) || (TinyDDS_ArraySlices(ctx) > 1) || (TinyDDS_IsCubemap(ctx)))
+- {
+- error_printf("Unsupported DDS texture type in file \"%s\"\n", pFilename);
+- goto failure;
+- }
+-
+- width = TinyDDS_Width(ctx);
+- height = TinyDDS_Height(ctx);
+-
+- if (!width || !height)
+- {
+- error_printf("DDS texture dimensions invalid in file \"%s\"\n", pFilename);
+- goto failure;
+- }
+-
+- if ((width > MAX_IMAGE_DIM) || (height > MAX_IMAGE_DIM))
+- {
+- error_printf("DDS texture dimensions too large in file \"%s\"\n", pFilename);
+- goto failure;
+- }
+-
+- tfmt = TinyDDS_GetFormat(ctx);
+- switch (tfmt)
+- {
+- case TDDS_R8G8B8A8_SRGB:
+- case TDDS_R8G8B8A8_UNORM:
+- case TDDS_B8G8R8A8_SRGB:
+- case TDDS_B8G8R8A8_UNORM:
+- fmt = cRGBA32;
+- break;
+- case TDDS_R16G16B16A16_SFLOAT:
+- fmt = cRGBA_HALF;
+- hdr_flag = true;
+- break;
+- case TDDS_R32G32B32A32_SFLOAT:
+- fmt = cRGBA_FLOAT;
+- hdr_flag = true;
+- break;
+- default:
+- error_printf("File \"%s\" has an unsupported DDS texture format (only supports RGBA/BGRA 32bpp, RGBA HALF float, or RGBA FLOAT)\n", pFilename);
+- goto failure;
+- }
+-
+- if (hdr_flag)
+- hdr_mips.resize(TinyDDS_NumberOfMipmaps(ctx));
+- else
+- ldr_mips.resize(TinyDDS_NumberOfMipmaps(ctx));
+-
+- for (uint32_t level = 0; level < TinyDDS_NumberOfMipmaps(ctx); level++)
+- {
+- const uint32_t level_width = TinyDDS_MipMapReduce(width, level);
+- const uint32_t level_height = TinyDDS_MipMapReduce(height, level);
+- const uint32_t total_level_texels = level_width * level_height;
+-
+- const void* pImage = TinyDDS_ImageRawData(ctx, level);
+- const uint32_t image_size = TinyDDS_ImageSize(ctx, level);
+-
+- if (fmt == cRGBA32)
+- {
+- ldr_mips[level].resize(level_width, level_height);
+-
+- if ((ldr_mips[level].get_total_pixels() * sizeof(uint32_t) != image_size))
+- {
+- assert(0);
+- goto failure;
+- }
+-
+- memcpy(ldr_mips[level].get_ptr(), pImage, image_size);
+-
+- if ((tfmt == TDDS_B8G8R8A8_SRGB) || (tfmt == TDDS_B8G8R8A8_UNORM))
+- {
+- // Swap R and B components.
+- uint32_t *pTexels = (uint32_t *)ldr_mips[level].get_ptr();
+- for (uint32_t i = 0; i < total_level_texels; i++)
+- {
+- const uint32_t v = pTexels[i];
+- const uint32_t r = (v >> 16) & 0xFF;
+- const uint32_t b = v & 0xFF;
+- pTexels[i] = r | (b << 16) | (v & 0xFF00FF00);
+- }
+- }
+- }
+- else if (fmt == cRGBA_FLOAT)
+- {
+- hdr_mips[level].resize(level_width, level_height);
+-
+- if ((hdr_mips[level].get_total_pixels() * sizeof(float) * 4 != image_size))
+- {
+- assert(0);
+- goto failure;
+- }
+-
+- memcpy(hdr_mips[level].get_ptr(), pImage, image_size);
+- }
+- else if (fmt == cRGBA_HALF)
+- {
+- hdr_mips[level].resize(level_width, level_height);
+-
+- if ((hdr_mips[level].get_total_pixels() * sizeof(basist::half_float) * 4 != image_size))
+- {
+- assert(0);
+- goto failure;
+- }
+-
+- // Unpack half to float.
+- const basist::half_float* pSrc_comps = static_cast<const basist::half_float*>(pImage);
+- vec4F* pDst_texels = hdr_mips[level].get_ptr();
+-
+- for (uint32_t i = 0; i < total_level_texels; i++)
+- {
+- (*pDst_texels)[0] = basist::half_to_float(pSrc_comps[0]);
+- (*pDst_texels)[1] = basist::half_to_float(pSrc_comps[1]);
+- (*pDst_texels)[2] = basist::half_to_float(pSrc_comps[2]);
+- (*pDst_texels)[3] = basist::half_to_float(pSrc_comps[3]);
+-
+- pSrc_comps += 4;
+- pDst_texels++;
+- } // y
+- }
+- } // level
+-
+- TinyDDS_DestroyContext(ctx);
+- fclose(pFile);
+-
+- return true;
+-
+- failure:
+- if (ctx)
+- TinyDDS_DestroyContext(ctx);
+-
+- if (pFile)
+- fclose(pFile);
+-
+ return false;
+ }
+
diff --git a/thirdparty/basis_universal/transcoder/basisu.h b/thirdparty/basis_universal/transcoder/basisu.h
index 1230b59ec6..939ee79e62 100644
--- a/thirdparty/basis_universal/transcoder/basisu.h
+++ b/thirdparty/basis_universal/transcoder/basisu.h
@@ -1,5 +1,5 @@
// basisu.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
// Important: If compiling with gcc, be sure strict aliasing is disabled: -fno-strict-aliasing
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -117,13 +117,26 @@ namespace basisu
typedef basisu::vector<uint64_t> uint64_vec;
typedef basisu::vector<int> int_vec;
typedef basisu::vector<bool> bool_vec;
+ typedef basisu::vector<float> float_vec;
void enable_debug_printf(bool enabled);
void debug_printf(const char *pFmt, ...);
-
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
+
template <typename T> inline void clear_obj(T& obj) { memset(&obj, 0, sizeof(obj)); }
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#endif
+
template <typename T0, typename T1> inline T0 lerp(T0 a, T0 b, T1 c) { return a + (b - a) * c; }
template <typename S> inline S maximum(S a, S b) { return (a > b) ? a : b; }
@@ -162,10 +175,45 @@ namespace basisu
template<typename T> inline T open_range_check(T v, T minv, T maxv) { assert(v >= minv && v < maxv); BASISU_NOTE_UNUSED(minv); BASISU_NOTE_UNUSED(maxv); return v; }
template<typename T> inline T open_range_check(T v, T maxv) { assert(v < maxv); BASISU_NOTE_UNUSED(maxv); return v; }
+ // Open interval
+ inline bool in_bounds(int v, int l, int h)
+ {
+ return (v >= l) && (v < h);
+ }
+
+ // Closed interval
+ inline bool in_range(int v, int l, int h)
+ {
+ return (v >= l) && (v <= h);
+ }
+
inline uint32_t total_bits(uint32_t v) { uint32_t l = 0; for ( ; v > 0U; ++l) v >>= 1; return l; }
template<typename T> inline T saturate(T val) { return clamp(val, 0.0f, 1.0f); }
+ inline uint32_t get_bit(uint32_t src, int ndx)
+ {
+ assert(in_bounds(ndx, 0, 32));
+ return (src >> ndx) & 1;
+ }
+
+ inline bool is_bit_set(uint32_t src, int ndx)
+ {
+ return get_bit(src, ndx) != 0;
+ }
+
+ inline uint32_t get_bits(uint32_t val, int low, int high)
+ {
+ const int num_bits = (high - low) + 1;
+ assert(in_range(num_bits, 1, 32));
+
+ val >>= low;
+ if (num_bits != 32)
+ val &= ((1u << num_bits) - 1);
+
+ return val;
+ }
+
template<typename T, typename R> inline void append_vector(T &vec, const R *pObjs, size_t n)
{
if (n)
@@ -267,6 +315,11 @@ namespace basisu
return true;
}
+ static inline uint32_t read_le_word(const uint8_t* pBytes)
+ {
+ return (pBytes[1] << 8U) | (pBytes[0]);
+ }
+
static inline uint32_t read_le_dword(const uint8_t *pBytes)
{
return (pBytes[3] << 24U) | (pBytes[2] << 16U) | (pBytes[1] << 8U) | (pBytes[0]);
@@ -303,6 +356,10 @@ namespace basisu
return *this;
}
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
inline operator uint32_t() const
{
switch (NumBytes)
@@ -354,6 +411,9 @@ namespace basisu
}
}
}
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
};
enum eZero { cZero };
@@ -402,8 +462,11 @@ namespace basisu
cBC3, // DXT5 (BC4/DXT5A block followed by a BC1/DXT1 block)
cBC4, // DXT5A
cBC5, // 3DC/DXN (two BC4/DXT5A blocks)
+ cBC6HSigned, // HDR
+ cBC6HUnsigned, // HDR
cBC7,
- cASTC4x4, // LDR only
+ cASTC_LDR_4x4, // ASTC 4x4 LDR only
+ cASTC_HDR_4x4, // ASTC 4x4 HDR only (but may use LDR ASTC blocks internally)
cPVRTC1_4_RGB,
cPVRTC1_4_RGBA,
cATC_RGB,
@@ -413,17 +476,22 @@ namespace basisu
cETC2_R11_EAC,
cETC2_RG11_EAC,
cUASTC4x4,
+ cUASTC_HDR_4x4,
cBC1_NV,
cBC1_AMD,
-
+
// Uncompressed/raw pixels
cRGBA32,
cRGB565,
cBGR565,
cRGBA4444,
- cABGR4444
+ cABGR4444,
+ cRGBA_HALF,
+ cRGB_HALF,
+ cRGB_9E5
};
+ // This is bytes per block for GPU formats, or bytes per texel for uncompressed formats.
inline uint32_t get_bytes_per_block(texture_format fmt)
{
switch (fmt)
@@ -443,13 +511,27 @@ namespace basisu
case texture_format::cETC2_R11_EAC:
return 8;
case texture_format::cRGBA32:
- return sizeof(uint32_t) * 16;
+ case texture_format::cRGB_9E5:
+ return sizeof(uint32_t);
+ case texture_format::cRGB_HALF:
+ return sizeof(uint16_t) * 3;
+ case texture_format::cRGBA_HALF:
+ return sizeof(uint16_t) * 4;
+ case texture_format::cRGB565:
+ case texture_format::cBGR565:
+ case texture_format::cRGBA4444:
+ case texture_format::cABGR4444:
+ return sizeof(uint16_t);
+
default:
break;
}
+
+ // Everything else is 16 bytes/block.
return 16;
}
+ // This is qwords per block for GPU formats, or not valid for uncompressed formats.
inline uint32_t get_qwords_per_block(texture_format fmt)
{
return get_bytes_per_block(fmt) >> 3;
@@ -473,6 +555,17 @@ namespace basisu
BASISU_NOTE_UNUSED(fmt);
return 4;
}
+
+ inline bool is_hdr_texture_format(texture_format fmt)
+ {
+ if (fmt == texture_format::cASTC_HDR_4x4)
+ return true;
+ if (fmt == texture_format::cUASTC_HDR_4x4)
+ return true;
+ if ((fmt == texture_format::cBC6HSigned) || (fmt == texture_format::cBC6HUnsigned))
+ return true;
+ return false;
+ }
} // namespace basisu
diff --git a/thirdparty/basis_universal/transcoder/basisu_astc_hdr_core.h b/thirdparty/basis_universal/transcoder/basisu_astc_hdr_core.h
new file mode 100644
index 0000000000..82dcd2bfe1
--- /dev/null
+++ b/thirdparty/basis_universal/transcoder/basisu_astc_hdr_core.h
@@ -0,0 +1,102 @@
+// File: basisu_astc_hdr_core.h
+#pragma once
+#include "basisu_astc_helpers.h"
+
+namespace basist
+{
+ struct astc_blk
+ {
+ uint8_t m_vals[16];
+ };
+
+ // ASTC_HDR_MAX_VAL is the maximum color component value that can be encoded.
+ // If the input has values higher than this, they need to be linearly scaled so all values are between [0,ASTC_HDR_MAX_VAL], and the linear scaling inverted in the shader.
+ const float ASTC_HDR_MAX_VAL = 65216.0f; // actually MAX_QLOG12_VAL
+
+ // Maximum usable QLOG encodings, and their floating point equivalent values, that don't result in NaN/Inf's.
+ const uint32_t MAX_QLOG7 = 123;
+ //const float MAX_QLOG7_VAL = 55296.0f;
+
+ const uint32_t MAX_QLOG8 = 247;
+ //const float MAX_QLOG8_VAL = 60416.0f;
+
+ const uint32_t MAX_QLOG9 = 495;
+ //const float MAX_QLOG9_VAL = 62976.0f;
+
+ const uint32_t MAX_QLOG10 = 991;
+ //const float MAX_QLOG10_VAL = 64256.0f;
+
+ const uint32_t MAX_QLOG11 = 1983;
+ //const float MAX_QLOG11_VAL = 64896.0f;
+
+ const uint32_t MAX_QLOG12 = 3967;
+ //const float MAX_QLOG12_VAL = 65216.0f;
+
+ const uint32_t MAX_QLOG16 = 63487;
+ const float MAX_QLOG16_VAL = 65504.0f;
+
+ const uint32_t NUM_MODE11_ENDPOINTS = 6, NUM_MODE7_ENDPOINTS = 4;
+
+ // Notes:
+ // qlog16_to_half(half_to_qlog16(half_val_as_int)) == half_val_as_int (is lossless)
+ // However, this is not lossless in the general sense.
+ inline half_float qlog16_to_half_slow(uint32_t qlog16)
+ {
+ assert(qlog16 <= 0xFFFF);
+
+ int C = qlog16;
+
+ int E = (C & 0xF800) >> 11;
+ int M = C & 0x7FF;
+
+ int Mt;
+ if (M < 512)
+ Mt = 3 * M;
+ else if (M >= 1536)
+ Mt = 5 * M - 2048;
+ else
+ Mt = 4 * M - 512;
+
+ int Cf = (E << 10) + (Mt >> 3);
+ return (half_float)Cf;
+ }
+
+ // This is not lossless
+ inline half_float qlog_to_half_slow(uint32_t qlog, uint32_t bits)
+ {
+ assert((bits >= 7U) && (bits <= 16U));
+ assert(qlog < (1U << bits));
+
+ int C = qlog << (16 - bits);
+ return qlog16_to_half_slow(C);
+ }
+
+ void astc_hdr_core_init();
+
+ void decode_mode7_to_qlog12_ise20(
+ const uint8_t* pEndpoints,
+ int e[2][3],
+ int* pScale);
+
+ bool decode_mode7_to_qlog12(
+ const uint8_t* pEndpoints,
+ int e[2][3],
+ int* pScale,
+ uint32_t ise_endpoint_range);
+
+ void decode_mode11_to_qlog12_ise20(
+ const uint8_t* pEndpoints,
+ int e[2][3]);
+
+ bool decode_mode11_to_qlog12(
+ const uint8_t* pEndpoints,
+ int e[2][3],
+ uint32_t ise_endpoint_range);
+
+ bool transcode_bc6h_1subset(half_float h_e[3][2], const astc_helpers::log_astc_block& best_blk, bc6h_block& transcoded_bc6h_blk);
+ bool transcode_bc6h_2subsets(uint32_t common_part_index, const astc_helpers::log_astc_block& best_blk, bc6h_block& transcoded_bc6h_blk);
+
+ bool astc_hdr_transcode_to_bc6h(const astc_blk& src_blk, bc6h_block& dst_blk);
+ bool astc_hdr_transcode_to_bc6h(const astc_helpers::log_astc_block& log_blk, bc6h_block& dst_blk);
+
+} // namespace basist
diff --git a/thirdparty/basis_universal/transcoder/basisu_astc_helpers.h b/thirdparty/basis_universal/transcoder/basisu_astc_helpers.h
new file mode 100644
index 0000000000..09a234b2ae
--- /dev/null
+++ b/thirdparty/basis_universal/transcoder/basisu_astc_helpers.h
@@ -0,0 +1,3587 @@
+// basisu_astc_helpers.h
+// Be sure to define ASTC_HELPERS_IMPLEMENTATION somewhere to get the implementation, otherwise you only get the header.
+#pragma once
+#ifndef BASISU_ASTC_HELPERS_HEADER
+#define BASISU_ASTC_HELPERS_HEADER
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+#include <fenv.h>
+
+namespace astc_helpers
+{
+ const uint32_t MAX_WEIGHT_VALUE = 64; // grid texel weights must range from [0,64]
+ const uint32_t MIN_GRID_DIM = 2; // the minimum dimension of a block's weight grid
+ const uint32_t MIN_BLOCK_DIM = 4, MAX_BLOCK_DIM = 12; // the valid block dimensions in texels
+ const uint32_t MAX_GRID_WEIGHTS = 64; // a block may have a maximum of 64 weight grid values
+
+ static const uint32_t NUM_ASTC_BLOCK_SIZES = 14;
+ extern const uint8_t g_astc_block_sizes[NUM_ASTC_BLOCK_SIZES][2];
+
+ // The Color Endpoint Modes (CEM's)
+ enum cems
+ {
+ CEM_LDR_LUM_DIRECT = 0,
+ CEM_LDR_LUM_BASE_PLUS_OFS = 1,
+ CEM_HDR_LUM_LARGE_RANGE = 2,
+ CEM_HDR_LUM_SMALL_RANGE = 3,
+ CEM_LDR_LUM_ALPHA_DIRECT = 4,
+ CEM_LDR_LUM_ALPHA_BASE_PLUS_OFS = 5,
+ CEM_LDR_RGB_BASE_SCALE = 6,
+ CEM_HDR_RGB_BASE_SCALE = 7,
+ CEM_LDR_RGB_DIRECT = 8,
+ CEM_LDR_RGB_BASE_PLUS_OFFSET = 9,
+ CEM_LDR_RGB_BASE_SCALE_PLUS_TWO_A = 10,
+ CEM_HDR_RGB = 11,
+ CEM_LDR_RGBA_DIRECT = 12,
+ CEM_LDR_RGBA_BASE_PLUS_OFFSET = 13,
+ CEM_HDR_RGB_LDR_ALPHA = 14,
+ CEM_HDR_RGB_HDR_ALPHA = 15
+ };
+
+ // All Bounded Integer Sequence Coding (BISE or ISE) ranges.
+ // Weights: Ranges [0,11] are valid.
+ // Endpoints: Ranges [4,20] are valid.
+ enum bise_levels
+ {
+ BISE_2_LEVELS = 0,
+ BISE_3_LEVELS = 1,
+ BISE_4_LEVELS = 2,
+ BISE_5_LEVELS = 3,
+ BISE_6_LEVELS = 4,
+ BISE_8_LEVELS = 5,
+ BISE_10_LEVELS = 6,
+ BISE_12_LEVELS = 7,
+ BISE_16_LEVELS = 8,
+ BISE_20_LEVELS = 9,
+ BISE_24_LEVELS = 10,
+ BISE_32_LEVELS = 11,
+ BISE_40_LEVELS = 12,
+ BISE_48_LEVELS = 13,
+ BISE_64_LEVELS = 14,
+ BISE_80_LEVELS = 15,
+ BISE_96_LEVELS = 16,
+ BISE_128_LEVELS = 17,
+ BISE_160_LEVELS = 18,
+ BISE_192_LEVELS = 19,
+ BISE_256_LEVELS = 20
+ };
+
+ const uint32_t TOTAL_ISE_RANGES = 21;
+
+ // Valid endpoint ISE ranges
+ const uint32_t FIRST_VALID_ENDPOINT_ISE_RANGE = BISE_6_LEVELS; // 4
+ const uint32_t LAST_VALID_ENDPOINT_ISE_RANGE = BISE_256_LEVELS; // 20
+ const uint32_t TOTAL_ENDPOINT_ISE_RANGES = LAST_VALID_ENDPOINT_ISE_RANGE - FIRST_VALID_ENDPOINT_ISE_RANGE + 1;
+
+ // Valid weight ISE ranges
+ const uint32_t FIRST_VALID_WEIGHT_ISE_RANGE = BISE_2_LEVELS; // 0
+ const uint32_t LAST_VALID_WEIGHT_ISE_RANGE = BISE_32_LEVELS; // 11
+ const uint32_t TOTAL_WEIGHT_ISE_RANGES = LAST_VALID_WEIGHT_ISE_RANGE - FIRST_VALID_WEIGHT_ISE_RANGE + 1;
+
+ // The ISE range table.
+ extern const int8_t g_ise_range_table[TOTAL_ISE_RANGES][3]; // 0=bits (0 to 8), 1=trits (0 or 1), 2=quints (0 or 1)
+
+ // Possible Color Component Select values, used in dual plane mode.
+ // The CCS component will be interpolated using the 2nd weight plane.
+ enum ccs
+ {
+ CCS_GBA_R = 0,
+ CCS_RBA_G = 1,
+ CCS_RGA_B = 2,
+ CCS_RGB_A = 3
+ };
+
+ struct astc_block
+ {
+ uint32_t m_vals[4];
+ };
+
+ const uint32_t MAX_PARTITIONS = 4; // Max # of partitions or subsets for single plane mode
+ const uint32_t MAX_DUAL_PLANE_PARTITIONS = 3; // Max # of partitions or subsets for dual plane mode
+ const uint32_t NUM_PARTITION_PATTERNS = 1024; // Total # of partition pattern seeds (10-bits)
+ const uint32_t MAX_ENDPOINTS = 18; // Maximum # of endpoint values in a block
+
+ struct log_astc_block
+ {
+ bool m_error_flag;
+
+ bool m_solid_color_flag_ldr, m_solid_color_flag_hdr;
+ uint16_t m_solid_color[4];
+
+ // Rest is only valid if !m_solid_color_flag_ldr && !m_solid_color_flag_hdr
+ uint32_t m_grid_width, m_grid_height; // weight grid dimensions, not the dimension of the block
+
+ bool m_dual_plane;
+
+ uint32_t m_weight_ise_range; // 0-11
+ uint32_t m_endpoint_ise_range; // 4-20, this is actually inferred from the size of the other config bits+weights, but this is here for checking
+
+ uint32_t m_color_component_selector; // 0-3, 0=GBA R, 1=RBA G, 2=RGA B, 3=RGB A, only used in dual plane mode
+
+ uint32_t m_num_partitions; // or the # of subsets, 1-4 (1-3 if dual plane mode)
+ uint32_t m_partition_id; // 10-bits, must be 0 if m_num_partitions==1
+
+ uint32_t m_color_endpoint_modes[MAX_PARTITIONS]; // each subset's CEM's
+
+ // ISE weight grid values. In dual plane mode, the order is p0,p1, p0,p1, etc.
+ uint8_t m_weights[MAX_GRID_WEIGHTS];
+
+ // ISE endpoint values
+ // Endpoint order examples:
+ // 1 subset LA : LL0 LH0 AL0 AH0
+ // 1 subset RGB : RL0 RH0 GL0 GH0 BL0 BH0
+ // 1 subset RGBA : RL0 RH0 GL0 GH0 BL0 BH0 AL0 AH0
+ // 2 subset LA : LL0 LH0 AL0 AH0 LL1 LH1 AL1 AH1
+ // 2 subset RGB : RL0 RH0 GL0 GH0 BL0 BH0 RL1 RH1 GL1 GH1 BL1 BH1
+ // 2 subset RGBA : RL0 RH0 GL0 GH0 BL0 BH0 AL0 AH0 RL1 RH1 GL1 GH1 BL1 BH1 AL1 AH1
+ uint8_t m_endpoints[MAX_ENDPOINTS];
+
+ void clear()
+ {
+ memset(this, 0, sizeof(*this));
+ }
+ };
+
+ // Open interval
+ inline int bounds_check(int v, int l, int h) { (void)v; (void)l; (void)h; assert(v >= l && v < h); return v; }
+ inline uint32_t bounds_check(uint32_t v, uint32_t l, uint32_t h) { (void)v; (void)l; (void)h; assert(v >= l && v < h); return v; }
+
+ inline uint32_t get_bits(uint32_t val, int low, int high)
+ {
+ const int num_bits = (high - low) + 1;
+ assert((num_bits >= 1) && (num_bits <= 32));
+
+ val >>= low;
+ if (num_bits != 32)
+ val &= ((1u << num_bits) - 1);
+
+ return val;
+ }
+
+ // Returns the number of levels in the given ISE range.
+ inline uint32_t get_ise_levels(uint32_t ise_range)
+ {
+ assert(ise_range < TOTAL_ISE_RANGES);
+ return (1 + 2 * g_ise_range_table[ise_range][1] + 4 * g_ise_range_table[ise_range][2]) << g_ise_range_table[ise_range][0];
+ }
+
+ inline int get_ise_sequence_bits(int count, int range)
+ {
+ // See 18.22 Data Size Determination
+ int total_bits = g_ise_range_table[range][0] * count;
+ total_bits += (g_ise_range_table[range][1] * 8 * count + 4) / 5;
+ total_bits += (g_ise_range_table[range][2] * 7 * count + 2) / 3;
+ return total_bits;
+ }
+
+ inline uint32_t weight_interpolate(uint32_t l, uint32_t h, uint32_t w)
+ {
+ assert(w <= MAX_WEIGHT_VALUE);
+ return (l * (64 - w) + h * w + 32) >> 6;
+ }
+
+ void encode_bise(uint32_t* pDst, const uint8_t* pSrc_vals, uint32_t bit_pos, int num_vals, int range);
+
+ // Packs a logical to physical ASTC block. Note this does not validate the block's dimensions (use is_valid_block_size()), just the grid dimensions.
+ bool pack_astc_block(astc_block &phys_block, const log_astc_block& log_block, int* pExpected_endpoint_range = nullptr);
+
+ // Pack LDR void extent (really solid color) blocks. For LDR, pass in (val | (val << 8)) for each component.
+ void pack_void_extent_ldr(astc_block& blk, uint16_t r, uint16_t g, uint16_t b, uint16_t a);
+
+ // Pack HDR void extent (16-bit values are FP16/half floats - no NaN/Inf's)
+ void pack_void_extent_hdr(astc_block& blk, uint16_t rh, uint16_t gh, uint16_t bh, uint16_t ah);
+
+ // These helpers are all quite slow, but are useful for table preparation.
+
+ // Dequantizes ISE encoded endpoint val to [0,255]
+ uint32_t dequant_bise_endpoint(uint32_t val, uint32_t ise_range); // ISE ranges 4-11
+
+ // Dequantizes ISE encoded weight val to [0,64]
+ uint32_t dequant_bise_weight(uint32_t val, uint32_t ise_range); // ISE ranges 0-10
+
+ uint32_t find_nearest_bise_endpoint(int v, uint32_t ise_range);
+ uint32_t find_nearest_bise_weight(int v, uint32_t ise_range);
+
+ void create_quant_tables(
+ uint8_t* pVal_to_ise, // [0-255] or [0-64] value to nearest ISE symbol, array size is [256] or [65]
+ uint8_t* pISE_to_val, // ASTC encoded ISE symbol to [0,255] or [0,64] value, [levels]
+ uint8_t* pISE_to_rank, // returns the level rank index given an ISE symbol, [levels]
+ uint8_t* pRank_to_ISE, // returns the ISE symbol given a level rank, inverse of pISE_to_rank, [levels]
+ uint32_t ise_range, // ise range, [4,20] for endpoints, [0,11] for weights
+ bool weight_flag); // false if block endpoints, true if weights
+
+ // True if the CEM is LDR.
+ bool is_cem_ldr(uint32_t mode);
+ inline bool is_cem_hdr(uint32_t mode) { return !is_cem_ldr(mode); }
+
+ // True if the passed in dimensions are a valid ASTC block size. There are 14 supported configs, from 4x4 (8bpp) to 12x12 (.89bpp).
+ bool is_valid_block_size(uint32_t w, uint32_t h);
+
+ bool block_has_any_hdr_cems(const log_astc_block& log_blk);
+ bool block_has_any_ldr_cems(const log_astc_block& log_blk);
+
+ // Returns the # of endpoint values for the given CEM.
+ inline uint32_t get_num_cem_values(uint32_t cem) { assert(cem <= 15); return 2 + 2 * (cem >> 2); }
+
+ struct dequant_table
+ {
+ basisu::vector<uint8_t> m_val_to_ise; // [0-255] or [0-64] value to nearest ISE symbol, array size is [256] or [65]
+ basisu::vector<uint8_t> m_ISE_to_val; // ASTC encoded ISE symbol to [0,255] or [0,64] value, [levels]
+ basisu::vector<uint8_t> m_ISE_to_rank; // returns the level rank index given an ISE symbol, [levels]
+ basisu::vector<uint8_t> m_rank_to_ISE; // returns the ISE symbol given a level rank, inverse of pISE_to_rank, [levels]
+
+ void init(bool weight_flag, uint32_t num_levels, bool init_rank_tabs)
+ {
+ m_val_to_ise.resize(weight_flag ? (MAX_WEIGHT_VALUE + 1) : 256);
+ m_ISE_to_val.resize(num_levels);
+ if (init_rank_tabs)
+ {
+ m_ISE_to_rank.resize(num_levels);
+ m_rank_to_ISE.resize(num_levels);
+ }
+ }
+ };
+
+ struct dequant_tables
+ {
+ dequant_table m_weights[TOTAL_WEIGHT_ISE_RANGES];
+ dequant_table m_endpoints[TOTAL_ENDPOINT_ISE_RANGES];
+
+ const dequant_table& get_weight_tab(uint32_t range) const
+ {
+ assert((range >= FIRST_VALID_WEIGHT_ISE_RANGE) && (range <= LAST_VALID_WEIGHT_ISE_RANGE));
+ return m_weights[range - FIRST_VALID_WEIGHT_ISE_RANGE];
+ }
+
+ dequant_table& get_weight_tab(uint32_t range)
+ {
+ assert((range >= FIRST_VALID_WEIGHT_ISE_RANGE) && (range <= LAST_VALID_WEIGHT_ISE_RANGE));
+ return m_weights[range - FIRST_VALID_WEIGHT_ISE_RANGE];
+ }
+
+ const dequant_table& get_endpoint_tab(uint32_t range) const
+ {
+ assert((range >= FIRST_VALID_ENDPOINT_ISE_RANGE) && (range <= LAST_VALID_ENDPOINT_ISE_RANGE));
+ return m_endpoints[range - FIRST_VALID_ENDPOINT_ISE_RANGE];
+ }
+
+ dequant_table& get_endpoint_tab(uint32_t range)
+ {
+ assert((range >= FIRST_VALID_ENDPOINT_ISE_RANGE) && (range <= LAST_VALID_ENDPOINT_ISE_RANGE));
+ return m_endpoints[range - FIRST_VALID_ENDPOINT_ISE_RANGE];
+ }
+
+ void init(bool init_rank_tabs)
+ {
+ for (uint32_t range = FIRST_VALID_WEIGHT_ISE_RANGE; range <= LAST_VALID_WEIGHT_ISE_RANGE; range++)
+ {
+ const uint32_t num_levels = get_ise_levels(range);
+ dequant_table& tab = get_weight_tab(range);
+
+ tab.init(true, num_levels, init_rank_tabs);
+
+ create_quant_tables(tab.m_val_to_ise.data(), tab.m_ISE_to_val.data(), init_rank_tabs ? tab.m_ISE_to_rank.data() : nullptr, init_rank_tabs ? tab.m_rank_to_ISE.data() : nullptr, range, true);
+ }
+
+ for (uint32_t range = FIRST_VALID_ENDPOINT_ISE_RANGE; range <= LAST_VALID_ENDPOINT_ISE_RANGE; range++)
+ {
+ const uint32_t num_levels = get_ise_levels(range);
+ dequant_table& tab = get_endpoint_tab(range);
+
+ tab.init(false, num_levels, init_rank_tabs);
+
+ create_quant_tables(tab.m_val_to_ise.data(), tab.m_ISE_to_val.data(), init_rank_tabs ? tab.m_ISE_to_rank.data() : nullptr, init_rank_tabs ? tab.m_rank_to_ISE.data() : nullptr, range, false);
+ }
+ }
+ };
+
+ extern dequant_tables g_dequant_tables;
+ void init_tables(bool init_rank_tabs);
+
+ // Procedurally returns the texel partition/subset index given the block coordinate and config.
+ int compute_texel_partition(uint32_t seedIn, uint32_t xIn, uint32_t yIn, uint32_t zIn, int num_partitions, bool small_block);
+
+ void blue_contract(
+ int r, int g, int b, int a,
+ int& dr, int& dg, int& db, int& da);
+
+ void bit_transfer_signed(int& a, int& b);
+
+ void decode_endpoint(uint32_t cem_index, int (*pEndpoints)[2], const uint8_t* pE);
+
+ typedef uint16_t half_float;
+ half_float float_to_half(float val, bool toward_zero);
+ float half_to_float(half_float hval);
+
+ const int MAX_RGB9E5 = 0xff80;
+ void unpack_rgb9e5(uint32_t packed, float& r, float& g, float& b);
+ uint32_t pack_rgb9e5(float r, float g, float b);
+
+ enum decode_mode
+ {
+ cDecodeModeSRGB8 = 0, // returns uint8_t's, not valid on HDR blocks
+ cDecodeModeLDR8 = 1, // returns uint8_t's, not valid on HDR blocks
+ cDecodeModeHDR16 = 2, // returns uint16_t's (half floats), valid on all LDR/HDR blocks
+ cDecodeModeRGB9E5 = 3 // returns uint32_t's, packed as RGB 9E5 (shared exponent), see https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt
+ };
+
+ // Decodes logical block to output pixels.
+ // pPixels must point to either 32-bit pixel values (SRGB8/LDR8/9E5) or 64-bit pixel values (HDR16)
+ bool decode_block(const log_astc_block& log_blk, void* pPixels, uint32_t blk_width, uint32_t blk_height, decode_mode dec_mode);
+
+ void decode_bise(uint32_t ise_range, uint8_t* pVals, uint32_t num_vals, const uint8_t *pBits128, uint32_t bit_ofs);
+
+ // Unpack a physical ASTC encoded GPU texture block to a logical block description.
+ bool unpack_block(const void* pASTC_block, log_astc_block& log_blk, uint32_t blk_width, uint32_t blk_height);
+
+} // namespace astc_helpers
+
+#endif // BASISU_ASTC_HELPERS_HEADER
+
+//------------------------------------------------------------------
+
+#ifdef BASISU_ASTC_HELPERS_IMPLEMENTATION
+
+namespace astc_helpers
+{
+ template<typename T> inline T my_min(T a, T b) { return (a < b) ? a : b; }
+ template<typename T> inline T my_max(T a, T b) { return (a > b) ? a : b; }
+
+ const uint8_t g_astc_block_sizes[NUM_ASTC_BLOCK_SIZES][2] = {
+ { 4, 4 }, { 5, 4 }, { 5, 5 }, { 6, 5 },
+ { 6, 6 }, { 8, 5 }, { 8, 6 }, { 10, 5 },
+ { 10, 6 }, { 8, 8 }, { 10, 8 }, { 10, 10 },
+ { 12, 10 }, { 12, 12 }
+ };
+
+ const int8_t g_ise_range_table[TOTAL_ISE_RANGES][3] =
+ {
+ //b t q
+ //2 3 5 // rng ise_index notes
+ { 1, 0, 0 }, // 0..1 0
+ { 0, 1, 0 }, // 0..2 1
+ { 2, 0, 0 }, // 0..3 2
+ { 0, 0, 1 }, // 0..4 3
+ { 1, 1, 0 }, // 0..5 4 min endpoint ISE index
+ { 3, 0, 0 }, // 0..7 5
+ { 1, 0, 1 }, // 0..9 6
+ { 2, 1, 0 }, // 0..11 7
+ { 4, 0, 0 }, // 0..15 8
+ { 2, 0, 1 }, // 0..19 9
+ { 3, 1, 0 }, // 0..23 10
+ { 5, 0, 0 }, // 0..31 11 max weight ISE index
+ { 3, 0, 1 }, // 0..39 12
+ { 4, 1, 0 }, // 0..47 13
+ { 6, 0, 0 }, // 0..63 14
+ { 4, 0, 1 }, // 0..79 15
+ { 5, 1, 0 }, // 0..95 16
+ { 7, 0, 0 }, // 0..127 17
+ { 5, 0, 1 }, // 0..159 18
+ { 6, 1, 0 }, // 0..191 19
+ { 8, 0, 0 }, // 0..255 20
+ };
+
+ static inline void astc_set_bits_1_to_9(uint32_t* pDst, uint32_t& bit_offset, uint32_t code, uint32_t codesize)
+ {
+ uint8_t* pBuf = reinterpret_cast<uint8_t*>(pDst);
+
+ assert(codesize <= 9);
+ if (codesize)
+ {
+ uint32_t byte_bit_offset = bit_offset & 7;
+ uint32_t val = code << byte_bit_offset;
+
+ uint32_t index = bit_offset >> 3;
+ pBuf[index] |= (uint8_t)val;
+
+ if (codesize > (8 - byte_bit_offset))
+ pBuf[index + 1] |= (uint8_t)(val >> 8);
+
+ bit_offset += codesize;
+ }
+ }
+
+ static inline uint32_t astc_extract_bits(uint32_t bits, int low, int high)
+ {
+ return (bits >> low) & ((1 << (high - low + 1)) - 1);
+ }
+
+ // Writes bits to output in an endian safe way
+ static inline void astc_set_bits(uint32_t* pOutput, uint32_t& bit_pos, uint32_t value, uint32_t total_bits)
+ {
+ assert(total_bits <= 31);
+ assert(value < (1u << total_bits));
+
+ uint8_t* pBytes = reinterpret_cast<uint8_t*>(pOutput);
+
+ while (total_bits)
+ {
+ const uint32_t bits_to_write = my_min<int>(total_bits, 8 - (bit_pos & 7));
+
+ pBytes[bit_pos >> 3] |= static_cast<uint8_t>(value << (bit_pos & 7));
+
+ bit_pos += bits_to_write;
+ total_bits -= bits_to_write;
+ value >>= bits_to_write;
+ }
+ }
+
+ static const uint8_t g_astc_quint_encode[125] =
+ {
+ 0, 1, 2, 3, 4, 8, 9, 10, 11, 12, 16, 17, 18, 19, 20, 24, 25, 26, 27, 28, 5, 13, 21, 29, 6, 32, 33, 34, 35, 36, 40, 41, 42, 43, 44, 48, 49, 50, 51, 52, 56, 57,
+ 58, 59, 60, 37, 45, 53, 61, 14, 64, 65, 66, 67, 68, 72, 73, 74, 75, 76, 80, 81, 82, 83, 84, 88, 89, 90, 91, 92, 69, 77, 85, 93, 22, 96, 97, 98, 99, 100, 104,
+ 105, 106, 107, 108, 112, 113, 114, 115, 116, 120, 121, 122, 123, 124, 101, 109, 117, 125, 30, 102, 103, 70, 71, 38, 110, 111, 78, 79, 46, 118, 119, 86, 87, 54,
+ 126, 127, 94, 95, 62, 39, 47, 55, 63, 7 /*31 - results in the same decode as 7*/
+ };
+
+ // Encodes 3 values to output, usable for any range that uses quints and bits
+ static inline void astc_encode_quints(uint32_t* pOutput, const uint8_t* pValues, uint32_t& bit_pos, int n)
+ {
+ // First extract the quints and the bits from the 3 input values
+ int quints = 0, bits[3];
+ const uint32_t bit_mask = (1 << n) - 1;
+ for (int i = 0; i < 3; i++)
+ {
+ static const int s_muls[3] = { 1, 5, 25 };
+
+ const int t = pValues[i] >> n;
+
+ quints += t * s_muls[i];
+ bits[i] = pValues[i] & bit_mask;
+ }
+
+ // Encode the quints, by inverting the bit manipulations done by the decoder, converting 3 quints into 7-bits.
+ // See https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html#astc-integer-sequence-encoding
+
+ assert(quints < 125);
+ const int T = g_astc_quint_encode[quints];
+
+ // Now interleave the 7 encoded quint bits with the bits to form the encoded output. See table 95-96.
+ astc_set_bits(pOutput, bit_pos, bits[0] | (astc_extract_bits(T, 0, 2) << n) | (bits[1] << (3 + n)) | (astc_extract_bits(T, 3, 4) << (3 + n * 2)) |
+ (bits[2] << (5 + n * 2)) | (astc_extract_bits(T, 5, 6) << (5 + n * 3)), 7 + n * 3);
+ }
+
+ static const uint8_t g_astc_trit_encode[243] = { 0, 1, 2, 4, 5, 6, 8, 9, 10, 16, 17, 18, 20, 21, 22, 24, 25, 26, 3, 7, 11, 19, 23, 27, 12, 13, 14, 32, 33, 34, 36, 37, 38, 40, 41, 42, 48, 49, 50, 52, 53, 54, 56, 57, 58, 35, 39,
+ 43, 51, 55, 59, 44, 45, 46, 64, 65, 66, 68, 69, 70, 72, 73, 74, 80, 81, 82, 84, 85, 86, 88, 89, 90, 67, 71, 75, 83, 87, 91, 76, 77, 78, 128, 129, 130, 132, 133, 134, 136, 137, 138, 144, 145, 146, 148, 149, 150, 152, 153, 154,
+ 131, 135, 139, 147, 151, 155, 140, 141, 142, 160, 161, 162, 164, 165, 166, 168, 169, 170, 176, 177, 178, 180, 181, 182, 184, 185, 186, 163, 167, 171, 179, 183, 187, 172, 173, 174, 192, 193, 194, 196, 197, 198, 200, 201, 202,
+ 208, 209, 210, 212, 213, 214, 216, 217, 218, 195, 199, 203, 211, 215, 219, 204, 205, 206, 96, 97, 98, 100, 101, 102, 104, 105, 106, 112, 113, 114, 116, 117, 118, 120, 121, 122, 99, 103, 107, 115, 119, 123, 108, 109, 110, 224,
+ 225, 226, 228, 229, 230, 232, 233, 234, 240, 241, 242, 244, 245, 246, 248, 249, 250, 227, 231, 235, 243, 247, 251, 236, 237, 238, 28, 29, 30, 60, 61, 62, 92, 93, 94, 156, 157, 158, 188, 189, 190, 220, 221, 222, 31, 63, 95, 159,
+ 191, 223, 124, 125, 126 };
+
+ // Encodes 5 values to output, usable for any range that uses trits and bits
+ static void astc_encode_trits(uint32_t* pOutput, const uint8_t* pValues, uint32_t& bit_pos, int n)
+ {
+ // First extract the trits and the bits from the 5 input values
+ int trits = 0, bits[5];
+ const uint32_t bit_mask = (1 << n) - 1;
+ for (int i = 0; i < 5; i++)
+ {
+ static const int s_muls[5] = { 1, 3, 9, 27, 81 };
+
+ const int t = pValues[i] >> n;
+
+ trits += t * s_muls[i];
+ bits[i] = pValues[i] & bit_mask;
+ }
+
+ // Encode the trits, by inverting the bit manipulations done by the decoder, converting 5 trits into 8-bits.
+ // See https://www.khronos.org/registry/DataFormat/specs/1.2/dataformat.1.2.html#astc-integer-sequence-encoding
+
+ assert(trits < 243);
+ const int T = g_astc_trit_encode[trits];
+
+ // Now interleave the 8 encoded trit bits with the bits to form the encoded output. See table 94.
+ astc_set_bits(pOutput, bit_pos, bits[0] | (astc_extract_bits(T, 0, 1) << n) | (bits[1] << (2 + n)), n * 2 + 2);
+
+ astc_set_bits(pOutput, bit_pos, astc_extract_bits(T, 2, 3) | (bits[2] << 2) | (astc_extract_bits(T, 4, 4) << (2 + n)) | (bits[3] << (3 + n)) | (astc_extract_bits(T, 5, 6) << (3 + n * 2)) |
+ (bits[4] << (5 + n * 2)) | (astc_extract_bits(T, 7, 7) << (5 + n * 3)), n * 3 + 6);
+ }
+
+ // Packs values using ASTC's BISE to output buffer.
+ void encode_bise(uint32_t* pDst, const uint8_t* pSrc_vals, uint32_t bit_pos, int num_vals, int range)
+ {
+ uint32_t temp[5] = { 0 };
+
+ const int num_bits = g_ise_range_table[range][0];
+
+ int group_size = 0;
+ if (g_ise_range_table[range][1])
+ group_size = 5;
+ else if (g_ise_range_table[range][2])
+ group_size = 3;
+
+#ifndef NDEBUG
+ const uint32_t num_levels = get_ise_levels(range);
+ for (int i = 0; i < num_vals; i++)
+ {
+ assert(pSrc_vals[i] < num_levels);
+ }
+#endif
+
+ if (group_size)
+ {
+ // Range has trits or quints - pack each group of 5 or 3 values
+ const int total_groups = (group_size == 5) ? ((num_vals + 4) / 5) : ((num_vals + 2) / 3);
+
+ for (int group_index = 0; group_index < total_groups; group_index++)
+ {
+ uint8_t vals[5] = { 0 };
+
+ const int limit = my_min(group_size, num_vals - group_index * group_size);
+ for (int i = 0; i < limit; i++)
+ vals[i] = pSrc_vals[group_index * group_size + i];
+
+ if (group_size == 5)
+ astc_encode_trits(temp, vals, bit_pos, num_bits);
+ else
+ astc_encode_quints(temp, vals, bit_pos, num_bits);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < num_vals; i++)
+ astc_set_bits_1_to_9(temp, bit_pos, pSrc_vals[i], num_bits);
+ }
+
+ // TODO: Could this write too many bits on incomplete blocks?
+ pDst[0] |= temp[0]; pDst[1] |= temp[1];
+ pDst[2] |= temp[2]; pDst[3] |= temp[3];
+ }
+
+ inline uint32_t rev_dword(uint32_t bits)
+ {
+ uint32_t v = (bits << 16) | (bits >> 16);
+ v = ((v & 0x00ff00ff) << 8) | ((v & 0xff00ff00) >> 8); v = ((v & 0x0f0f0f0f) << 4) | ((v & 0xf0f0f0f0) >> 4);
+ v = ((v & 0x33333333) << 2) | ((v & 0xcccccccc) >> 2); v = ((v & 0x55555555) << 1) | ((v & 0xaaaaaaaa) >> 1);
+ return v;
+ }
+
+ static inline bool is_packable(int value, int num_bits) { assert((num_bits >= 1) && (num_bits < 31)); return (value >= 0) && (value < (1 << num_bits)); }
+
+ static bool get_config_bits(const log_astc_block &log_block, uint32_t &config_bits)
+ {
+ config_bits = 0;
+
+ const int W = log_block.m_grid_width, H = log_block.m_grid_height;
+
+ const uint32_t P = log_block.m_weight_ise_range >= 6; // high precision
+ const uint32_t Dp_P = (log_block.m_dual_plane << 1) | P; // pack dual plane+high precision bits
+
+ // See Tables 81-82
+ // Compute p from weight range
+ uint32_t p = 2 + log_block.m_weight_ise_range - (P ? 6 : 0);
+
+ // Rearrange p's bits to p0 p2 p1
+ p = (p >> 1) + ((p & 1) << 2);
+
+ // Try encoding each row of table 82.
+
+ // W+4 H+2
+ if (is_packable(W - 4, 2) && is_packable(H - 2, 2))
+ {
+ config_bits = (Dp_P << 9) | ((W - 4) << 7) | ((H - 2) << 5) | ((p & 4) << 2) | (p & 3);
+ return true;
+ }
+
+ // W+8 H+2
+ if (is_packable(W - 8, 2) && is_packable(H - 2, 2))
+ {
+ config_bits = (Dp_P << 9) | ((W - 8) << 7) | ((H - 2) << 5) | ((p & 4) << 2) | 4 | (p & 3);
+ return true;
+ }
+
+ // W+2 H+8
+ if (is_packable(W - 2, 2) && is_packable(H - 8, 2))
+ {
+ config_bits = (Dp_P << 9) | ((H - 8) << 7) | ((W - 2) << 5) | ((p & 4) << 2) | 8 | (p & 3);
+ return true;
+ }
+
+ // W+2 H+6
+ if (is_packable(W - 2, 2) && is_packable(H - 6, 1))
+ {
+ config_bits = (Dp_P << 9) | ((H - 6) << 7) | ((W - 2) << 5) | ((p & 4) << 2) | 12 | (p & 3);
+ return true;
+ }
+
+ // W+2 H+2
+ if (is_packable(W - 2, 1) && is_packable(H - 2, 2))
+ {
+ config_bits = (Dp_P << 9) | ((W) << 7) | ((H - 2) << 5) | ((p & 4) << 2) | 12 | (p & 3);
+ return true;
+ }
+
+ // 12 H+2
+ if ((W == 12) && is_packable(H - 2, 2))
+ {
+ config_bits = (Dp_P << 9) | ((H - 2) << 5) | (p << 2);
+ return true;
+ }
+
+ // W+2 12
+ if ((H == 12) && is_packable(W - 2, 2))
+ {
+ config_bits = (Dp_P << 9) | (1 << 7) | ((W - 2) << 5) | (p << 2);
+ return true;
+ }
+
+ // 6 10
+ if ((W == 6) && (H == 10))
+ {
+ config_bits = (Dp_P << 9) | (3 << 7) | (p << 2);
+ return true;
+ }
+
+ // 10 6
+ if ((W == 10) && (H == 6))
+ {
+ config_bits = (Dp_P << 9) | (0b1101 << 5) | (p << 2);
+ return true;
+ }
+
+ // W+6 H+6 (no dual plane or high prec)
+ if ((!Dp_P) && is_packable(W - 6, 2) && is_packable(H - 6, 2))
+ {
+ config_bits = ((H - 6) << 9) | 256 | ((W - 6) << 5) | (p << 2);
+ return true;
+ }
+
+ // Failed: unsupported weight grid dimensions or config.
+ return false;
+ }
+
+ bool pack_astc_block(astc_block& phys_block, const log_astc_block& log_block, int* pExpected_endpoint_range)
+ {
+ memset(&phys_block, 0, sizeof(phys_block));
+
+ if (pExpected_endpoint_range)
+ *pExpected_endpoint_range = -1;
+
+ assert(!log_block.m_error_flag);
+ if (log_block.m_error_flag)
+ return false;
+
+ if (log_block.m_solid_color_flag_ldr)
+ {
+ pack_void_extent_ldr(phys_block, log_block.m_solid_color[0], log_block.m_solid_color[1], log_block.m_solid_color[2], log_block.m_solid_color[3]);
+ return true;
+ }
+ else if (log_block.m_solid_color_flag_hdr)
+ {
+ pack_void_extent_hdr(phys_block, log_block.m_solid_color[0], log_block.m_solid_color[1], log_block.m_solid_color[2], log_block.m_solid_color[3]);
+ return true;
+ }
+
+ if ((log_block.m_num_partitions < 1) || (log_block.m_num_partitions > MAX_PARTITIONS))
+ return false;
+
+ // Max usable weight range is 11
+ if (log_block.m_weight_ise_range > LAST_VALID_WEIGHT_ISE_RANGE)
+ return false;
+
+ // See 23.24 Illegal Encodings, [0,5] is the minimum ISE encoding for endpoints
+ if ((log_block.m_endpoint_ise_range < FIRST_VALID_ENDPOINT_ISE_RANGE) || (log_block.m_endpoint_ise_range > LAST_VALID_ENDPOINT_ISE_RANGE))
+ return false;
+
+ if (log_block.m_color_component_selector > 3)
+ return false;
+
+ uint32_t config_bits = 0;
+ if (!get_config_bits(log_block, config_bits))
+ return false;
+
+ uint32_t bit_pos = 0;
+ astc_set_bits(&phys_block.m_vals[0], bit_pos, config_bits, 11);
+
+ const uint32_t total_grid_weights = (log_block.m_dual_plane ? 2 : 1) * (log_block.m_grid_width * log_block.m_grid_height);
+ const uint32_t total_weight_bits = get_ise_sequence_bits(total_grid_weights, log_block.m_weight_ise_range);
+
+ // 18.24 Illegal Encodings
+ if ((!total_grid_weights) || (total_grid_weights > MAX_GRID_WEIGHTS) || (total_weight_bits < 24) || (total_weight_bits > 96))
+ return false;
+
+ uint32_t total_extra_bits = 0;
+
+ astc_set_bits(&phys_block.m_vals[0], bit_pos, log_block.m_num_partitions - 1, 2);
+
+ if (log_block.m_num_partitions > 1)
+ {
+ if (log_block.m_partition_id >= NUM_PARTITION_PATTERNS)
+ return false;
+
+ astc_set_bits(&phys_block.m_vals[0], bit_pos, log_block.m_partition_id, 10);
+
+ uint32_t highest_cem = 0, lowest_cem = UINT32_MAX;
+ for (uint32_t j = 0; j < log_block.m_num_partitions; j++)
+ {
+ highest_cem = my_max(highest_cem, log_block.m_color_endpoint_modes[j]);
+ lowest_cem = my_min(lowest_cem, log_block.m_color_endpoint_modes[j]);
+ }
+
+ if (highest_cem > 15)
+ return false;
+
+ // Ensure CEM range is contiguous
+ if (((highest_cem >> 2) > (1 + (lowest_cem >> 2))))
+ return false;
+
+ // See tables 79/80
+ uint32_t encoded_cem = log_block.m_color_endpoint_modes[0] << 2;
+ if (lowest_cem != highest_cem)
+ {
+ encoded_cem = my_min<uint32_t>(3, 1 + (lowest_cem >> 2));
+
+ // See tables at 23.11 Color Endpoint Mode
+ for (uint32_t j = 0; j < log_block.m_num_partitions; j++)
+ {
+ const int M = log_block.m_color_endpoint_modes[j] & 3;
+
+ const int C = (log_block.m_color_endpoint_modes[j] >> 2) - ((encoded_cem & 3) - 1);
+ if ((C & 1) != C)
+ return false;
+
+ encoded_cem |= (C << (2 + j)) | (M << (2 + log_block.m_num_partitions + 2 * j));
+ }
+
+ total_extra_bits = 3 * log_block.m_num_partitions - 4;
+
+ if ((total_weight_bits + total_extra_bits) > 128)
+ return false;
+
+ uint32_t cem_bit_pos = 128 - total_weight_bits - total_extra_bits;
+ astc_set_bits(&phys_block.m_vals[0], cem_bit_pos, encoded_cem >> 6, total_extra_bits);
+ }
+
+ astc_set_bits(&phys_block.m_vals[0], bit_pos, encoded_cem & 0x3f, 6);
+ }
+ else
+ {
+ if (log_block.m_partition_id)
+ return false;
+ if (log_block.m_color_endpoint_modes[0] > 15)
+ return false;
+
+ astc_set_bits(&phys_block.m_vals[0], bit_pos, log_block.m_color_endpoint_modes[0], 4);
+ }
+
+ if (log_block.m_dual_plane)
+ {
+ if (log_block.m_num_partitions > 3)
+ return false;
+
+ total_extra_bits += 2;
+
+ uint32_t ccs_bit_pos = 128 - (int)total_weight_bits - (int)total_extra_bits;
+ astc_set_bits(&phys_block.m_vals[0], ccs_bit_pos, log_block.m_color_component_selector, 2);
+ }
+
+ const uint32_t total_config_bits = bit_pos + total_extra_bits;
+ const int num_remaining_bits = 128 - (int)total_config_bits - (int)total_weight_bits;
+ if (num_remaining_bits < 0)
+ return false;
+
+ uint32_t total_cem_vals = 0;
+ for (uint32_t j = 0; j < log_block.m_num_partitions; j++)
+ total_cem_vals += 2 + 2 * (log_block.m_color_endpoint_modes[j] >> 2);
+
+ if (total_cem_vals > MAX_ENDPOINTS)
+ return false;
+
+ int endpoint_ise_range = -1;
+ for (int k = 20; k > 0; k--)
+ {
+ int bits = get_ise_sequence_bits(total_cem_vals, k);
+ if (bits <= num_remaining_bits)
+ {
+ endpoint_ise_range = k;
+ break;
+ }
+ }
+
+ // See 23.24 Illegal Encodings, [0,5] is the minimum ISE encoding for endpoints
+ if (endpoint_ise_range < (int)FIRST_VALID_ENDPOINT_ISE_RANGE)
+ return false;
+
+ // Ensure the caller utilized the right endpoint ISE range.
+ if ((int)log_block.m_endpoint_ise_range != endpoint_ise_range)
+ {
+ if (pExpected_endpoint_range)
+ *pExpected_endpoint_range = endpoint_ise_range;
+ return false;
+ }
+
+ // Pack endpoints forwards
+ encode_bise(&phys_block.m_vals[0], log_block.m_endpoints, bit_pos, total_cem_vals, endpoint_ise_range);
+
+ // Pack weights backwards
+ uint32_t weight_data[4] = { 0 };
+ encode_bise(weight_data, log_block.m_weights, 0, total_grid_weights, log_block.m_weight_ise_range);
+
+ for (uint32_t i = 0; i < 4; i++)
+ phys_block.m_vals[i] |= rev_dword(weight_data[3 - i]);
+
+ return true;
+ }
+
+ static inline uint32_t bit_replication_scale(uint32_t src, int num_src_bits, int num_dst_bits)
+ {
+ assert(num_src_bits <= num_dst_bits);
+ assert((src & ((1 << num_src_bits) - 1)) == src);
+
+ uint32_t dst = 0;
+ for (int shift = num_dst_bits - num_src_bits; shift > -num_src_bits; shift -= num_src_bits)
+ dst |= (shift >= 0) ? (src << shift) : (src >> -shift);
+
+ return dst;
+ }
+
+ uint32_t dequant_bise_endpoint(uint32_t val, uint32_t ise_range)
+ {
+ assert((ise_range >= FIRST_VALID_ENDPOINT_ISE_RANGE) && (ise_range <= LAST_VALID_ENDPOINT_ISE_RANGE));
+ assert(val < get_ise_levels(ise_range));
+
+ uint32_t u = 0;
+
+ switch (ise_range)
+ {
+ case 5:
+ {
+ u = bit_replication_scale(val, 3, 8);
+ break;
+ }
+ case 8:
+ {
+ u = bit_replication_scale(val, 4, 8);
+ break;
+ }
+ case 11:
+ {
+ u = bit_replication_scale(val, 5, 8);
+ break;
+ }
+ case 14:
+ {
+ u = bit_replication_scale(val, 6, 8);
+ break;
+ }
+ case 17:
+ {
+ u = bit_replication_scale(val, 7, 8);
+ break;
+ }
+ case 20:
+ {
+ u = val;
+ break;
+ }
+ case 4:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ case 13:
+ case 15:
+ case 16:
+ case 18:
+ case 19:
+ {
+ const uint32_t num_bits = g_ise_range_table[ise_range][0];
+ const uint32_t num_trits = g_ise_range_table[ise_range][1]; BASISU_NOTE_UNUSED(num_trits);
+ const uint32_t num_quints = g_ise_range_table[ise_range][2]; BASISU_NOTE_UNUSED(num_quints);
+
+ // compute Table 103 row index
+ const int range_index = (num_bits * 2 + (num_quints ? 1 : 0)) - 2;
+
+ assert(range_index >= 0 && range_index <= 10);
+
+ uint32_t bits = val & ((1 << num_bits) - 1);
+ uint32_t tval = val >> num_bits;
+
+ assert(tval < (num_trits ? 3U : 5U));
+
+ uint32_t a = bits & 1;
+ uint32_t b = (bits >> 1) & 1;
+ uint32_t c = (bits >> 2) & 1;
+ uint32_t d = (bits >> 3) & 1;
+ uint32_t e = (bits >> 4) & 1;
+ uint32_t f = (bits >> 5) & 1;
+
+ uint32_t A = a ? 511 : 0;
+ uint32_t B = 0;
+
+ switch (range_index)
+ {
+ case 2:
+ {
+ // 876543210
+ // b000b0bb0
+ B = (b << 1) | (b << 2) | (b << 4) | (b << 8);
+ break;
+ }
+ case 3:
+ {
+ // 876543210
+ // b0000bb00
+ B = (b << 2) | (b << 3) | (b << 8);
+ break;
+ }
+ case 4:
+ {
+ // 876543210
+ // cb000cbcb
+ B = b | (c << 1) | (b << 2) | (c << 3) | (b << 7) | (c << 8);
+ break;
+ }
+ case 5:
+ {
+ // 876543210
+ // cb0000cbc
+ B = c | (b << 1) | (c << 2) | (b << 7) | (c << 8);
+ break;
+ }
+ case 6:
+ {
+ // 876543210
+ // dcb000dcb
+ B = b | (c << 1) | (d << 2) | (b << 6) | (c << 7) | (d << 8);
+ break;
+ }
+ case 7:
+ {
+ // 876543210
+ // dcb0000dc
+ B = c | (d << 1) | (b << 6) | (c << 7) | (d << 8);
+ break;
+ }
+ case 8:
+ {
+ // 876543210
+ // edcb000ed
+ B = d | (e << 1) | (b << 5) | (c << 6) | (d << 7) | (e << 8);
+ break;
+ }
+ case 9:
+ {
+ // 876543210
+ // edcb0000e
+ B = e | (b << 5) | (c << 6) | (d << 7) | (e << 8);
+ break;
+ }
+ case 10:
+ {
+ // 876543210
+ // fedcb000f
+ B = f | (b << 4) | (c << 5) | (d << 6) | (e << 7) | (f << 8);
+ break;
+ }
+ default:
+ break;
+ }
+
+ static uint8_t C_vals[11] = { 204, 113, 93, 54, 44, 26, 22, 13, 11, 6, 5 };
+ uint32_t C = C_vals[range_index];
+ uint32_t D = tval;
+
+ u = D * C + B;
+ u = u ^ A;
+ u = (A & 0x80) | (u >> 2);
+
+ break;
+ }
+ default:
+ {
+ assert(0);
+ break;
+ }
+ }
+
+ return u;
+ }
+
+ uint32_t dequant_bise_weight(uint32_t val, uint32_t ise_range)
+ {
+ assert(val < get_ise_levels(ise_range));
+
+ uint32_t u = 0;
+ switch (ise_range)
+ {
+ case 0:
+ {
+ u = val ? 63 : 0;
+ break;
+ }
+ case 1: // 0-2
+ {
+ const uint8_t s_tab_0_2[3] = { 0, 32, 63 };
+ u = s_tab_0_2[val];
+ break;
+ }
+ case 2: // 0-3
+ {
+ u = bit_replication_scale(val, 2, 6);
+ break;
+ }
+ case 3: // 0-4
+ {
+ const uint8_t s_tab_0_4[5] = { 0, 16, 32, 47, 63 };
+ u = s_tab_0_4[val];
+ break;
+ }
+ case 5: // 0-7
+ {
+ u = bit_replication_scale(val, 3, 6);
+ break;
+ }
+ case 8: // 0-15
+ {
+ u = bit_replication_scale(val, 4, 6);
+ break;
+ }
+ case 11: // 0-31
+ {
+ u = bit_replication_scale(val, 5, 6);
+ break;
+ }
+ case 4: // 0-5
+ case 6: // 0-9
+ case 7: // 0-11
+ case 9: // 0-19
+ case 10: // 0-23
+ {
+ const uint32_t num_bits = g_ise_range_table[ise_range][0];
+ const uint32_t num_trits = g_ise_range_table[ise_range][1]; BASISU_NOTE_UNUSED(num_trits);
+ const uint32_t num_quints = g_ise_range_table[ise_range][2]; BASISU_NOTE_UNUSED(num_quints);
+
+ // compute Table 103 row index
+ const int range_index = num_bits * 2 + (num_quints ? 1 : 0);
+
+ // Extract bits and tris/quints from value
+ const uint32_t bits = val & ((1u << num_bits) - 1);
+ const uint32_t D = val >> num_bits;
+
+ assert(D < (num_trits ? 3U : 5U));
+
+ // Now dequantize
+ // See Table 103. ASTC weight unquantization parameters
+ static const uint32_t C_table[5] = { 50, 28, 23, 13, 11 };
+
+ const uint32_t a = bits & 1, b = (bits >> 1) & 1, c = (bits >> 2) & 1;
+
+ const uint32_t A = (a == 0) ? 0 : 0x7F;
+
+ uint32_t B = 0;
+ if (range_index == 4)
+ B = ((b << 6) | (b << 2) | (b << 0));
+ else if (range_index == 5)
+ B = ((b << 6) | (b << 1));
+ else if (range_index == 6)
+ B = ((c << 6) | (b << 5) | (c << 1) | (b << 0));
+
+ const uint32_t C = C_table[range_index - 2];
+
+ u = D * C + B;
+ u = u ^ A;
+ u = (A & 0x20) | (u >> 2);
+ break;
+ }
+ default:
+ assert(0);
+ break;
+ }
+
+ if (u > 32)
+ u++;
+
+ return u;
+ }
+
+ // Returns the nearest ISE symbol given a [0,255] endpoint value.
+ uint32_t find_nearest_bise_endpoint(int v, uint32_t ise_range)
+ {
+ assert(ise_range >= FIRST_VALID_ENDPOINT_ISE_RANGE && ise_range <= LAST_VALID_ENDPOINT_ISE_RANGE);
+
+ const uint32_t total_levels = get_ise_levels(ise_range);
+ int best_e = INT_MAX, best_index = 0;
+ for (uint32_t i = 0; i < total_levels; i++)
+ {
+ const int qv = dequant_bise_endpoint(i, ise_range);
+ int e = labs(v - qv);
+ if (e < best_e)
+ {
+ best_e = e;
+ best_index = i;
+ if (!best_e)
+ break;
+ }
+ }
+ return best_index;
+ }
+
+ // Returns the nearest ISE weight given a [0,64] endpoint value.
+ uint32_t find_nearest_bise_weight(int v, uint32_t ise_range)
+ {
+ assert(ise_range >= FIRST_VALID_WEIGHT_ISE_RANGE && ise_range <= LAST_VALID_WEIGHT_ISE_RANGE);
+ assert(v <= (int)MAX_WEIGHT_VALUE);
+
+ const uint32_t total_levels = get_ise_levels(ise_range);
+ int best_e = INT_MAX, best_index = 0;
+ for (uint32_t i = 0; i < total_levels; i++)
+ {
+ const int qv = dequant_bise_weight(i, ise_range);
+ int e = labs(v - qv);
+ if (e < best_e)
+ {
+ best_e = e;
+ best_index = i;
+ if (!best_e)
+ break;
+ }
+ }
+ return best_index;
+ }
+
+ void create_quant_tables(
+ uint8_t* pVal_to_ise, // [0-255] or [0-64] value to nearest ISE symbol, array size is [256] or [65]
+ uint8_t* pISE_to_val, // ASTC encoded ISE symbol to [0,255] or [0,64] value, [levels]
+ uint8_t* pISE_to_rank, // returns the level rank index given an ISE symbol, [levels]
+ uint8_t* pRank_to_ISE, // returns the ISE symbol given a level rank, inverse of pISE_to_rank, [levels]
+ uint32_t ise_range, // ise range, [4,20] for endpoints, [0,11] for weights
+ bool weight_flag) // false if block endpoints, true if weights
+ {
+ const uint32_t num_dequant_vals = weight_flag ? (MAX_WEIGHT_VALUE + 1) : 256;
+
+ for (uint32_t i = 0; i < num_dequant_vals; i++)
+ {
+ uint32_t bise_index = weight_flag ? astc_helpers::find_nearest_bise_weight(i, ise_range) : astc_helpers::find_nearest_bise_endpoint(i, ise_range);
+
+ if (pVal_to_ise)
+ pVal_to_ise[i] = (uint8_t)bise_index;
+
+ if (pISE_to_val)
+ pISE_to_val[bise_index] = weight_flag ? (uint8_t)astc_helpers::dequant_bise_weight(bise_index, ise_range) : (uint8_t)astc_helpers::dequant_bise_endpoint(bise_index, ise_range);
+ }
+
+ if (pISE_to_rank || pRank_to_ISE)
+ {
+ const uint32_t num_levels = get_ise_levels(ise_range);
+
+ if (!g_ise_range_table[ise_range][1] && !g_ise_range_table[ise_range][2])
+ {
+ // Only bits
+ for (uint32_t i = 0; i < num_levels; i++)
+ {
+ if (pISE_to_rank)
+ pISE_to_rank[i] = (uint8_t)i;
+
+ if (pRank_to_ISE)
+ pRank_to_ISE[i] = (uint8_t)i;
+ }
+ }
+ else
+ {
+ // Range has trits or quints
+ uint32_t vals[256];
+ for (uint32_t i = 0; i < num_levels; i++)
+ {
+ uint32_t v = weight_flag ? astc_helpers::dequant_bise_weight(i, ise_range) : astc_helpers::dequant_bise_endpoint(i, ise_range);
+
+ // Low=ISE value
+ // High=dequantized value
+ vals[i] = (v << 16) | i;
+ }
+
+ // Sorts by dequantized value
+ std::sort(vals, vals + num_levels);
+
+ for (uint32_t rank = 0; rank < num_levels; rank++)
+ {
+ uint32_t ise_val = (uint8_t)vals[rank];
+
+ if (pISE_to_rank)
+ pISE_to_rank[ise_val] = (uint8_t)rank;
+
+ if (pRank_to_ISE)
+ pRank_to_ISE[rank] = (uint8_t)ise_val;
+ }
+ }
+ }
+ }
+
+ void pack_void_extent_ldr(astc_block &blk, uint16_t rh, uint16_t gh, uint16_t bh, uint16_t ah)
+ {
+ uint8_t* pDst = (uint8_t*)&blk.m_vals[0];
+ memset(pDst, 0xFF, 16);
+
+ pDst[0] = 0b11111100;
+ pDst[1] = 0b11111101;
+
+ pDst[8] = (uint8_t)rh;
+ pDst[9] = (uint8_t)(rh >> 8);
+ pDst[10] = (uint8_t)gh;
+ pDst[11] = (uint8_t)(gh >> 8);
+ pDst[12] = (uint8_t)bh;
+ pDst[13] = (uint8_t)(bh >> 8);
+ pDst[14] = (uint8_t)ah;
+ pDst[15] = (uint8_t)(ah >> 8);
+ }
+
+ // rh-ah are half-floats
+ void pack_void_extent_hdr(astc_block& blk, uint16_t rh, uint16_t gh, uint16_t bh, uint16_t ah)
+ {
+ uint8_t* pDst = (uint8_t*)&blk.m_vals[0];
+ memset(pDst, 0xFF, 16);
+
+ pDst[0] = 0b11111100;
+
+ pDst[8] = (uint8_t)rh;
+ pDst[9] = (uint8_t)(rh >> 8);
+ pDst[10] = (uint8_t)gh;
+ pDst[11] = (uint8_t)(gh >> 8);
+ pDst[12] = (uint8_t)bh;
+ pDst[13] = (uint8_t)(bh >> 8);
+ pDst[14] = (uint8_t)ah;
+ pDst[15] = (uint8_t)(ah >> 8);
+ }
+
+ bool is_cem_ldr(uint32_t mode)
+ {
+ switch (mode)
+ {
+ case CEM_LDR_LUM_DIRECT:
+ case CEM_LDR_LUM_BASE_PLUS_OFS:
+ case CEM_LDR_LUM_ALPHA_DIRECT:
+ case CEM_LDR_LUM_ALPHA_BASE_PLUS_OFS:
+ case CEM_LDR_RGB_BASE_SCALE:
+ case CEM_LDR_RGB_DIRECT:
+ case CEM_LDR_RGB_BASE_PLUS_OFFSET:
+ case CEM_LDR_RGB_BASE_SCALE_PLUS_TWO_A:
+ case CEM_LDR_RGBA_DIRECT:
+ case CEM_LDR_RGBA_BASE_PLUS_OFFSET:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ bool is_valid_block_size(uint32_t w, uint32_t h)
+ {
+ assert((w >= MIN_BLOCK_DIM) && (w <= MAX_BLOCK_DIM));
+ assert((h >= MIN_BLOCK_DIM) && (h <= MAX_BLOCK_DIM));
+
+#define SIZECHK(x, y) if ((w == (x)) && (h == (y))) return true;
+ SIZECHK(4, 4);
+ SIZECHK(5, 4);
+
+ SIZECHK(5, 5);
+
+ SIZECHK(6, 5);
+ SIZECHK(6, 6);
+
+ SIZECHK(8, 5);
+ SIZECHK(8, 6);
+ SIZECHK(10, 5);
+ SIZECHK(10, 6);
+
+ SIZECHK(8, 8);
+ SIZECHK(10, 8);
+ SIZECHK(10, 10);
+
+ SIZECHK(12, 10);
+ SIZECHK(12, 12);
+#undef SIZECHK
+
+ return false;
+ }
+
+ bool block_has_any_hdr_cems(const log_astc_block& log_blk)
+ {
+ assert((log_blk.m_num_partitions >= 1) && (log_blk.m_num_partitions <= MAX_PARTITIONS));
+
+ for (uint32_t i = 0; i < log_blk.m_num_partitions; i++)
+ if (is_cem_hdr(log_blk.m_color_endpoint_modes[i]))
+ return true;
+
+ return false;
+ }
+
+ bool block_has_any_ldr_cems(const log_astc_block& log_blk)
+ {
+ assert((log_blk.m_num_partitions >= 1) && (log_blk.m_num_partitions <= MAX_PARTITIONS));
+
+ for (uint32_t i = 0; i < log_blk.m_num_partitions; i++)
+ if (!is_cem_hdr(log_blk.m_color_endpoint_modes[i]))
+ return true;
+
+ return false;
+ }
+
+ dequant_tables g_dequant_tables;
+
+ void precompute_texel_partitions_4x4();
+
+ void init_tables(bool init_rank_tabs)
+ {
+ g_dequant_tables.init(init_rank_tabs);
+
+ precompute_texel_partitions_4x4();
+ }
+
+ struct weighted_sample
+ {
+ uint8_t m_src_x;
+ uint8_t m_src_y;
+ uint8_t m_weights[2][2]; // [y][x], scaled by 16, round by adding 8
+ };
+
+ static void compute_upsample_weights(
+ int block_width, int block_height,
+ int weight_grid_width, int weight_grid_height,
+ weighted_sample* pWeights) // there will be block_width * block_height bilinear samples
+ {
+ const uint32_t scaleX = (1024 + block_width / 2) / (block_width - 1);
+ const uint32_t scaleY = (1024 + block_height / 2) / (block_height - 1);
+
+ for (int texelY = 0; texelY < block_height; texelY++)
+ {
+ for (int texelX = 0; texelX < block_width; texelX++)
+ {
+ const uint32_t gX = (scaleX * texelX * (weight_grid_width - 1) + 32) >> 6;
+ const uint32_t gY = (scaleY * texelY * (weight_grid_height - 1) + 32) >> 6;
+ const uint32_t jX = gX >> 4;
+ const uint32_t jY = gY >> 4;
+ const uint32_t fX = gX & 0xf;
+ const uint32_t fY = gY & 0xf;
+ const uint32_t w11 = (fX * fY + 8) >> 4;
+ const uint32_t w10 = fY - w11;
+ const uint32_t w01 = fX - w11;
+ const uint32_t w00 = 16 - fX - fY + w11;
+
+ weighted_sample& s = pWeights[texelX + texelY * block_width];
+ s.m_src_x = (uint8_t)jX;
+ s.m_src_y = (uint8_t)jY;
+ s.m_weights[0][0] = (uint8_t)w00;
+ s.m_weights[0][1] = (uint8_t)w01;
+ s.m_weights[1][0] = (uint8_t)w10;
+ s.m_weights[1][1] = (uint8_t)w11;
+ }
+ }
+ }
+
+ // Should be dequantized [0,64] weights
+ static void upsample_weight_grid(
+ uint32_t bx, uint32_t by, // destination/to dimension
+ uint32_t wx, uint32_t wy, // source/from dimension
+ const uint8_t* pSrc_weights, // these are dequantized [0,64] weights, NOT ISE symbols, [wy][wx]
+ uint8_t* pDst_weights) // [by][bx]
+ {
+ assert((bx >= 2) && (by >= 2) && (bx <= 12) && (by <= 12));
+ assert((wx >= 2) && (wy >= 2) && (wx <= bx) && (wy <= by));
+
+ const uint32_t total_src_weights = wx * wy;
+ const uint32_t total_dst_weights = bx * by;
+
+ if (total_src_weights == total_dst_weights)
+ {
+ memcpy(pDst_weights, pSrc_weights, total_src_weights);
+ return;
+ }
+
+ weighted_sample weights[12 * 12];
+ compute_upsample_weights(bx, by, wx, wy, weights);
+
+ const weighted_sample* pS = weights;
+
+ for (uint32_t y = 0; y < by; y++)
+ {
+ for (uint32_t x = 0; x < bx; x++, ++pS)
+ {
+ const uint32_t w00 = pS->m_weights[0][0];
+ const uint32_t w01 = pS->m_weights[0][1];
+ const uint32_t w10 = pS->m_weights[1][0];
+ const uint32_t w11 = pS->m_weights[1][1];
+
+ assert(w00 || w01 || w10 || w11);
+
+ const uint32_t sx = pS->m_src_x, sy = pS->m_src_y;
+
+ uint32_t total = 8;
+ if (w00) total += pSrc_weights[bounds_check(sx + sy * wx, 0U, total_src_weights)] * w00;
+ if (w01) total += pSrc_weights[bounds_check(sx + 1 + sy * wx, 0U, total_src_weights)] * w01;
+ if (w10) total += pSrc_weights[bounds_check(sx + (sy + 1) * wx, 0U, total_src_weights)] * w10;
+ if (w11) total += pSrc_weights[bounds_check(sx + 1 + (sy + 1) * wx, 0U, total_src_weights)] * w11;
+
+ pDst_weights[x + y * bx] = (uint8_t)(total >> 4);
+ }
+ }
+ }
+
+ inline uint32_t hash52(uint32_t v)
+ {
+ uint32_t p = v;
+ p ^= p >> 15; p -= p << 17; p += p << 7; p += p << 4;
+ p ^= p >> 5; p += p << 16; p ^= p >> 7; p ^= p >> 3;
+ p ^= p << 6; p ^= p >> 17;
+ return p;
+ }
+
+ int compute_texel_partition(uint32_t seedIn, uint32_t xIn, uint32_t yIn, uint32_t zIn, int num_partitions, bool small_block)
+ {
+ assert(zIn == 0);
+
+ const uint32_t x = small_block ? xIn << 1 : xIn;
+ const uint32_t y = small_block ? yIn << 1 : yIn;
+ const uint32_t z = small_block ? zIn << 1 : zIn;
+ const uint32_t seed = seedIn + 1024 * (num_partitions - 1);
+ const uint32_t rnum = hash52(seed);
+
+ uint8_t seed1 = (uint8_t)(rnum & 0xf);
+ uint8_t seed2 = (uint8_t)((rnum >> 4) & 0xf);
+ uint8_t seed3 = (uint8_t)((rnum >> 8) & 0xf);
+ uint8_t seed4 = (uint8_t)((rnum >> 12) & 0xf);
+ uint8_t seed5 = (uint8_t)((rnum >> 16) & 0xf);
+ uint8_t seed6 = (uint8_t)((rnum >> 20) & 0xf);
+ uint8_t seed7 = (uint8_t)((rnum >> 24) & 0xf);
+ uint8_t seed8 = (uint8_t)((rnum >> 28) & 0xf);
+ uint8_t seed9 = (uint8_t)((rnum >> 18) & 0xf);
+ uint8_t seed10 = (uint8_t)((rnum >> 22) & 0xf);
+ uint8_t seed11 = (uint8_t)((rnum >> 26) & 0xf);
+ uint8_t seed12 = (uint8_t)(((rnum >> 30) | (rnum << 2)) & 0xf);
+
+ seed1 = (uint8_t)(seed1 * seed1);
+ seed2 = (uint8_t)(seed2 * seed2);
+ seed3 = (uint8_t)(seed3 * seed3);
+ seed4 = (uint8_t)(seed4 * seed4);
+ seed5 = (uint8_t)(seed5 * seed5);
+ seed6 = (uint8_t)(seed6 * seed6);
+ seed7 = (uint8_t)(seed7 * seed7);
+ seed8 = (uint8_t)(seed8 * seed8);
+ seed9 = (uint8_t)(seed9 * seed9);
+ seed10 = (uint8_t)(seed10 * seed10);
+ seed11 = (uint8_t)(seed11 * seed11);
+ seed12 = (uint8_t)(seed12 * seed12);
+
+ const int shA = (seed & 2) != 0 ? 4 : 5;
+ const int shB = (num_partitions == 3) ? 6 : 5;
+ const int sh1 = (seed & 1) != 0 ? shA : shB;
+ const int sh2 = (seed & 1) != 0 ? shB : shA;
+ const int sh3 = (seed & 0x10) != 0 ? sh1 : sh2;
+
+ seed1 = (uint8_t)(seed1 >> sh1);
+ seed2 = (uint8_t)(seed2 >> sh2);
+ seed3 = (uint8_t)(seed3 >> sh1);
+ seed4 = (uint8_t)(seed4 >> sh2);
+ seed5 = (uint8_t)(seed5 >> sh1);
+ seed6 = (uint8_t)(seed6 >> sh2);
+ seed7 = (uint8_t)(seed7 >> sh1);
+ seed8 = (uint8_t)(seed8 >> sh2);
+ seed9 = (uint8_t)(seed9 >> sh3);
+ seed10 = (uint8_t)(seed10 >> sh3);
+ seed11 = (uint8_t)(seed11 >> sh3);
+ seed12 = (uint8_t)(seed12 >> sh3);
+
+ const int a = 0x3f & (seed1 * x + seed2 * y + seed11 * z + (rnum >> 14));
+ const int b = 0x3f & (seed3 * x + seed4 * y + seed12 * z + (rnum >> 10));
+ const int c = (num_partitions >= 3) ? 0x3f & (seed5 * x + seed6 * y + seed9 * z + (rnum >> 6)) : 0;
+ const int d = (num_partitions >= 4) ? 0x3f & (seed7 * x + seed8 * y + seed10 * z + (rnum >> 2)) : 0;
+
+ return (a >= b && a >= c && a >= d) ? 0
+ : (b >= c && b >= d) ? 1
+ : (c >= d) ? 2
+ : 3;
+ }
+
+ static uint32_t g_texel_partitions_4x4[1024][2];
+
+ void precompute_texel_partitions_4x4()
+ {
+ for (uint32_t p = 0; p < 1024; p++)
+ {
+ uint32_t v2 = 0, v3 = 0;
+
+ for (uint32_t y = 0; y < 4; y++)
+ {
+ for (uint32_t x = 0; x < 4; x++)
+ {
+ const uint32_t shift = x * 2 + y * 8;
+ v2 |= (compute_texel_partition(p, x, y, 0, 2, true) << shift);
+ v3 |= (compute_texel_partition(p, x, y, 0, 3, true) << shift);
+ }
+ }
+
+ g_texel_partitions_4x4[p][0] = v2;
+ g_texel_partitions_4x4[p][1] = v3;
+ }
+ }
+
+ static inline int get_precompute_texel_partitions_4x4(uint32_t seed, uint32_t x, uint32_t y, uint32_t num_partitions)
+ {
+ assert(g_texel_partitions_4x4[1][0]);
+ assert(seed < 1024);
+ assert((x <= 3) && (y <= 3));
+ assert((num_partitions >= 2) && (num_partitions <= 3));
+
+ const uint32_t shift = x * 2 + y * 8;
+ return (g_texel_partitions_4x4[seed][num_partitions - 2] >> shift) & 3;
+ }
+
+ void blue_contract(
+ int r, int g, int b, int a,
+ int &dr, int &dg, int &db, int &da)
+ {
+ dr = (r + b) >> 1;
+ dg = (g + b) >> 1;
+ db = b;
+ da = a;
+ }
+
+ inline void bit_transfer_signed(int& a, int& b)
+ {
+ b >>= 1;
+ b |= (a & 0x80);
+ a >>= 1;
+ a &= 0x3F;
+ if ((a & 0x20) != 0)
+ a -= 0x40;
+ }
+
+ static inline int clamp(int a, int l, int h)
+ {
+ if (a < l)
+ a = l;
+ else if (a > h)
+ a = h;
+ return a;
+ }
+
+ static inline float clampf(float a, float l, float h)
+ {
+ if (a < l)
+ a = l;
+ else if (a > h)
+ a = h;
+ return a;
+ }
+
+ inline int sign_extend(int src, int num_src_bits)
+ {
+ assert((num_src_bits >= 2) && (num_src_bits <= 31));
+
+ const bool negative = (src & (1 << (num_src_bits - 1))) != 0;
+ if (negative)
+ return src | ~((1 << num_src_bits) - 1);
+ else
+ return src & ((1 << num_src_bits) - 1);
+ }
+
+ // endpoints is [4][2]
+ void decode_endpoint(uint32_t cem_index, int (*pEndpoints)[2], const uint8_t *pE)
+ {
+ assert(cem_index <= CEM_HDR_RGB_HDR_ALPHA);
+
+ int v0 = pE[0], v1 = pE[1];
+
+ int& e0_r = pEndpoints[0][0], &e0_g = pEndpoints[1][0], &e0_b = pEndpoints[2][0], &e0_a = pEndpoints[3][0];
+ int& e1_r = pEndpoints[0][1], &e1_g = pEndpoints[1][1], &e1_b = pEndpoints[2][1], &e1_a = pEndpoints[3][1];
+
+ switch (cem_index)
+ {
+ case CEM_LDR_LUM_DIRECT:
+ {
+ e0_r = v0; e1_r = v1;
+ e0_g = v0; e1_g = v1;
+ e0_b = v0; e1_b = v1;
+ e0_a = 0xFF; e1_a = 0xFF;
+ break;
+ }
+ case CEM_LDR_LUM_BASE_PLUS_OFS:
+ {
+ int l0 = (v0 >> 2) | (v1 & 0xc0);
+ int l1 = l0 + (v1 & 0x3f);
+
+ if (l1 > 0xFF)
+ l1 = 0xFF;
+
+ e0_r = l0; e1_r = l1;
+ e0_g = l0; e1_g = l1;
+ e0_b = l0; e1_b = l1;
+ e0_a = 0xFF; e1_a = 0xFF;
+ break;
+ }
+ case CEM_LDR_LUM_ALPHA_DIRECT:
+ {
+ int v2 = pE[2], v3 = pE[3];
+
+ e0_r = v0; e1_r = v1;
+ e0_g = v0; e1_g = v1;
+ e0_b = v0; e1_b = v1;
+ e0_a = v2; e1_a = v3;
+ break;
+ }
+ case CEM_LDR_LUM_ALPHA_BASE_PLUS_OFS:
+ {
+ int v2 = pE[2], v3 = pE[3];
+
+ bit_transfer_signed(v1, v0);
+ bit_transfer_signed(v3, v2);
+
+ e0_r = v0; e1_r = v0 + v1;
+ e0_g = v0; e1_g = v0 + v1;
+ e0_b = v0; e1_b = v0 + v1;
+ e0_a = v2; e1_a = v2 + v3;
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ pEndpoints[c][0] = clamp(pEndpoints[c][0], 0, 255);
+ pEndpoints[c][1] = clamp(pEndpoints[c][1], 0, 255);
+ }
+
+ break;
+ }
+ case CEM_LDR_RGB_BASE_SCALE:
+ {
+ int v2 = pE[2], v3 = pE[3];
+
+ e0_r = (v0 * v3) >> 8; e1_r = v0;
+ e0_g = (v1 * v3) >> 8; e1_g = v1;
+ e0_b = (v2 * v3) >> 8; e1_b = v2;
+ e0_a = 0xFF; e1_a = 0xFF;
+
+ break;
+ }
+ case CEM_LDR_RGB_DIRECT:
+ {
+ int v2 = pE[2], v3 = pE[3], v4 = pE[4], v5 = pE[5];
+
+ if ((v1 + v3 + v5) >= (v0 + v2 + v4))
+ {
+ e0_r = v0; e1_r = v1;
+ e0_g = v2; e1_g = v3;
+ e0_b = v4; e1_b = v5;
+ e0_a = 0xFF; e1_a = 0xFF;
+ }
+ else
+ {
+ blue_contract(v1, v3, v5, 0xFF, e0_r, e0_g, e0_b, e0_a);
+ blue_contract(v0, v2, v4, 0xFF, e1_r, e1_g, e1_b, e1_a);
+ }
+
+ break;
+ }
+ case CEM_LDR_RGB_BASE_PLUS_OFFSET:
+ {
+ int v2 = pE[2], v3 = pE[3], v4 = pE[4], v5 = pE[5];
+
+ bit_transfer_signed(v1, v0);
+ bit_transfer_signed(v3, v2);
+ bit_transfer_signed(v5, v4);
+
+ if ((v1 + v3 + v5) >= 0)
+ {
+ e0_r = v0; e1_r = v0 + v1;
+ e0_g = v2; e1_g = v2 + v3;
+ e0_b = v4; e1_b = v4 + v5;
+ e0_a = 0xFF; e1_a = 0xFF;
+ }
+ else
+ {
+ blue_contract(v0 + v1, v2 + v3, v4 + v5, 0xFF, e0_r, e0_g, e0_b, e0_a);
+ blue_contract(v0, v2, v4, 0xFF, e1_r, e1_g, e1_b, e1_a);
+ }
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ pEndpoints[c][0] = clamp(pEndpoints[c][0], 0, 255);
+ pEndpoints[c][1] = clamp(pEndpoints[c][1], 0, 255);
+ }
+
+ break;
+ }
+ case CEM_LDR_RGB_BASE_SCALE_PLUS_TWO_A:
+ {
+ int v2 = pE[2], v3 = pE[3], v4 = pE[4], v5 = pE[5];
+
+ e0_r = (v0 * v3) >> 8; e1_r = v0;
+ e0_g = (v1 * v3) >> 8; e1_g = v1;
+ e0_b = (v2 * v3) >> 8; e1_b = v2;
+ e0_a = v4; e1_a = v5;
+
+ break;
+ }
+ case CEM_LDR_RGBA_DIRECT:
+ {
+ int v2 = pE[2], v3 = pE[3], v4 = pE[4], v5 = pE[5], v6 = pE[6], v7 = pE[7];
+
+ if ((v1 + v3 + v5) >= (v0 + v2 + v4))
+ {
+ e0_r = v0; e1_r = v1;
+ e0_g = v2; e1_g = v3;
+ e0_b = v4; e1_b = v5;
+ e0_a = v6; e1_a = v7;
+ }
+ else
+ {
+ blue_contract(v1, v3, v5, v7, e0_r, e0_g, e0_b, e0_a);
+ blue_contract(v0, v2, v4, v6, e1_r, e1_g, e1_b, e1_a);
+ }
+
+ break;
+ }
+ case CEM_LDR_RGBA_BASE_PLUS_OFFSET:
+ {
+ int v2 = pE[2], v3 = pE[3], v4 = pE[4], v5 = pE[5], v6 = pE[6], v7 = pE[7];
+
+ bit_transfer_signed(v1, v0);
+ bit_transfer_signed(v3, v2);
+ bit_transfer_signed(v5, v4);
+ bit_transfer_signed(v7, v6);
+
+ if ((v1 + v3 + v5) >= 0)
+ {
+ e0_r = v0; e1_r = v0 + v1;
+ e0_g = v2; e1_g = v2 + v3;
+ e0_b = v4; e1_b = v4 + v5;
+ e0_a = v6; e1_a = v6 + v7;
+ }
+ else
+ {
+ blue_contract(v0 + v1, v2 + v3, v4 + v5, v6 + v7, e0_r, e0_g, e0_b, e0_a);
+ blue_contract(v0, v2, v4, v6, e1_r, e1_g, e1_b, e1_a);
+ }
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ pEndpoints[c][0] = clamp(pEndpoints[c][0], 0, 255);
+ pEndpoints[c][1] = clamp(pEndpoints[c][1], 0, 255);
+ }
+
+ break;
+ }
+ case CEM_HDR_LUM_LARGE_RANGE:
+ {
+ int y0, y1;
+ if (v1 >= v0)
+ {
+ y0 = (v0 << 4);
+ y1 = (v1 << 4);
+ }
+ else
+ {
+ y0 = (v1 << 4) + 8;
+ y1 = (v0 << 4) - 8;
+ }
+
+ e0_r = y0; e1_r = y1;
+ e0_g = y0; e1_g = y1;
+ e0_b = y0; e1_b = y1;
+ e0_a = 0x780; e1_a = 0x780;
+
+ break;
+ }
+ case CEM_HDR_LUM_SMALL_RANGE:
+ {
+ int y0, y1, d;
+
+ if ((v0 & 0x80) != 0)
+ {
+ y0 = ((v1 & 0xE0) << 4) | ((v0 & 0x7F) << 2);
+ d = (v1 & 0x1F) << 2;
+ }
+ else
+ {
+ y0 = ((v1 & 0xF0) << 4) | ((v0 & 0x7F) << 1);
+ d = (v1 & 0x0F) << 1;
+ }
+
+ y1 = y0 + d;
+ if (y1 > 0xFFF)
+ y1 = 0xFFF;
+
+ e0_r = y0; e1_r = y1;
+ e0_g = y0; e1_g = y1;
+ e0_b = y0; e1_b = y1;
+ e0_a = 0x780; e1_a = 0x780;
+
+ break;
+ }
+ case CEM_HDR_RGB_BASE_SCALE:
+ {
+ int v2 = pE[2], v3 = pE[3];
+
+ int modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4);
+
+ int majcomp, mode;
+ if ((modeval & 0xC) != 0xC)
+ {
+ majcomp = modeval >> 2;
+ mode = modeval & 3;
+ }
+ else if (modeval != 0xF)
+ {
+ majcomp = modeval & 3;
+ mode = 4;
+ }
+ else
+ {
+ majcomp = 0;
+ mode = 5;
+ }
+
+ int red = v0 & 0x3f;
+ int green = v1 & 0x1f;
+ int blue = v2 & 0x1f;
+ int scale = v3 & 0x1f;
+
+ int x0 = (v1 >> 6) & 1;
+ int x1 = (v1 >> 5) & 1;
+ int x2 = (v2 >> 6) & 1;
+ int x3 = (v2 >> 5) & 1;
+ int x4 = (v3 >> 7) & 1;
+ int x5 = (v3 >> 6) & 1;
+ int x6 = (v3 >> 5) & 1;
+
+ int ohm = 1 << mode;
+ if (ohm & 0x30) green |= x0 << 6;
+ if (ohm & 0x3A) green |= x1 << 5;
+ if (ohm & 0x30) blue |= x2 << 6;
+ if (ohm & 0x3A) blue |= x3 << 5;
+ if (ohm & 0x3D) scale |= x6 << 5;
+ if (ohm & 0x2D) scale |= x5 << 6;
+ if (ohm & 0x04) scale |= x4 << 7;
+ if (ohm & 0x3B) red |= x4 << 6;
+ if (ohm & 0x04) red |= x3 << 6;
+ if (ohm & 0x10) red |= x5 << 7;
+ if (ohm & 0x0F) red |= x2 << 7;
+ if (ohm & 0x05) red |= x1 << 8;
+ if (ohm & 0x0A) red |= x0 << 8;
+ if (ohm & 0x05) red |= x0 << 9;
+ if (ohm & 0x02) red |= x6 << 9;
+ if (ohm & 0x01) red |= x3 << 10;
+ if (ohm & 0x02) red |= x5 << 10;
+
+ static const int s_shamts[6] = { 1,1,2,3,4,5 };
+
+ const int shamt = s_shamts[mode];
+ red <<= shamt;
+ green <<= shamt;
+ blue <<= shamt;
+ scale <<= shamt;
+
+ if (mode != 5)
+ {
+ green = red - green;
+ blue = red - blue;
+ }
+
+ if (majcomp == 1)
+ std::swap(red, green);
+
+ if (majcomp == 2)
+ std::swap(red, blue);
+
+ e1_r = clamp(red, 0, 0xFFF);
+ e1_g = clamp(green, 0, 0xFFF);
+ e1_b = clamp(blue, 0, 0xFFF);
+ e1_a = 0x780;
+
+ e0_r = clamp(red - scale, 0, 0xFFF);
+ e0_g = clamp(green - scale, 0, 0xFFF);
+ e0_b = clamp(blue - scale, 0, 0xFFF);
+ e0_a = 0x780;
+
+ break;
+ }
+ case CEM_HDR_RGB_HDR_ALPHA:
+ case CEM_HDR_RGB_LDR_ALPHA:
+ case CEM_HDR_RGB:
+ {
+ int v2 = pE[2], v3 = pE[3], v4 = pE[4], v5 = pE[5];
+
+ int majcomp = ((v4 & 0x80) >> 7) | ((v5 & 0x80) >> 6);
+
+ e0_a = 0x780;
+ e1_a = 0x780;
+
+ if (majcomp == 3)
+ {
+ e0_r = v0 << 4;
+ e0_g = v2 << 4;
+ e0_b = (v4 & 0x7f) << 5;
+
+ e1_r = v1 << 4;
+ e1_g = v3 << 4;
+ e1_b = (v5 & 0x7f) << 5;
+ }
+ else
+ {
+ int mode = ((v1 & 0x80) >> 7) | ((v2 & 0x80) >> 6) | ((v3 & 0x80) >> 5);
+ int va = v0 | ((v1 & 0x40) << 2);
+ int vb0 = v2 & 0x3f;
+ int vb1 = v3 & 0x3f;
+ int vc = v1 & 0x3f;
+ int vd0 = v4 & 0x7f;
+ int vd1 = v5 & 0x7f;
+
+ static const int s_dbitstab[8] = { 7,6,7,6,5,6,5,6 };
+ vd0 = sign_extend(vd0, s_dbitstab[mode]);
+ vd1 = sign_extend(vd1, s_dbitstab[mode]);
+
+ int x0 = (v2 >> 6) & 1;
+ int x1 = (v3 >> 6) & 1;
+ int x2 = (v4 >> 6) & 1;
+ int x3 = (v5 >> 6) & 1;
+ int x4 = (v4 >> 5) & 1;
+ int x5 = (v5 >> 5) & 1;
+
+ int ohm = 1 << mode;
+ if (ohm & 0xA4) va |= x0 << 9;
+ if (ohm & 0x08) va |= x2 << 9;
+ if (ohm & 0x50) va |= x4 << 9;
+ if (ohm & 0x50) va |= x5 << 10;
+ if (ohm & 0xA0) va |= x1 << 10;
+ if (ohm & 0xC0) va |= x2 << 11;
+ if (ohm & 0x04) vc |= x1 << 6;
+ if (ohm & 0xE8) vc |= x3 << 6;
+ if (ohm & 0x20) vc |= x2 << 7;
+ if (ohm & 0x5B) vb0 |= x0 << 6;
+ if (ohm & 0x5B) vb1 |= x1 << 6;
+ if (ohm & 0x12) vb0 |= x2 << 7;
+ if (ohm & 0x12) vb1 |= x3 << 7;
+
+ int shamt = (mode >> 1) ^ 3;
+ va = (uint32_t)va << shamt;
+ vb0 = (uint32_t)vb0 << shamt;
+ vb1 = (uint32_t)vb1 << shamt;
+ vc = (uint32_t)vc << shamt;
+ vd0 = (uint32_t)vd0 << shamt;
+ vd1 = (uint32_t)vd1 << shamt;
+
+ e1_r = clamp(va, 0, 0xFFF);
+ e1_g = clamp(va - vb0, 0, 0xFFF);
+ e1_b = clamp(va - vb1, 0, 0xFFF);
+
+ e0_r = clamp(va - vc, 0, 0xFFF);
+ e0_g = clamp(va - vb0 - vc - vd0, 0, 0xFFF);
+ e0_b = clamp(va - vb1 - vc - vd1, 0, 0xFFF);
+
+ if (majcomp == 1)
+ {
+ std::swap(e0_r, e0_g);
+ std::swap(e1_r, e1_g);
+ }
+ else if (majcomp == 2)
+ {
+ std::swap(e0_r, e0_b);
+ std::swap(e1_r, e1_b);
+ }
+ }
+
+ if (cem_index == CEM_HDR_RGB_LDR_ALPHA)
+ {
+ int v6 = pE[6], v7 = pE[7];
+
+ e0_a = v6;
+ e1_a = v7;
+ }
+ else if (cem_index == CEM_HDR_RGB_HDR_ALPHA)
+ {
+ int v6 = pE[6], v7 = pE[7];
+
+ // Extract mode bits
+ int mode = ((v6 >> 7) & 1) | ((v7 >> 6) & 2);
+ v6 &= 0x7F;
+ v7 &= 0x7F;
+
+ if (mode == 3)
+ {
+ e0_a = v6 << 5;
+ e1_a = v7 << 5;
+ }
+ else
+ {
+ v6 |= (v7 << (mode + 1)) & 0x780;
+ v7 &= (0x3F >> mode);
+ v7 ^= (0x20 >> mode);
+ v7 -= (0x20 >> mode);
+ v6 <<= (4 - mode);
+ v7 <<= (4 - mode);
+
+ v7 += v6;
+ v7 = clamp(v7, 0, 0xFFF);
+ e0_a = v6;
+ e1_a = v7;
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ assert(0);
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ pEndpoints[c][0] = 0;
+ pEndpoints[c][1] = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ static inline bool is_half_inf_or_nan(half_float v)
+ {
+ return get_bits(v, 10, 14) == 31;
+ }
+
+ // This float->half conversion matches how "F32TO16" works on Intel GPU's.
+ half_float float_to_half(float val, bool toward_zero)
+ {
+ union { float f; int32_t i; uint32_t u; } fi = { val };
+ const int flt_m = fi.i & 0x7FFFFF, flt_e = (fi.i >> 23) & 0xFF, flt_s = (fi.i >> 31) & 0x1;
+ int s = flt_s, e = 0, m = 0;
+
+ // inf/NaN
+ if (flt_e == 0xff)
+ {
+ e = 31;
+ if (flt_m != 0) // NaN
+ m = 1;
+ }
+ // not zero or denormal
+ else if (flt_e != 0)
+ {
+ int new_exp = flt_e - 127;
+ if (new_exp > 15)
+ e = 31;
+ else if (new_exp < -14)
+ {
+ if (toward_zero)
+ m = (int)truncf((1 << 24) * fabsf(fi.f));
+ else
+ m = lrintf((1 << 24) * fabsf(fi.f));
+ }
+ else
+ {
+ e = new_exp + 15;
+ if (toward_zero)
+ m = (int)truncf((float)flt_m * (1.0f / (float)(1 << 13)));
+ else
+ m = lrintf((float)flt_m * (1.0f / (float)(1 << 13)));
+ }
+ }
+
+ assert((0 <= m) && (m <= 1024));
+ if (m == 1024)
+ {
+ e++;
+ m = 0;
+ }
+
+ assert((s >= 0) && (s <= 1));
+ assert((e >= 0) && (e <= 31));
+ assert((m >= 0) && (m <= 1023));
+
+ half_float result = (half_float)((s << 15) | (e << 10) | m);
+ return result;
+ }
+
+ float half_to_float(half_float hval)
+ {
+ union { float f; uint32_t u; } x = { 0 };
+
+ uint32_t s = ((uint32_t)hval >> 15) & 1;
+ uint32_t e = ((uint32_t)hval >> 10) & 0x1F;
+ uint32_t m = (uint32_t)hval & 0x3FF;
+
+ if (!e)
+ {
+ if (!m)
+ {
+ // +- 0
+ x.u = s << 31;
+ return x.f;
+ }
+ else
+ {
+ // denormalized
+ while (!(m & 0x00000400))
+ {
+ m <<= 1;
+ --e;
+ }
+
+ ++e;
+ m &= ~0x00000400;
+ }
+ }
+ else if (e == 31)
+ {
+ if (m == 0)
+ {
+ // +/- INF
+ x.u = (s << 31) | 0x7f800000;
+ return x.f;
+ }
+ else
+ {
+ // +/- NaN
+ x.u = (s << 31) | 0x7f800000 | (m << 13);
+ return x.f;
+ }
+ }
+
+ e = e + (127 - 15);
+ m = m << 13;
+
+ assert(s <= 1);
+ assert(m <= 0x7FFFFF);
+ assert(e <= 255);
+
+ x.u = m | (e << 23) | (s << 31);
+ return x.f;
+ }
+
+ static inline half_float qlog16_to_half(int k)
+ {
+ assert((k >= 0) && (k <= 0xFFFF));
+
+ int E = (k & 0xF800) >> 11;
+ int M = k & 0x7FF;
+
+ int Mt;
+ if (M < 512)
+ Mt = 3 * M;
+ else if (M >= 1536)
+ Mt = 5 * M - 2048;
+ else
+ Mt = 4 * M - 512;
+
+ return (half_float)((E << 10) + (Mt >> 3));
+ }
+
+ // See https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt
+ const int RGB9E5_EXPONENT_BITS = 5, RGB9E5_MANTISSA_BITS = 9, RGB9E5_EXP_BIAS = 15, RGB9E5_MAX_VALID_BIASED_EXP = 31;
+ const int MAX_RGB9E5_EXP = (RGB9E5_MAX_VALID_BIASED_EXP - RGB9E5_EXP_BIAS);
+ const int RGB9E5_MANTISSA_VALUES = (1 << RGB9E5_MANTISSA_BITS);
+ const int MAX_RGB9E5_MANTISSA = (RGB9E5_MANTISSA_VALUES - 1);
+ //const int MAX_RGB9E5 = (int)(((float)MAX_RGB9E5_MANTISSA) / RGB9E5_MANTISSA_VALUES * (1 << MAX_RGB9E5_EXP));
+ const int EPSILON_RGB9E5 = (int)((1.0f / (float)RGB9E5_MANTISSA_VALUES) / (float)(1 << RGB9E5_EXP_BIAS));
+
+ void unpack_rgb9e5(uint32_t packed, float& r, float& g, float& b)
+ {
+ int x = packed & 511;
+ int y = (packed >> 9) & 511;
+ int z = (packed >> 18) & 511;
+ int w = (packed >> 27) & 31;
+
+ const float scale = powf(2.0f, static_cast<float>(w - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS));
+
+ r = x * scale;
+ g = y * scale;
+ b = z * scale;
+ }
+
+ // floor_log2 is not correct for the denorm and zero values, but we are going to do a max of this value with the minimum rgb9e5 exponent that will hide these problem cases.
+ static inline int floor_log2(float x)
+ {
+ union float754
+ {
+ unsigned int raw;
+ float value;
+ };
+
+ float754 f;
+ f.value = x;
+ // Extract float exponent
+ return ((f.raw >> 23) & 0xFF) - 127;
+ }
+
+ static inline int maximumi(int a, int b) { return (a > b) ? a : b; }
+ static inline float maximumf(float a, float b) { return (a > b) ? a : b; }
+
+ uint32_t pack_rgb9e5(float r, float g, float b)
+ {
+ r = clampf(r, 0.0f, MAX_RGB9E5);
+ g = clampf(g, 0.0f, MAX_RGB9E5);
+ b = clampf(b, 0.0f, MAX_RGB9E5);
+
+ float maxrgb = maximumf(maximumf(r, g), b);
+ int exp_shared = maximumi(-RGB9E5_EXP_BIAS - 1, floor_log2(maxrgb)) + 1 + RGB9E5_EXP_BIAS;
+ assert((exp_shared >= 0) && (exp_shared <= RGB9E5_MAX_VALID_BIASED_EXP));
+
+ float denom = powf(2.0f, (float)(exp_shared - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS));
+
+ int maxm = (int)floorf((maxrgb / denom) + 0.5f);
+ if (maxm == (MAX_RGB9E5_MANTISSA + 1))
+ {
+ denom *= 2;
+ exp_shared += 1;
+ assert(exp_shared <= RGB9E5_MAX_VALID_BIASED_EXP);
+ }
+ else
+ {
+ assert(maxm <= MAX_RGB9E5_MANTISSA);
+ }
+
+ int rm = (int)floorf((r / denom) + 0.5f);
+ int gm = (int)floorf((g / denom) + 0.5f);
+ int bm = (int)floorf((b / denom) + 0.5f);
+
+ assert((rm >= 0) && (rm <= MAX_RGB9E5_MANTISSA));
+ assert((gm >= 0) && (gm <= MAX_RGB9E5_MANTISSA));
+ assert((bm >= 0) && (bm <= MAX_RGB9E5_MANTISSA));
+
+ return rm | (gm << 9) | (bm << 18) | (exp_shared << 27);
+ }
+
+ static inline int clz17(uint32_t x)
+ {
+ assert(x <= 0x1FFFF);
+ x &= 0x1FFFF;
+
+ if (!x)
+ return 17;
+
+ uint32_t n = 0;
+ while ((x & 0x10000) == 0)
+ {
+ x <<= 1u;
+ n++;
+ }
+
+ return n;
+ }
+
+ static inline uint32_t pack_rgb9e5_ldr_astc(int Cr, int Cg, int Cb)
+ {
+ int lz = clz17(Cr | Cg | Cb | 1);
+ if (Cr == 65535) { Cr = 65536; lz = 0; }
+ if (Cg == 65535) { Cg = 65536; lz = 0; }
+ if (Cb == 65535) { Cb = 65536; lz = 0; }
+ Cr <<= lz; Cg <<= lz; Cb <<= lz;
+ Cr = (Cr >> 8) & 0x1FF;
+ Cg = (Cg >> 8) & 0x1FF;
+ Cb = (Cb >> 8) & 0x1FF;
+ uint32_t exponent = 16 - lz;
+ uint32_t texel = (exponent << 27) | (Cb << 18) | (Cg << 9) | Cr;
+ return texel;
+ }
+
+ static inline uint32_t pack_rgb9e5_hdr_astc(int Cr, int Cg, int Cb)
+ {
+ if (Cr > 0x7c00) Cr = 0; else if (Cr == 0x7c00) Cr = 0x7bff;
+ if (Cg > 0x7c00) Cg = 0; else if (Cg == 0x7c00) Cg = 0x7bff;
+ if (Cb > 0x7c00) Cb = 0; else if (Cb == 0x7c00) Cb = 0x7bff;
+ int Re = (Cr >> 10) & 0x1F;
+ int Ge = (Cg >> 10) & 0x1F;
+ int Be = (Cb >> 10) & 0x1F;
+ int Rex = (Re == 0) ? 1 : Re;
+ int Gex = (Ge == 0) ? 1 : Ge;
+ int Bex = (Be == 0) ? 1 : Be;
+ int Xm = ((Cr | Cg | Cb) & 0x200) >> 9;
+ int Xe = Re | Ge | Be;
+ uint32_t rshift, gshift, bshift, expo;
+
+ if (Xe == 0)
+ {
+ expo = rshift = gshift = bshift = Xm;
+ }
+ else if (Re >= Ge && Re >= Be)
+ {
+ expo = Rex + 1;
+ rshift = 2;
+ gshift = Rex - Gex + 2;
+ bshift = Rex - Bex + 2;
+ }
+ else if (Ge >= Be)
+ {
+ expo = Gex + 1;
+ rshift = Gex - Rex + 2;
+ gshift = 2;
+ bshift = Gex - Bex + 2;
+ }
+ else
+ {
+ expo = Bex + 1;
+ rshift = Bex - Rex + 2;
+ gshift = Bex - Gex + 2;
+ bshift = 2;
+ }
+
+ int Rm = (Cr & 0x3FF) | (Re == 0 ? 0 : 0x400);
+ int Gm = (Cg & 0x3FF) | (Ge == 0 ? 0 : 0x400);
+ int Bm = (Cb & 0x3FF) | (Be == 0 ? 0 : 0x400);
+ Rm = (Rm >> rshift) & 0x1FF;
+ Gm = (Gm >> gshift) & 0x1FF;
+ Bm = (Bm >> bshift) & 0x1FF;
+
+ uint32_t texel = (expo << 27) | (Bm << 18) | (Gm << 9) | (Rm << 0);
+ return texel;
+ }
+
+ // Important: pPixels is either 32-bit/texel or 64-bit/texel.
+ bool decode_block(const log_astc_block& log_blk, void* pPixels, uint32_t blk_width, uint32_t blk_height, decode_mode dec_mode)
+ {
+ assert(is_valid_block_size(blk_width, blk_height));
+
+ assert(g_dequant_tables.m_endpoints[0].m_ISE_to_val.size());
+ if (!g_dequant_tables.m_endpoints[0].m_ISE_to_val.size())
+ return false;
+
+ const uint32_t num_blk_pixels = blk_width * blk_height;
+
+ // Write block error color
+ if (dec_mode == cDecodeModeHDR16)
+ {
+ // NaN's
+ memset(pPixels, 0xFF, num_blk_pixels * sizeof(half_float) * 4);
+ }
+ else if (dec_mode == cDecodeModeRGB9E5)
+ {
+ const uint32_t purple_9e5 = pack_rgb9e5(1.0f, 0.0f, 1.0f);
+
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ ((uint32_t*)pPixels)[i] = purple_9e5;
+ }
+ else
+ {
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ ((uint32_t*)pPixels)[i] = 0xFFFF00FF;
+ }
+
+ if (log_blk.m_error_flag)
+ {
+ // Should this return false? It's not an invalid logical block config, though.
+ return false;
+ }
+
+ // Handle solid color blocks
+ if (log_blk.m_solid_color_flag_ldr)
+ {
+ // LDR solid block
+ if (dec_mode == cDecodeModeHDR16)
+ {
+ // Convert LDR pixels to half-float
+ half_float h[4];
+ for (uint32_t c = 0; c < 4; c++)
+ h[c] = (log_blk.m_solid_color[c] == 0xFFFF) ? 0x3C00 : float_to_half((float)log_blk.m_solid_color[c] * (1.0f / 65536.0f), true);
+
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ memcpy((uint16_t*)pPixels + i * 4, h, sizeof(half_float) * 4);
+ }
+ else if (dec_mode == cDecodeModeRGB9E5)
+ {
+ float r = (log_blk.m_solid_color[0] == 0xFFFF) ? 1.0f : ((float)log_blk.m_solid_color[0] * (1.0f / 65536.0f));
+ float g = (log_blk.m_solid_color[1] == 0xFFFF) ? 1.0f : ((float)log_blk.m_solid_color[1] * (1.0f / 65536.0f));
+ float b = (log_blk.m_solid_color[2] == 0xFFFF) ? 1.0f : ((float)log_blk.m_solid_color[2] * (1.0f / 65536.0f));
+
+ const uint32_t packed = pack_rgb9e5(r, g, b);
+
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ ((uint32_t*)pPixels)[i] = packed;
+ }
+ else
+ {
+ // Convert LDR pixels to 8-bits
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ for (uint32_t c = 0; c < 4; c++)
+ ((uint8_t*)pPixels)[i * 4 + c] = (log_blk.m_solid_color[c] >> 8);
+ }
+
+ return true;
+ }
+ else if (log_blk.m_solid_color_flag_hdr)
+ {
+ // HDR solid block, decode mode must be half-float or RGB9E5
+ if (dec_mode == cDecodeModeHDR16)
+ {
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ memcpy((uint16_t*)pPixels + i * 4, log_blk.m_solid_color, sizeof(half_float) * 4);
+ }
+ else if (dec_mode == cDecodeModeRGB9E5)
+ {
+ float r = half_to_float(log_blk.m_solid_color[0]);
+ float g = half_to_float(log_blk.m_solid_color[1]);
+ float b = half_to_float(log_blk.m_solid_color[2]);
+
+ const uint32_t packed = pack_rgb9e5(r, g, b);
+
+ for (uint32_t i = 0; i < num_blk_pixels; i++)
+ ((uint32_t*)pPixels)[i] = packed;
+ }
+ else
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ // Sanity check block's config
+ if ((log_blk.m_grid_width < 2) || (log_blk.m_grid_height < 2))
+ return false;
+ if ((log_blk.m_grid_width > blk_width) || (log_blk.m_grid_height > blk_height))
+ return false;
+
+ if ((log_blk.m_endpoint_ise_range < FIRST_VALID_ENDPOINT_ISE_RANGE) || (log_blk.m_endpoint_ise_range > LAST_VALID_ENDPOINT_ISE_RANGE))
+ return false;
+ if ((log_blk.m_weight_ise_range < FIRST_VALID_WEIGHT_ISE_RANGE) || (log_blk.m_weight_ise_range > LAST_VALID_WEIGHT_ISE_RANGE))
+ return false;
+ if ((log_blk.m_num_partitions < 1) || (log_blk.m_num_partitions > MAX_PARTITIONS))
+ return false;
+ if ((log_blk.m_dual_plane) && (log_blk.m_num_partitions > MAX_DUAL_PLANE_PARTITIONS))
+ return false;
+ if (log_blk.m_partition_id >= NUM_PARTITION_PATTERNS)
+ return false;
+ if ((log_blk.m_num_partitions == 1) && (log_blk.m_partition_id > 0))
+ return false;
+ if (log_blk.m_color_component_selector > 3)
+ return false;
+
+ const uint32_t total_endpoint_levels = get_ise_levels(log_blk.m_endpoint_ise_range);
+ const uint32_t total_weight_levels = get_ise_levels(log_blk.m_weight_ise_range);
+
+ bool is_ldr_endpoints[MAX_PARTITIONS];
+
+ // Check CEM's
+ uint32_t total_cem_vals = 0;
+ for (uint32_t i = 0; i < log_blk.m_num_partitions; i++)
+ {
+ if (log_blk.m_color_endpoint_modes[i] > 15)
+ return false;
+
+ total_cem_vals += get_num_cem_values(log_blk.m_color_endpoint_modes[i]);
+
+ is_ldr_endpoints[i] = is_cem_ldr(log_blk.m_color_endpoint_modes[i]);
+ }
+
+ if (total_cem_vals > MAX_ENDPOINTS)
+ return false;
+
+ const dequant_table& endpoint_dequant_tab = g_dequant_tables.get_endpoint_tab(log_blk.m_endpoint_ise_range);
+ const uint8_t* pEndpoint_dequant = endpoint_dequant_tab.m_ISE_to_val.data();
+
+ // Dequantized endpoints to [0,255]
+ uint8_t dequantized_endpoints[MAX_ENDPOINTS];
+ for (uint32_t i = 0; i < total_cem_vals; i++)
+ {
+ if (log_blk.m_endpoints[i] >= total_endpoint_levels)
+ return false;
+ dequantized_endpoints[i] = pEndpoint_dequant[log_blk.m_endpoints[i]];
+ }
+
+ // Dequantize weights to [0,64]
+ uint8_t dequantized_weights[2][12 * 12];
+
+ const dequant_table& weight_dequant_tab = g_dequant_tables.get_weight_tab(log_blk.m_weight_ise_range);
+ const uint8_t* pWeight_dequant = weight_dequant_tab.m_ISE_to_val.data();
+
+ const uint32_t total_weight_vals = (log_blk.m_dual_plane ? 2 : 1) * log_blk.m_grid_width * log_blk.m_grid_height;
+ for (uint32_t i = 0; i < total_weight_vals; i++)
+ {
+ if (log_blk.m_weights[i] >= total_weight_levels)
+ return false;
+
+ const uint32_t plane_index = log_blk.m_dual_plane ? (i & 1) : 0;
+ const uint32_t grid_index = log_blk.m_dual_plane ? (i >> 1) : i;
+
+ dequantized_weights[plane_index][grid_index] = pWeight_dequant[log_blk.m_weights[i]];
+ }
+
+ // Upsample weight grid. [0,64] weights
+ uint8_t upsampled_weights[2][12 * 12];
+
+ upsample_weight_grid(blk_width, blk_height, log_blk.m_grid_width, log_blk.m_grid_height, &dequantized_weights[0][0], &upsampled_weights[0][0]);
+ if (log_blk.m_dual_plane)
+ upsample_weight_grid(blk_width, blk_height, log_blk.m_grid_width, log_blk.m_grid_height, &dequantized_weights[1][0], &upsampled_weights[1][0]);
+
+ // Decode CEM's
+ int endpoints[4][4][2]; // [subset][comp][l/h]
+
+ uint32_t endpoint_val_index = 0;
+ for (uint32_t subset = 0; subset < log_blk.m_num_partitions; subset++)
+ {
+ const uint32_t cem_index = log_blk.m_color_endpoint_modes[subset];
+
+ decode_endpoint(cem_index, &endpoints[subset][0], &dequantized_endpoints[endpoint_val_index]);
+
+ endpoint_val_index += get_num_cem_values(cem_index);
+ }
+
+ // Decode texels
+ const bool small_block = num_blk_pixels < 31;
+ const bool use_precomputed_texel_partitions = (blk_width == 4) && (blk_height == 4) && (log_blk.m_num_partitions >= 2) && (log_blk.m_num_partitions <= 3);
+ const uint32_t ccs = log_blk.m_dual_plane ? log_blk.m_color_component_selector : UINT32_MAX;
+
+ bool success = true;
+
+ if (dec_mode == cDecodeModeRGB9E5)
+ {
+ // returns uint32_t's
+ for (uint32_t y = 0; y < blk_height; y++)
+ {
+ for (uint32_t x = 0; x < blk_width; x++)
+ {
+ const uint32_t pixel_index = x + y * blk_width;
+ const uint32_t subset = (log_blk.m_num_partitions > 1) ?
+ (use_precomputed_texel_partitions ? get_precompute_texel_partitions_4x4(log_blk.m_partition_id, x, y, log_blk.m_num_partitions) : compute_texel_partition(log_blk.m_partition_id, x, y, 0, log_blk.m_num_partitions, small_block))
+ : 0;
+
+ int comp[3];
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ const uint32_t w = upsampled_weights[(c == ccs) ? 1 : 0][pixel_index];
+
+ if (is_ldr_endpoints[subset])
+ {
+ assert((endpoints[subset][c][0] >= 0) && (endpoints[subset][c][0] <= 0xFF));
+ assert((endpoints[subset][c][1] >= 0) && (endpoints[subset][c][1] <= 0xFF));
+
+ int le = endpoints[subset][c][0];
+ int he = endpoints[subset][c][1];
+
+ le = (le << 8) | le;
+ he = (he << 8) | he;
+
+ int k = weight_interpolate(le, he, w);
+ assert((k >= 0) && (k <= 0xFFFF));
+
+ comp[c] = k; // 1.0
+ }
+ else
+ {
+ assert((endpoints[subset][c][0] >= 0) && (endpoints[subset][c][0] <= 0xFFF));
+ assert((endpoints[subset][c][1] >= 0) && (endpoints[subset][c][1] <= 0xFFF));
+
+ int le = endpoints[subset][c][0] << 4;
+ int he = endpoints[subset][c][1] << 4;
+
+ int qlog16 = weight_interpolate(le, he, w);
+
+ comp[c] = qlog16_to_half(qlog16);
+
+ if (is_half_inf_or_nan((half_float)comp[c]))
+ comp[c] = 0x7BFF;
+ }
+
+ } // c
+
+ uint32_t packed;
+ if (is_ldr_endpoints[subset])
+ packed = pack_rgb9e5_ldr_astc(comp[0], comp[1], comp[2]);
+ else
+ packed = pack_rgb9e5_hdr_astc(comp[0], comp[1], comp[2]);
+
+ ((uint32_t*)pPixels)[pixel_index] = packed;
+
+ } // x
+ } // y
+ }
+ else if (dec_mode == cDecodeModeHDR16)
+ {
+ // Note: must round towards zero when converting float to half for ASTC (18.19 Weight Application)
+
+ // returns half floats
+ for (uint32_t y = 0; y < blk_height; y++)
+ {
+ for (uint32_t x = 0; x < blk_width; x++)
+ {
+ const uint32_t pixel_index = x + y * blk_width;
+ const uint32_t subset = (log_blk.m_num_partitions > 1) ?
+ (use_precomputed_texel_partitions ? get_precompute_texel_partitions_4x4(log_blk.m_partition_id, x, y, log_blk.m_num_partitions) : compute_texel_partition(log_blk.m_partition_id, x, y, 0, log_blk.m_num_partitions, small_block))
+ : 0;
+
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ const uint32_t w = upsampled_weights[(c == ccs) ? 1 : 0][pixel_index];
+
+ half_float o;
+
+ if ( (is_ldr_endpoints[subset]) ||
+ ((log_blk.m_color_endpoint_modes[subset] == CEM_HDR_RGB_LDR_ALPHA) && (c == 3)) )
+ {
+ assert((endpoints[subset][c][0] >= 0) && (endpoints[subset][c][0] <= 0xFF));
+ assert((endpoints[subset][c][1] >= 0) && (endpoints[subset][c][1] <= 0xFF));
+
+ int le = endpoints[subset][c][0];
+ int he = endpoints[subset][c][1];
+
+ le = (le << 8) | le;
+ he = (he << 8) | he;
+
+ int k = weight_interpolate(le, he, w);
+ assert((k >= 0) && (k <= 0xFFFF));
+
+ if (k == 0xFFFF)
+ o = 0x3C00; // 1.0
+ else
+ o = float_to_half((float)k * (1.0f / 65536.0f), true);
+ }
+ else
+ {
+ assert((endpoints[subset][c][0] >= 0) && (endpoints[subset][c][0] <= 0xFFF));
+ assert((endpoints[subset][c][1] >= 0) && (endpoints[subset][c][1] <= 0xFFF));
+
+ int le = endpoints[subset][c][0] << 4;
+ int he = endpoints[subset][c][1] << 4;
+
+ int qlog16 = weight_interpolate(le, he, w);
+
+ o = qlog16_to_half(qlog16);
+
+ if (is_half_inf_or_nan(o))
+ o = 0x7BFF;
+ }
+
+ ((half_float*)pPixels)[pixel_index * 4 + c] = o;
+ }
+
+ } // x
+ } // y
+ }
+ else
+ {
+ // returns uint8_t's
+ for (uint32_t y = 0; y < blk_height; y++)
+ {
+ for (uint32_t x = 0; x < blk_width; x++)
+ {
+ const uint32_t pixel_index = x + y * blk_width;
+
+ const uint32_t subset = (log_blk.m_num_partitions > 1) ?
+ (use_precomputed_texel_partitions ? get_precompute_texel_partitions_4x4(log_blk.m_partition_id, x, y, log_blk.m_num_partitions) : compute_texel_partition(log_blk.m_partition_id, x, y, 0, log_blk.m_num_partitions, small_block))
+ : 0;
+
+ if (!is_ldr_endpoints[subset])
+ {
+ ((uint32_t*)pPixels)[pixel_index * 4] = 0xFFFF00FF;
+ success = false;
+ }
+ else
+ {
+ for (uint32_t c = 0; c < 4; c++)
+ {
+ const uint32_t w = upsampled_weights[(c == ccs) ? 1 : 0][pixel_index];
+
+ int le = endpoints[subset][c][0];
+ int he = endpoints[subset][c][1];
+
+ // FIXME: the spec is apparently wrong? this matches ARM's and Google's decoder
+ //if ((dec_mode == cDecodeModeSRGB8) && (c <= 2))
+ // See https://github.com/ARM-software/astc-encoder/issues/447
+ if (dec_mode == cDecodeModeSRGB8)
+ {
+ le = (le << 8) | 0x80;
+ he = (he << 8) | 0x80;
+ }
+ else
+ {
+ le = (le << 8) | le;
+ he = (he << 8) | he;
+ }
+
+ uint32_t k = weight_interpolate(le, he, w);
+
+ // FIXME: This is what the spec says to do in LDR mode, but this is not what ARM's decoder does
+ // See decompress_symbolic_block(), decode_texel() and unorm16_to_sf16.
+ // It seems to effectively divide by 65535.0 and convert to FP16, then back to float, mul by 255.0, add .5 and then convert to 8-bit.
+ ((uint8_t*)pPixels)[pixel_index * 4 + c] = (uint8_t)(k >> 8);
+ }
+ }
+
+ } // x
+ } // y
+ }
+
+ return success;
+ }
+
+ //------------------------------------------------
+ // Physical to logical block decoding
+
+ // unsigned 128-bit int, with some signed helpers
+ class uint128
+ {
+ uint64_t m_lo, m_hi;
+
+ public:
+ uint128() = default;
+ inline uint128(uint64_t lo) : m_lo(lo), m_hi(0) { }
+ inline uint128(uint64_t lo, uint64_t hi) : m_lo(lo), m_hi(hi) { }
+ inline uint128(const uint128& other) : m_lo(other.m_lo), m_hi(other.m_hi) { }
+
+ inline uint128& set_signed(int64_t lo) { m_lo = lo; m_hi = (lo < 0) ? UINT64_MAX : 0; return *this; }
+ inline uint128& set(uint64_t lo) { m_lo = lo; m_hi = 0; return *this; }
+
+ inline explicit operator uint8_t () const { return (uint8_t)m_lo; }
+ inline explicit operator uint16_t () const { return (uint16_t)m_lo; }
+ inline explicit operator uint32_t () const { return (uint32_t)m_lo; }
+ inline explicit operator uint64_t () const { return m_lo; }
+
+ inline uint128& operator= (const uint128& rhs) { m_lo = rhs.m_lo; m_hi = rhs.m_hi; return *this; }
+ inline uint128& operator= (const uint64_t val) { m_lo = val; m_hi = 0; return *this; }
+
+ inline uint64_t get_low() const { return m_lo; }
+ inline uint64_t& get_low() { return m_lo; }
+
+ inline uint64_t get_high() const { return m_hi; }
+ inline uint64_t& get_high() { return m_hi; }
+
+ inline bool operator== (const uint128& rhs) const { return (m_lo == rhs.m_lo) && (m_hi == rhs.m_hi); }
+ inline bool operator!= (const uint128& rhs) const { return (m_lo != rhs.m_lo) || (m_hi != rhs.m_hi); }
+
+ inline bool operator< (const uint128& rhs) const
+ {
+ if (m_hi < rhs.m_hi)
+ return true;
+
+ if (m_hi == rhs.m_hi)
+ {
+ if (m_lo < rhs.m_lo)
+ return true;
+ }
+
+ return false;
+ }
+
+ inline bool operator> (const uint128& rhs) const { return (rhs < *this); }
+
+ inline bool operator<= (const uint128& rhs) const { return (*this == rhs) || (*this < rhs); }
+ inline bool operator>= (const uint128& rhs) const { return (*this == rhs) || (*this > rhs); }
+
+ inline bool is_zero() const { return (m_lo == 0) && (m_hi == 0); }
+ inline bool is_all_ones() const { return (m_lo == UINT64_MAX) && (m_hi == UINT64_MAX); }
+ inline bool is_non_zero() const { return (m_lo != 0) || (m_hi != 0); }
+ inline explicit operator bool() const { return is_non_zero(); }
+ inline bool is_signed() const { return ((int64_t)m_hi) < 0; }
+
+ inline bool signed_less(const uint128& rhs) const
+ {
+ const bool l_signed = is_signed(), r_signed = rhs.is_signed();
+
+ if (l_signed == r_signed)
+ return *this < rhs;
+
+ if (l_signed && !r_signed)
+ return true;
+
+ assert(!l_signed && r_signed);
+ return false;
+ }
+
+ inline bool signed_greater(const uint128& rhs) const { return rhs.signed_less(*this); }
+ inline bool signed_less_equal(const uint128& rhs) const { return !rhs.signed_less(*this); }
+ inline bool signed_greater_equal(const uint128& rhs) const { return !signed_less(rhs); }
+
+ double get_double() const
+ {
+ double res = 0;
+
+ if (m_hi)
+ res = (double)m_hi * pow(2.0f, 64.0f);
+
+ res += (double)m_lo;
+
+ return res;
+ }
+
+ double get_signed_double() const
+ {
+ if (is_signed())
+ return -(uint128(*this).abs().get_double());
+ else
+ return get_double();
+ }
+
+ inline uint128 abs() const
+ {
+ uint128 res(*this);
+ if (res.is_signed())
+ res = -res;
+ return res;
+ }
+
+ inline uint128& operator<<= (int shift)
+ {
+ assert(shift >= 0);
+ if (shift < 0)
+ return *this;
+
+ m_hi = (shift >= 64) ? ((shift >= 128) ? 0 : (m_lo << (shift - 64))) : (m_hi << shift);
+
+ if ((shift) && (shift < 64))
+ m_hi |= (m_lo >> (64 - shift));
+
+ m_lo = (shift >= 64) ? 0 : (m_lo << shift);
+
+ return *this;
+ }
+
+ inline uint128 operator<< (int shift) const { uint128 res(*this); res <<= shift; return res; }
+
+ inline uint128& operator>>= (int shift)
+ {
+ assert(shift >= 0);
+ if (shift < 0)
+ return *this;
+
+ m_lo = (shift >= 64) ? ((shift >= 128) ? 0 : (m_hi >> (shift - 64))) : (m_lo >> shift);
+
+ if ((shift) && (shift < 64))
+ m_lo |= (m_hi << (64 - shift));
+
+ m_hi = (shift >= 64) ? 0 : (m_hi >> shift);
+
+ return *this;
+ }
+
+ inline uint128 operator>> (int shift) const { uint128 res(*this); res >>= shift; return res; }
+
+ inline uint128 signed_shift_right(int shift) const
+ {
+ uint128 res(*this);
+ res >>= shift;
+
+ if (is_signed())
+ {
+ uint128 x(0U);
+ x = ~x;
+ x >>= shift;
+ res |= (~x);
+ }
+
+ return res;
+ }
+
+ inline uint128& operator |= (const uint128& rhs) { m_lo |= rhs.m_lo; m_hi |= rhs.m_hi; return *this; }
+ inline uint128 operator | (const uint128& rhs) const { uint128 res(*this); res |= rhs; return res; }
+
+ inline uint128& operator &= (const uint128& rhs) { m_lo &= rhs.m_lo; m_hi &= rhs.m_hi; return *this; }
+ inline uint128 operator & (const uint128& rhs) const { uint128 res(*this); res &= rhs; return res; }
+
+ inline uint128& operator ^= (const uint128& rhs) { m_lo ^= rhs.m_lo; m_hi ^= rhs.m_hi; return *this; }
+ inline uint128 operator ^ (const uint128& rhs) const { uint128 res(*this); res ^= rhs; return res; }
+
+ inline uint128 operator ~() const { return uint128(~m_lo, ~m_hi); }
+
+ inline uint128 operator -() const { uint128 res(~*this); if (++res.m_lo == 0) ++res.m_hi; return res; }
+
+ // prefix
+ inline uint128 operator ++()
+ {
+ if (++m_lo == 0)
+ ++m_hi;
+ return *this;
+ }
+
+ // postfix
+ inline uint128 operator ++(int)
+ {
+ uint128 res(*this);
+ if (++m_lo == 0)
+ ++m_hi;
+ return res;
+ }
+
+ // prefix
+ inline uint128 operator --()
+ {
+ const uint64_t t = m_lo;
+ if (--m_lo > t)
+ --m_hi;
+ return *this;
+ }
+
+ // postfix
+ inline uint128 operator --(int)
+ {
+ const uint64_t t = m_lo;
+ uint128 res(*this);
+ if (--m_lo > t)
+ --m_hi;
+ return res;
+ }
+
+ inline uint128& operator+= (const uint128& rhs)
+ {
+ const uint64_t t = m_lo + rhs.m_lo;
+ m_hi = m_hi + rhs.m_hi + (t < m_lo);
+ m_lo = t;
+ return *this;
+ }
+
+ inline uint128 operator+ (const uint128& rhs) const { uint128 res(*this); res += rhs; return res; }
+
+ inline uint128& operator-= (const uint128& rhs)
+ {
+ const uint64_t t = m_lo - rhs.m_lo;
+ m_hi = m_hi - rhs.m_hi - (t > m_lo);
+ m_lo = t;
+ return *this;
+ }
+
+ inline uint128 operator- (const uint128& rhs) const { uint128 res(*this); res -= rhs; return res; }
+
+ // computes bit by bit, very slow
+ uint128& operator*=(const uint128& rhs)
+ {
+ uint128 temp(*this), result(0U);
+
+ for (uint128 bitmask(rhs); bitmask; bitmask >>= 1, temp <<= 1)
+ if (bitmask.get_low() & 1)
+ result += temp;
+
+ *this = result;
+ return *this;
+ }
+
+ uint128 operator*(const uint128& rhs) const { uint128 res(*this); res *= rhs; return res; }
+
+ // computes bit by bit, very slow
+ friend uint128 divide(const uint128& dividend, const uint128& divisor, uint128& remainder)
+ {
+ remainder = 0;
+
+ if (!divisor)
+ {
+ assert(0);
+ return ~uint128(0U);
+ }
+
+ uint128 quotient(0), one(1);
+
+ for (int i = 127; i >= 0; i--)
+ {
+ remainder = (remainder << 1) | ((dividend >> i) & one);
+ if (remainder >= divisor)
+ {
+ remainder -= divisor;
+ quotient |= (one << i);
+ }
+ }
+
+ return quotient;
+ }
+
+ uint128 operator/(const uint128& rhs) const { uint128 remainder, res; res = divide(*this, rhs, remainder); return res; }
+ uint128 operator/=(const uint128& rhs) { uint128 remainder; *this = divide(*this, rhs, remainder); return *this; }
+
+ uint128 operator%(const uint128& rhs) const { uint128 remainder; divide(*this, rhs, remainder); return remainder; }
+ uint128 operator%=(const uint128& rhs) { uint128 remainder; divide(*this, rhs, remainder); *this = remainder; return *this; }
+
+ void print_hex(FILE* pFile) const
+ {
+ fprintf(pFile, "0x%016llx%016llx", (unsigned long long int)m_hi, (unsigned long long int)m_lo);
+ }
+
+ void format_unsigned(std::string& res) const
+ {
+ basisu::vector<uint8_t> digits;
+ digits.reserve(39 + 1);
+
+ uint128 k(*this), ten(10);
+ do
+ {
+ uint128 r;
+ k = divide(k, ten, r);
+ digits.push_back((uint8_t)r);
+ } while (k);
+
+ for (int i = (int)digits.size() - 1; i >= 0; i--)
+ res += ('0' + digits[i]);
+ }
+
+ void format_signed(std::string& res) const
+ {
+ uint128 val(*this);
+
+ if (val.is_signed())
+ {
+ res.push_back('-');
+ val = -val;
+ }
+
+ val.format_unsigned(res);
+ }
+
+ void print_unsigned(FILE* pFile)
+ {
+ std::string str;
+ format_unsigned(str);
+ fprintf(pFile, "%s", str.c_str());
+ }
+
+ void print_signed(FILE* pFile)
+ {
+ std::string str;
+ format_signed(str);
+ fprintf(pFile, "%s", str.c_str());
+ }
+
+ uint128 get_reversed_bits() const
+ {
+ uint128 res;
+
+ const uint32_t* pSrc = (const uint32_t*)this;
+ uint32_t* pDst = (uint32_t*)&res;
+
+ pDst[0] = rev_dword(pSrc[3]);
+ pDst[1] = rev_dword(pSrc[2]);
+ pDst[2] = rev_dword(pSrc[1]);
+ pDst[3] = rev_dword(pSrc[0]);
+
+ return res;
+ }
+
+ uint128 get_byteswapped() const
+ {
+ uint128 res;
+
+ const uint8_t* pSrc = (const uint8_t*)this;
+ uint8_t* pDst = (uint8_t*)&res;
+
+ for (uint32_t i = 0; i < 16; i++)
+ pDst[i] = pSrc[15 - i];
+
+ return res;
+ }
+
+ inline uint64_t get_bits64(uint32_t bit_ofs, uint32_t bit_len) const
+ {
+ assert(bit_ofs < 128);
+ assert(bit_len && (bit_len <= 64) && ((bit_ofs + bit_len) <= 128));
+
+ uint128 res(*this);
+ res >>= bit_ofs;
+
+ const uint64_t bitmask = (bit_len == 64) ? UINT64_MAX : ((1ull << bit_len) - 1);
+ return res.get_low() & bitmask;
+ }
+
+ inline uint32_t get_bits(uint32_t bit_ofs, uint32_t bit_len) const
+ {
+ assert(bit_len <= 32);
+ return (uint32_t)get_bits64(bit_ofs, bit_len);
+ }
+
+ inline uint32_t next_bits(uint32_t& bit_ofs, uint32_t len) const
+ {
+ assert(len && (len <= 32));
+ uint32_t x = get_bits(bit_ofs, len);
+ bit_ofs += len;
+ return x;
+ }
+
+ inline uint128& set_bits(uint64_t val, uint32_t bit_ofs, uint32_t num_bits)
+ {
+ assert(bit_ofs < 128);
+ assert(num_bits && (num_bits <= 64) && ((bit_ofs + num_bits) <= 128));
+
+ uint128 bitmask(1);
+ bitmask = (bitmask << num_bits) - 1;
+ assert(uint128(val) <= bitmask);
+
+ bitmask <<= bit_ofs;
+ *this &= ~bitmask;
+
+ *this = *this | (uint128(val) << bit_ofs);
+ return *this;
+ }
+ };
+
+ static bool decode_void_extent(const uint128& bits, log_astc_block& log_blk)
+ {
+ if (bits.get_bits(10, 2) != 0b11)
+ return false;
+
+ uint32_t bit_ofs = 12;
+ const uint32_t min_s = bits.next_bits(bit_ofs, 13);
+ const uint32_t max_s = bits.next_bits(bit_ofs, 13);
+ const uint32_t min_t = bits.next_bits(bit_ofs, 13);
+ const uint32_t max_t = bits.next_bits(bit_ofs, 13);
+ assert(bit_ofs == 64);
+
+ const bool all_extents_all_ones = (min_s == 0x1FFF) && (max_s == 0x1FFF) && (min_t == 0x1FFF) && (max_t == 0x1FFF);
+
+ if (!all_extents_all_ones && ((min_s >= max_s) || (min_t >= max_t)))
+ return false;
+
+ const bool hdr_flag = bits.get_bits(9, 1) != 0;
+
+ if (hdr_flag)
+ log_blk.m_solid_color_flag_hdr = true;
+ else
+ log_blk.m_solid_color_flag_ldr = true;
+
+ log_blk.m_solid_color[0] = (uint16_t)bits.get_bits(64, 16);
+ log_blk.m_solid_color[1] = (uint16_t)bits.get_bits(80, 16);
+ log_blk.m_solid_color[2] = (uint16_t)bits.get_bits(96, 16);
+ log_blk.m_solid_color[3] = (uint16_t)bits.get_bits(112, 16);
+
+ if (log_blk.m_solid_color_flag_hdr)
+ {
+ for (uint32_t c = 0; c < 4; c++)
+ if (is_half_inf_or_nan(log_blk.m_solid_color[c]))
+ return false;
+ }
+
+ return true;
+ }
+
+ struct astc_dec_row
+ {
+ int8_t Dp_ofs, P_ofs, W_ofs, W_size, H_ofs, H_size, W_bias, H_bias, p0_ofs, p1_ofs, p2_ofs;
+ };
+
+ static const astc_dec_row s_dec_rows[10] =
+ {
+ // Dp_ofs, P_ofs, W_ofs, W_size, H_ofs, H_size, W_bias, H_bias, p0_ofs, p1_ofs, p2_ofs;
+ { 10, 9, 7, 2, 5, 2, 4, 2, 4, 0, 1 }, // 4 2
+ { 10, 9, 7, 2, 5, 2, 8, 2, 4, 0, 1 }, // 8 2
+ { 10, 9, 5, 2, 7, 2, 2, 8, 4, 0, 1 }, // 2 8
+ { 10, 9, 5, 2, 7, 1, 2, 6, 4, 0, 1 }, // 2 6
+
+ { 10, 9, 7, 1, 5, 2, 2, 2, 4, 0, 1 }, // 2 2
+ { 10, 9, 0, 0, 5, 2, 12, 2, 4, 2, 3 }, // 12 2
+ { 10, 9, 5, 2, 0, 0, 2, 12, 4, 2, 3 }, // 2 12
+ { 10, 9, 0, 0, 0, 0, 6, 10, 4, 2, 3 }, // 6 10
+
+ { 10, 9, 0, 0, 0, 0, 10, 6, 4, 2, 3 }, // 10 6
+ { -1, -1, 5, 2, 9, 2, 6, 6, 4, 2, 3 }, // 6 6
+ };
+
+ static bool decode_config(const uint128& bits, log_astc_block& log_blk)
+ {
+ // Reserved
+ if (bits.get_bits(0, 4) == 0)
+ return false;
+
+ // Reserved
+ if ((bits.get_bits(0, 2) == 0) && (bits.get_bits(6, 3) == 0b111))
+ {
+ if (bits.get_bits(2, 4) != 0b1111)
+ return false;
+ }
+
+ // Void extent
+ if (bits.get_bits(0, 9) == 0b111111100)
+ return decode_void_extent(bits, log_blk);
+
+ // Check rows
+ const uint32_t x0_2 = bits.get_bits(0, 2), x2_2 = bits.get_bits(2, 2);
+ const uint32_t x5_4 = bits.get_bits(5, 4), x8_1 = bits.get_bits(8, 1);
+ const uint32_t x7_2 = bits.get_bits(7, 2);
+
+ int row_index = -1;
+ if (x0_2 == 0)
+ {
+ if (x7_2 == 0b00)
+ row_index = 5;
+ else if (x7_2 == 0b01)
+ row_index = 6;
+ else if (x5_4 == 0b1100)
+ row_index = 7;
+ else if (x5_4 == 0b1101)
+ row_index = 8;
+ else if (x7_2 == 0b10)
+ row_index = 9;
+ }
+ else
+ {
+ if (x2_2 == 0b00)
+ row_index = 0;
+ else if (x2_2 == 0b01)
+ row_index = 1;
+ else if (x2_2 == 0b10)
+ row_index = 2;
+ else if ((x2_2 == 0b11) && (x8_1 == 0))
+ row_index = 3;
+ else if ((x2_2 == 0b11) && (x8_1 == 1))
+ row_index = 4;
+ }
+ if (row_index < 0)
+ return false;
+
+ const astc_dec_row& r = s_dec_rows[row_index];
+
+ bool P = false, Dp = false;
+ uint32_t W = r.W_bias, H = r.H_bias;
+
+ if (r.P_ofs >= 0)
+ P = bits.get_bits(r.P_ofs, 1) != 0;
+
+ if (r.Dp_ofs >= 0)
+ Dp = bits.get_bits(r.Dp_ofs, 1) != 0;
+
+ if (r.W_size)
+ W += bits.get_bits(r.W_ofs, r.W_size);
+
+ if (r.H_size)
+ H += bits.get_bits(r.H_ofs, r.H_size);
+
+ assert((W >= MIN_GRID_DIM) && (W <= MAX_BLOCK_DIM));
+ assert((H >= MIN_GRID_DIM) && (H <= MAX_BLOCK_DIM));
+
+ int p0 = bits.get_bits(r.p0_ofs, 1);
+ int p1 = bits.get_bits(r.p1_ofs, 1);
+ int p2 = bits.get_bits(r.p2_ofs, 1);
+
+ uint32_t p = p0 | (p1 << 1) | (p2 << 2);
+ if (p < 2)
+ return false;
+
+ log_blk.m_grid_width = W;
+ log_blk.m_grid_height = H;
+
+ log_blk.m_weight_ise_range = (p - 2) + (P * BISE_10_LEVELS);
+ assert(log_blk.m_weight_ise_range <= LAST_VALID_WEIGHT_ISE_RANGE);
+
+ log_blk.m_dual_plane = Dp;
+
+ return true;
+ }
+
+ static inline uint32_t read_le_dword(const uint8_t* pBytes)
+ {
+ return (pBytes[0]) | (pBytes[1] << 8U) | (pBytes[2] << 16U) | (pBytes[3] << 24U);
+ }
+
+ // See 18.12.Integer Sequence Encoding - tables computed by executing the decoder functions with all possible 8/7-bit inputs.
+ static const uint8_t s_trit_decode[256][5] =
+ {
+ {0,0,0,0,0},{1,0,0,0,0},{2,0,0,0,0},{0,0,2,0,0},{0,1,0,0,0},{1,1,0,0,0},{2,1,0,0,0},{1,0,2,0,0},
+ {0,2,0,0,0},{1,2,0,0,0},{2,2,0,0,0},{2,0,2,0,0},{0,2,2,0,0},{1,2,2,0,0},{2,2,2,0,0},{2,0,2,0,0},
+ {0,0,1,0,0},{1,0,1,0,0},{2,0,1,0,0},{0,1,2,0,0},{0,1,1,0,0},{1,1,1,0,0},{2,1,1,0,0},{1,1,2,0,0},
+ {0,2,1,0,0},{1,2,1,0,0},{2,2,1,0,0},{2,1,2,0,0},{0,0,0,2,2},{1,0,0,2,2},{2,0,0,2,2},{0,0,2,2,2},
+ {0,0,0,1,0},{1,0,0,1,0},{2,0,0,1,0},{0,0,2,1,0},{0,1,0,1,0},{1,1,0,1,0},{2,1,0,1,0},{1,0,2,1,0},
+ {0,2,0,1,0},{1,2,0,1,0},{2,2,0,1,0},{2,0,2,1,0},{0,2,2,1,0},{1,2,2,1,0},{2,2,2,1,0},{2,0,2,1,0},
+ {0,0,1,1,0},{1,0,1,1,0},{2,0,1,1,0},{0,1,2,1,0},{0,1,1,1,0},{1,1,1,1,0},{2,1,1,1,0},{1,1,2,1,0},
+ {0,2,1,1,0},{1,2,1,1,0},{2,2,1,1,0},{2,1,2,1,0},{0,1,0,2,2},{1,1,0,2,2},{2,1,0,2,2},{1,0,2,2,2},
+ {0,0,0,2,0},{1,0,0,2,0},{2,0,0,2,0},{0,0,2,2,0},{0,1,0,2,0},{1,1,0,2,0},{2,1,0,2,0},{1,0,2,2,0},
+ {0,2,0,2,0},{1,2,0,2,0},{2,2,0,2,0},{2,0,2,2,0},{0,2,2,2,0},{1,2,2,2,0},{2,2,2,2,0},{2,0,2,2,0},
+ {0,0,1,2,0},{1,0,1,2,0},{2,0,1,2,0},{0,1,2,2,0},{0,1,1,2,0},{1,1,1,2,0},{2,1,1,2,0},{1,1,2,2,0},
+ {0,2,1,2,0},{1,2,1,2,0},{2,2,1,2,0},{2,1,2,2,0},{0,2,0,2,2},{1,2,0,2,2},{2,2,0,2,2},{2,0,2,2,2},
+ {0,0,0,0,2},{1,0,0,0,2},{2,0,0,0,2},{0,0,2,0,2},{0,1,0,0,2},{1,1,0,0,2},{2,1,0,0,2},{1,0,2,0,2},
+ {0,2,0,0,2},{1,2,0,0,2},{2,2,0,0,2},{2,0,2,0,2},{0,2,2,0,2},{1,2,2,0,2},{2,2,2,0,2},{2,0,2,0,2},
+ {0,0,1,0,2},{1,0,1,0,2},{2,0,1,0,2},{0,1,2,0,2},{0,1,1,0,2},{1,1,1,0,2},{2,1,1,0,2},{1,1,2,0,2},
+ {0,2,1,0,2},{1,2,1,0,2},{2,2,1,0,2},{2,1,2,0,2},{0,2,2,2,2},{1,2,2,2,2},{2,2,2,2,2},{2,0,2,2,2},
+ {0,0,0,0,1},{1,0,0,0,1},{2,0,0,0,1},{0,0,2,0,1},{0,1,0,0,1},{1,1,0,0,1},{2,1,0,0,1},{1,0,2,0,1},
+ {0,2,0,0,1},{1,2,0,0,1},{2,2,0,0,1},{2,0,2,0,1},{0,2,2,0,1},{1,2,2,0,1},{2,2,2,0,1},{2,0,2,0,1},
+ {0,0,1,0,1},{1,0,1,0,1},{2,0,1,0,1},{0,1,2,0,1},{0,1,1,0,1},{1,1,1,0,1},{2,1,1,0,1},{1,1,2,0,1},
+ {0,2,1,0,1},{1,2,1,0,1},{2,2,1,0,1},{2,1,2,0,1},{0,0,1,2,2},{1,0,1,2,2},{2,0,1,2,2},{0,1,2,2,2},
+ {0,0,0,1,1},{1,0,0,1,1},{2,0,0,1,1},{0,0,2,1,1},{0,1,0,1,1},{1,1,0,1,1},{2,1,0,1,1},{1,0,2,1,1},
+ {0,2,0,1,1},{1,2,0,1,1},{2,2,0,1,1},{2,0,2,1,1},{0,2,2,1,1},{1,2,2,1,1},{2,2,2,1,1},{2,0,2,1,1},
+ {0,0,1,1,1},{1,0,1,1,1},{2,0,1,1,1},{0,1,2,1,1},{0,1,1,1,1},{1,1,1,1,1},{2,1,1,1,1},{1,1,2,1,1},
+ {0,2,1,1,1},{1,2,1,1,1},{2,2,1,1,1},{2,1,2,1,1},{0,1,1,2,2},{1,1,1,2,2},{2,1,1,2,2},{1,1,2,2,2},
+ {0,0,0,2,1},{1,0,0,2,1},{2,0,0,2,1},{0,0,2,2,1},{0,1,0,2,1},{1,1,0,2,1},{2,1,0,2,1},{1,0,2,2,1},
+ {0,2,0,2,1},{1,2,0,2,1},{2,2,0,2,1},{2,0,2,2,1},{0,2,2,2,1},{1,2,2,2,1},{2,2,2,2,1},{2,0,2,2,1},
+ {0,0,1,2,1},{1,0,1,2,1},{2,0,1,2,1},{0,1,2,2,1},{0,1,1,2,1},{1,1,1,2,1},{2,1,1,2,1},{1,1,2,2,1},
+ {0,2,1,2,1},{1,2,1,2,1},{2,2,1,2,1},{2,1,2,2,1},{0,2,1,2,2},{1,2,1,2,2},{2,2,1,2,2},{2,1,2,2,2},
+ {0,0,0,1,2},{1,0,0,1,2},{2,0,0,1,2},{0,0,2,1,2},{0,1,0,1,2},{1,1,0,1,2},{2,1,0,1,2},{1,0,2,1,2},
+ {0,2,0,1,2},{1,2,0,1,2},{2,2,0,1,2},{2,0,2,1,2},{0,2,2,1,2},{1,2,2,1,2},{2,2,2,1,2},{2,0,2,1,2},
+ {0,0,1,1,2},{1,0,1,1,2},{2,0,1,1,2},{0,1,2,1,2},{0,1,1,1,2},{1,1,1,1,2},{2,1,1,1,2},{1,1,2,1,2},
+ {0,2,1,1,2},{1,2,1,1,2},{2,2,1,1,2},{2,1,2,1,2},{0,2,2,2,2},{1,2,2,2,2},{2,2,2,2,2},{2,1,2,2,2}
+ };
+
+ static const uint8_t s_quint_decode[128][3] =
+ {
+ {0,0,0},{1,0,0},{2,0,0},{3,0,0},{4,0,0},{0,4,0},{4,4,0},{4,4,4},
+ {0,1,0},{1,1,0},{2,1,0},{3,1,0},{4,1,0},{1,4,0},{4,4,1},{4,4,4},
+ {0,2,0},{1,2,0},{2,2,0},{3,2,0},{4,2,0},{2,4,0},{4,4,2},{4,4,4},
+ {0,3,0},{1,3,0},{2,3,0},{3,3,0},{4,3,0},{3,4,0},{4,4,3},{4,4,4},
+ {0,0,1},{1,0,1},{2,0,1},{3,0,1},{4,0,1},{0,4,1},{4,0,4},{0,4,4},
+ {0,1,1},{1,1,1},{2,1,1},{3,1,1},{4,1,1},{1,4,1},{4,1,4},{1,4,4},
+ {0,2,1},{1,2,1},{2,2,1},{3,2,1},{4,2,1},{2,4,1},{4,2,4},{2,4,4},
+ {0,3,1},{1,3,1},{2,3,1},{3,3,1},{4,3,1},{3,4,1},{4,3,4},{3,4,4},
+ {0,0,2},{1,0,2},{2,0,2},{3,0,2},{4,0,2},{0,4,2},{2,0,4},{3,0,4},
+ {0,1,2},{1,1,2},{2,1,2},{3,1,2},{4,1,2},{1,4,2},{2,1,4},{3,1,4},
+ {0,2,2},{1,2,2},{2,2,2},{3,2,2},{4,2,2},{2,4,2},{2,2,4},{3,2,4},
+ {0,3,2},{1,3,2},{2,3,2},{3,3,2},{4,3,2},{3,4,2},{2,3,4},{3,3,4},
+ {0,0,3},{1,0,3},{2,0,3},{3,0,3},{4,0,3},{0,4,3},{0,0,4},{1,0,4},
+ {0,1,3},{1,1,3},{2,1,3},{3,1,3},{4,1,3},{1,4,3},{0,1,4},{1,1,4},
+ {0,2,3},{1,2,3},{2,2,3},{3,2,3},{4,2,3},{2,4,3},{0,2,4},{1,2,4},
+ {0,3,3},{1,3,3},{2,3,3},{3,3,3},{4,3,3},{3,4,3},{0,3,4},{1,3,4}
+ };
+
+ static void decode_trit_block(uint8_t* pVals, uint32_t num_vals, const uint128& bits, uint32_t& bit_ofs, uint32_t bits_per_val)
+ {
+ assert((num_vals >= 1) && (num_vals <= 5));
+ uint32_t m[5] = { 0 }, T = 0;
+
+ static const uint8_t s_t_bits[5] = { 2, 2, 1, 2, 1 };
+
+ for (uint32_t T_ofs = 0, c = 0; c < num_vals; c++)
+ {
+ if (bits_per_val)
+ m[c] = bits.next_bits(bit_ofs, bits_per_val);
+ T |= (bits.next_bits(bit_ofs, s_t_bits[c]) << T_ofs);
+ T_ofs += s_t_bits[c];
+ }
+
+ const uint8_t (&p_trits)[5] = s_trit_decode[T];
+
+ for (uint32_t i = 0; i < num_vals; i++)
+ pVals[i] = (uint8_t)((p_trits[i] << bits_per_val) | m[i]);
+ }
+
+ static void decode_quint_block(uint8_t* pVals, uint32_t num_vals, const uint128& bits, uint32_t& bit_ofs, uint32_t bits_per_val)
+ {
+ assert((num_vals >= 1) && (num_vals <= 3));
+ uint32_t m[3] = { 0 }, T = 0;
+
+ static const uint8_t s_t_bits[3] = { 3, 2, 2 };
+
+ for (uint32_t T_ofs = 0, c = 0; c < num_vals; c++)
+ {
+ if (bits_per_val)
+ m[c] = bits.next_bits(bit_ofs, bits_per_val);
+ T |= (bits.next_bits(bit_ofs, s_t_bits[c]) << T_ofs);
+ T_ofs += s_t_bits[c];
+ }
+
+ const uint8_t (&p_quints)[3] = s_quint_decode[T];
+
+ for (uint32_t i = 0; i < num_vals; i++)
+ pVals[i] = (uint8_t)((p_quints[i] << bits_per_val) | m[i]);
+ }
+
+ static void decode_bise(uint32_t ise_range, uint8_t* pVals, uint32_t num_vals, const uint128& bits, uint32_t bit_ofs)
+ {
+ assert(num_vals && (ise_range < TOTAL_ISE_RANGES));
+
+ const uint32_t bits_per_val = g_ise_range_table[ise_range][0];
+
+ if (g_ise_range_table[ise_range][1])
+ {
+ // Trits+bits, 5 vals per block, 7 bits extra per block
+ const uint32_t total_blocks = (num_vals + 4) / 5;
+ for (uint32_t b = 0; b < total_blocks; b++)
+ {
+ const uint32_t num_vals_in_block = std::min<int>(num_vals - 5 * b, 5);
+ decode_trit_block(pVals + 5 * b, num_vals_in_block, bits, bit_ofs, bits_per_val);
+ }
+ }
+ else if (g_ise_range_table[ise_range][2])
+ {
+ // Quints+bits, 3 vals per block, 8 bits extra per block
+ const uint32_t total_blocks = (num_vals + 2) / 3;
+ for (uint32_t b = 0; b < total_blocks; b++)
+ {
+ const uint32_t num_vals_in_block = std::min<int>(num_vals - 3 * b, 3);
+ decode_quint_block(pVals + 3 * b, num_vals_in_block, bits, bit_ofs, bits_per_val);
+ }
+ }
+ else
+ {
+ assert(bits_per_val);
+
+ // Only bits
+ for (uint32_t i = 0; i < num_vals; i++)
+ pVals[i] = (uint8_t)bits.next_bits(bit_ofs, bits_per_val);
+ }
+ }
+
+ void decode_bise(uint32_t ise_range, uint8_t* pVals, uint32_t num_vals, const uint8_t* pBits128, uint32_t bit_ofs)
+ {
+ const uint128 bits(
+ (uint64_t)read_le_dword(pBits128) | (((uint64_t)read_le_dword(pBits128 + sizeof(uint32_t))) << 32),
+ (uint64_t)read_le_dword(pBits128 + sizeof(uint32_t) * 2) | (((uint64_t)read_le_dword(pBits128 + sizeof(uint32_t) * 3)) << 32));
+
+ return decode_bise(ise_range, pVals, num_vals, bits, bit_ofs);
+ }
+
+ // Decodes a physical ASTC block to a logical ASTC block.
+ // blk_width/blk_height are only used to validate the weight grid's dimensions.
+ bool unpack_block(const void* pASTC_block, log_astc_block& log_blk, uint32_t blk_width, uint32_t blk_height)
+ {
+ assert(is_valid_block_size(blk_width, blk_height));
+
+ const uint8_t* pS = (uint8_t*)pASTC_block;
+
+ log_blk.clear();
+ log_blk.m_error_flag = true;
+
+ const uint128 bits(
+ (uint64_t)read_le_dword(pS) | (((uint64_t)read_le_dword(pS + sizeof(uint32_t))) << 32),
+ (uint64_t)read_le_dword(pS + sizeof(uint32_t) * 2) | (((uint64_t)read_le_dword(pS + sizeof(uint32_t) * 3)) << 32));
+
+ const uint128 rev_bits(bits.get_reversed_bits());
+
+ if (!decode_config(bits, log_blk))
+ return false;
+
+ if (log_blk.m_solid_color_flag_hdr || log_blk.m_solid_color_flag_ldr)
+ {
+ // Void extent
+ log_blk.m_error_flag = false;
+ return true;
+ }
+
+ // Check grid dimensions
+ if ((log_blk.m_grid_width > blk_width) || (log_blk.m_grid_height > blk_height))
+ return false;
+
+ // Now we have the grid width/height, dual plane, weight ISE range
+
+ const uint32_t total_grid_weights = (log_blk.m_dual_plane ? 2 : 1) * (log_blk.m_grid_width * log_blk.m_grid_height);
+ const uint32_t total_weight_bits = get_ise_sequence_bits(total_grid_weights, log_blk.m_weight_ise_range);
+
+ // 18.24 Illegal Encodings
+ if ((!total_grid_weights) || (total_grid_weights > MAX_GRID_WEIGHTS) || (total_weight_bits < 24) || (total_weight_bits > 96))
+ return false;
+
+ const uint32_t end_of_weight_bit_ofs = 128 - total_weight_bits;
+
+ uint32_t total_extra_bits = 0;
+
+ // Right before the weight bits, there may be extra CEM bits, then the 2 CCS bits if dual plane.
+
+ log_blk.m_num_partitions = bits.get_bits(11, 2) + 1;
+ if (log_blk.m_num_partitions == 1)
+ log_blk.m_color_endpoint_modes[0] = bits.get_bits(13, 4); // read CEM bits
+ else
+ {
+ // 2 or more partitions
+ if (log_blk.m_dual_plane && (log_blk.m_num_partitions == 4))
+ return false;
+
+ log_blk.m_partition_id = bits.get_bits(13, 10);
+
+ uint32_t cem_bits = bits.get_bits(23, 6);
+
+ if ((cem_bits & 3) == 0)
+ {
+ // All CEM's the same
+ for (uint32_t i = 0; i < log_blk.m_num_partitions; i++)
+ log_blk.m_color_endpoint_modes[i] = cem_bits >> 2;
+ }
+ else
+ {
+ // CEM's different, but within up to 2 adjacent classes
+ const uint32_t first_cem_index = ((cem_bits & 3) - 1) * 4;
+
+ total_extra_bits = 3 * log_blk.m_num_partitions - 4;
+
+ if ((total_weight_bits + total_extra_bits) > 128)
+ return false;
+
+ uint32_t cem_bit_pos = end_of_weight_bit_ofs - total_extra_bits;
+
+ uint32_t c[4] = { 0 }, m[4] = { 0 };
+
+ cem_bits >>= 2;
+ for (uint32_t i = 0; i < log_blk.m_num_partitions; i++, cem_bits >>= 1)
+ c[i] = cem_bits & 1;
+
+ switch (log_blk.m_num_partitions)
+ {
+ case 2:
+ {
+ m[0] = cem_bits & 3;
+ m[1] = bits.next_bits(cem_bit_pos, 2);
+ break;
+ }
+ case 3:
+ {
+ m[0] = cem_bits & 1;
+ m[0] |= (bits.next_bits(cem_bit_pos, 1) << 1);
+ m[1] = bits.next_bits(cem_bit_pos, 2);
+ m[2] = bits.next_bits(cem_bit_pos, 2);
+ break;
+ }
+ case 4:
+ {
+ for (uint32_t i = 0; i < 4; i++)
+ m[i] = bits.next_bits(cem_bit_pos, 2);
+ break;
+ }
+ default:
+ {
+ assert(0);
+ break;
+ }
+ }
+
+ assert(cem_bit_pos == end_of_weight_bit_ofs);
+
+ for (uint32_t i = 0; i < log_blk.m_num_partitions; i++)
+ {
+ log_blk.m_color_endpoint_modes[i] = first_cem_index + (c[i] * 4) + m[i];
+ assert(log_blk.m_color_endpoint_modes[i] <= 15);
+ }
+ }
+ }
+
+ // Now we have all the CEM indices.
+
+ if (log_blk.m_dual_plane)
+ {
+ // Read CCS bits, beneath any CEM bits
+ total_extra_bits += 2;
+
+ if (total_extra_bits > end_of_weight_bit_ofs)
+ return false;
+
+ uint32_t ccs_bit_pos = end_of_weight_bit_ofs - total_extra_bits;
+ log_blk.m_color_component_selector = bits.get_bits(ccs_bit_pos, 2);
+ }
+
+ uint32_t config_bit_pos = 11 + 2; // config+num_parts
+ if (log_blk.m_num_partitions == 1)
+ config_bit_pos += 4; // CEM bits
+ else
+ config_bit_pos += 10 + 6; // part_id+CEM bits
+
+ // config+num_parts+total_extra_bits (CEM extra+CCS)
+ uint32_t total_config_bits = config_bit_pos + total_extra_bits;
+
+ // Compute number of remaining bits in block
+ const int num_remaining_bits = 128 - (int)total_config_bits - (int)total_weight_bits;
+ if (num_remaining_bits < 0)
+ return false;
+
+ // Compute total number of ISE encoded color endpoint mode values
+ uint32_t total_cem_vals = 0;
+ for (uint32_t j = 0; j < log_blk.m_num_partitions; j++)
+ total_cem_vals += get_num_cem_values(log_blk.m_color_endpoint_modes[j]);
+
+ if (total_cem_vals > MAX_ENDPOINTS)
+ return false;
+
+ // Infer endpoint ISE range based off the # of values we need to encode, and the # of remaining bits in the block
+ int endpoint_ise_range = -1;
+ for (int k = 20; k > 0; k--)
+ {
+ int b = get_ise_sequence_bits(total_cem_vals, k);
+ if (b <= num_remaining_bits)
+ {
+ endpoint_ise_range = k;
+ break;
+ }
+ }
+
+ // See 23.24 Illegal Encodings, [0,5] is the minimum ISE encoding for endpoints
+ if (endpoint_ise_range < (int)FIRST_VALID_ENDPOINT_ISE_RANGE)
+ return false;
+
+ log_blk.m_endpoint_ise_range = endpoint_ise_range;
+
+ // Decode endpoints forwards in block
+ decode_bise(log_blk.m_endpoint_ise_range, log_blk.m_endpoints, total_cem_vals, bits, config_bit_pos);
+
+ // Decode grid weights backwards in block
+ decode_bise(log_blk.m_weight_ise_range, log_blk.m_weights, total_grid_weights, rev_bits, 0);
+
+ log_blk.m_error_flag = false;
+
+ return true;
+ }
+
+} // namespace astc_helpers
+
+#endif //BASISU_ASTC_HELPERS_IMPLEMENTATION
diff --git a/thirdparty/basis_universal/transcoder/basisu_containers.h b/thirdparty/basis_universal/transcoder/basisu_containers.h
index d3e14369ba..bfc51bb499 100644
--- a/thirdparty/basis_universal/transcoder/basisu_containers.h
+++ b/thirdparty/basis_universal/transcoder/basisu_containers.h
@@ -188,8 +188,9 @@ namespace basisu
#define BASISU_IS_SCALAR_TYPE(T) (scalar_type<T>::cFlag)
-#if defined(__GNUC__) && __GNUC__<5
- #define BASISU_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
+#if !defined(BASISU_HAVE_STD_TRIVIALLY_COPYABLE) && defined(__GNUC__) && __GNUC__<5
+ //#define BASISU_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
+ #define BASISU_IS_TRIVIALLY_COPYABLE(...) __is_trivially_copyable(__VA_ARGS__)
#else
#define BASISU_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
#endif
@@ -286,8 +287,19 @@ namespace basisu
if (BASISU_IS_BITWISE_COPYABLE(T))
{
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
if ((m_p) && (other.m_p))
memcpy(m_p, other.m_p, m_size * sizeof(T));
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#endif
}
else
{
@@ -330,8 +342,19 @@ namespace basisu
if (BASISU_IS_BITWISE_COPYABLE(T))
{
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
if ((m_p) && (other.m_p))
memcpy(m_p, other.m_p, other.m_size * sizeof(T));
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#endif
}
else
{
@@ -501,7 +524,7 @@ namespace basisu
if (new_capacity > m_capacity)
{
- if (!increase_capacity(new_capacity, false))
+ if (!increase_capacity(new_capacity, false, true))
return false;
}
else if (new_capacity < m_capacity)
@@ -509,7 +532,8 @@ namespace basisu
// Must work around the lack of a "decrease_capacity()" method.
// This case is rare enough in practice that it's probably not worth implementing an optimized in-place resize.
vector tmp;
- tmp.increase_capacity(helpers::maximum(m_size, new_capacity), false);
+ if (!tmp.increase_capacity(helpers::maximum(m_size, new_capacity), false, true))
+ return false;
tmp = *this;
swap(tmp);
}
@@ -750,7 +774,21 @@ namespace basisu
}
// Copy "down" the objects to preserve, filling in the empty slots.
+
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
+
memmove(pDst, pSrc, num_to_move * sizeof(T));
+
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#endif
}
else
{
@@ -1003,7 +1041,21 @@ namespace basisu
inline void set_all(const T& o)
{
if ((sizeof(T) == 1) && (scalar_type<T>::cFlag))
+ {
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#endif
memset(m_p, *reinterpret_cast<const uint8_t*>(&o), m_size);
+
+#ifndef __EMSCRIPTEN__
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+#endif
+ }
else
{
T* pDst = m_p;
@@ -1029,7 +1081,7 @@ namespace basisu
// Important: This method is used in Basis Universal. If you change how this container allocates memory, you'll need to change any users of this method.
inline bool grant_ownership(T* p, uint32_t size, uint32_t capacity)
{
- // To to prevent the caller from obviously shooting themselves in the foot.
+ // To prevent the caller from obviously shooting themselves in the foot.
if (((p + capacity) > m_p) && (p < (m_p + m_capacity)))
{
// Can grant ownership of a block inside the container itself!
diff --git a/thirdparty/basis_universal/transcoder/basisu_containers_impl.h b/thirdparty/basis_universal/transcoder/basisu_containers_impl.h
index d5cb61569b..60c0b3d89f 100644
--- a/thirdparty/basis_universal/transcoder/basisu_containers_impl.h
+++ b/thirdparty/basis_universal/transcoder/basisu_containers_impl.h
@@ -19,23 +19,30 @@ namespace basisu
if (m_capacity >= min_new_capacity)
return true;
- size_t new_capacity = min_new_capacity;
- if ((grow_hint) && (!helpers::is_power_of_2((uint64_t)new_capacity)))
- {
- new_capacity = (size_t)helpers::next_pow2((uint64_t)new_capacity);
-
- assert(new_capacity && (new_capacity > m_capacity));
+ uint64_t new_capacity_u64 = min_new_capacity;
+ if ((grow_hint) && (!helpers::is_power_of_2(new_capacity_u64)))
+ new_capacity_u64 = helpers::next_pow2(new_capacity_u64);
- if (new_capacity < min_new_capacity)
- {
- if (nofail)
- return false;
- fprintf(stderr, "vector too large\n");
- abort();
- }
+ size_t new_capacity = (size_t)new_capacity_u64;
+ if (new_capacity != new_capacity_u64)
+ {
+ if (nofail)
+ return false;
+ fprintf(stderr, "elemental_vector::increase_capacity: vector too large\n");
+ abort();
}
- const size_t desired_size = element_size * new_capacity;
+ const uint64_t desired_size_u64 = (uint64_t)element_size * new_capacity;
+
+ const size_t desired_size = (size_t)desired_size_u64;
+ if (desired_size_u64 != desired_size)
+ {
+ if (nofail)
+ return false;
+ fprintf(stderr, "elemental_vector::increase_capacity: vector too large\n");
+ abort();
+ }
+
size_t actual_size = 0;
if (!pMover)
{
@@ -46,11 +53,7 @@ namespace basisu
return false;
char buf[256];
-#ifdef _MSC_VER
- sprintf_s(buf, sizeof(buf), "vector: realloc() failed allocating %u bytes", (uint32_t)desired_size);
-#else
- sprintf(buf, "vector: realloc() failed allocating %u bytes", (uint32_t)desired_size);
-#endif
+ snprintf(buf, sizeof(buf), "elemental_vector::increase_capacity: realloc() failed allocating %zu bytes", desired_size);
fprintf(stderr, "%s", buf);
abort();
}
@@ -75,11 +78,7 @@ namespace basisu
return false;
char buf[256];
-#ifdef _MSC_VER
- sprintf_s(buf, sizeof(buf), "vector: malloc() failed allocating %u bytes", (uint32_t)desired_size);
-#else
- sprintf(buf, "vector: malloc() failed allocating %u bytes", (uint32_t)desired_size);
-#endif
+ snprintf(buf, sizeof(buf), "elemental_vector::increase_capacity: malloc() failed allocating %zu bytes", desired_size);
fprintf(stderr, "%s", buf);
abort();
}
diff --git a/thirdparty/basis_universal/transcoder/basisu_file_headers.h b/thirdparty/basis_universal/transcoder/basisu_file_headers.h
index 4316d738e6..d29e3feb03 100644
--- a/thirdparty/basis_universal/transcoder/basisu_file_headers.h
+++ b/thirdparty/basis_universal/transcoder/basisu_file_headers.h
@@ -1,5 +1,5 @@
// basis_file_headers.h
-// Copyright (C) 2019-2020 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -89,7 +89,8 @@ namespace basist
enum class basis_tex_format
{
cETC1S = 0,
- cUASTC4x4 = 1
+ cUASTC4x4 = 1,
+ cUASTC_HDR_4x4 = 2
};
struct basis_file_header
diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp b/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp
index c698861f3b..32018cd282 100644
--- a/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp
+++ b/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp
@@ -1,5 +1,5 @@
// basisu_transcoder.cpp
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,6 +17,11 @@
#include <limits.h>
#include "basisu_containers_impl.h"
+#define BASISU_ASTC_HELPERS_IMPLEMENTATION
+#include "basisu_astc_helpers.h"
+
+#include "basisu_astc_hdr_core.h"
+
#ifndef BASISD_IS_BIG_ENDIAN
// TODO: This doesn't work on OSX. How can this be so difficult?
//#if defined(__BIG_ENDIAN__) || defined(_BIG_ENDIAN) || defined(BIG_ENDIAN)
@@ -139,6 +144,10 @@
#endif
#endif
+#ifndef BASISD_SUPPORT_UASTC_HDR
+ #define BASISD_SUPPORT_UASTC_HDR 1
+#endif
+
#define BASISD_WRITE_NEW_BC7_MODE5_TABLES 0
#define BASISD_WRITE_NEW_DXT1_TABLES 0
#define BASISD_WRITE_NEW_ETC2_EAC_A8_TABLES 0
@@ -1908,17 +1917,24 @@ namespace basist
void basisu_transcoder_init()
{
if (g_transcoder_initialized)
- {
- BASISU_DEVEL_ERROR("basisu_transcoder::basisu_transcoder_init: Called more than once\n");
+ {
+ BASISU_DEVEL_ERROR("basisu_transcoder::basisu_transcoder_init: Called more than once\n");
return;
- }
+ }
- BASISU_DEVEL_ERROR("basisu_transcoder::basisu_transcoder_init: Initializing (this is not an error)\n");
+ BASISU_DEVEL_ERROR("basisu_transcoder::basisu_transcoder_init: Initializing (this is not an error)\n");
#if BASISD_SUPPORT_UASTC
uastc_init();
#endif
+#if BASISD_SUPPORT_UASTC_HDR
+ // TODO: Examine this, optimize for startup time/mem utilization.
+ astc_helpers::init_tables(false);
+
+ astc_hdr_core_init();
+#endif
+
#if BASISD_SUPPORT_ASTC
transcoder_init_astc();
#endif
@@ -2027,6 +2043,10 @@ namespace basist
transcoder_init_pvrtc2();
#endif
+#if BASISD_SUPPORT_UASTC_HDR
+ bc6h_enc_init();
+#endif
+
g_transcoder_initialized = true;
}
@@ -6928,7 +6948,7 @@ namespace basist
static inline int sq(int x) { return x * x; }
- // PVRTC2 is a slightly borked format for alpha: In Non-Interpolated mode, the way AlphaB8 is exanded from 4 to 8 bits means it can never be 0.
+ // PVRTC2 is a slightly borked format for alpha: In Non-Interpolated mode, the way AlphaB8 is expanded from 4 to 8 bits means it can never be 0.
// This is actually very bad, because on 100% transparent blocks which have non-trivial color pixels, part of the color channel will leak into alpha!
// And there's nothing straightforward we can do because using the other modes is too expensive/complex. I can see why Apple didn't adopt it.
static void convert_etc1s_to_pvrtc2_rgba(void* pDst, const endpoint* pEndpoints, const selector* pSelector, const endpoint* pEndpoint_codebook, const selector* pSelector_codebook)
@@ -7515,6 +7535,8 @@ namespace basist
}
#endif // BASISD_SUPPORT_PVRTC2
+ //------------------------------------------------------------------------------------------------
+
basisu_lowlevel_etc1s_transcoder::basisu_lowlevel_etc1s_transcoder() :
m_pGlobal_codebook(nullptr),
m_selector_history_buf_size(0)
@@ -8620,7 +8642,7 @@ namespace basist
// Now make sure the output buffer is large enough, or we'll overwrite memory.
if (output_blocks_buf_size_in_blocks_or_pixels < (output_rows_in_pixels * output_row_pitch_in_blocks_or_pixels))
{
- BASISU_DEVEL_ERROR("basisu_lowlevel_etc1s_transcoder::transcode_image: output_blocks_buf_size_in_blocks_or_pixels < (output_rows_in_pixels * output_row_pitch_in_blocks_or_pixels)\n");
+ BASISU_DEVEL_ERROR("basis_validate_output_buffer_size: output_blocks_buf_size_in_blocks_or_pixels < (output_rows_in_pixels * output_row_pitch_in_blocks_or_pixels)\n");
return false;
}
}
@@ -8632,7 +8654,7 @@ namespace basist
if (output_blocks_buf_size_in_blocks_or_pixels < total_blocks_fxt1)
{
- BASISU_DEVEL_ERROR("basisu_lowlevel_etc1s_transcoder::transcode_image: output_blocks_buf_size_in_blocks_or_pixels < total_blocks_fxt1\n");
+ BASISU_DEVEL_ERROR("basis_validate_output_buffer_size: output_blocks_buf_size_in_blocks_or_pixels < total_blocks_fxt1\n");
return false;
}
}
@@ -8640,7 +8662,7 @@ namespace basist
{
if (output_blocks_buf_size_in_blocks_or_pixels < total_slice_blocks)
{
- BASISU_DEVEL_ERROR("basisu_lowlevel_etc1s_transcoder::transcode_image: output_blocks_buf_size_in_blocks_or_pixels < transcode_image\n");
+ BASISU_DEVEL_ERROR("basis_validate_output_buffer_size: output_blocks_buf_size_in_blocks_or_pixels < transcode_image\n");
return false;
}
}
@@ -9242,13 +9264,17 @@ namespace basist
return status;
}
+
+ //------------------------------------------------------------------------------------------------
basisu_lowlevel_uastc_transcoder::basisu_lowlevel_uastc_transcoder()
{
}
- bool basisu_lowlevel_uastc_transcoder::transcode_slice(void* pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t* pImage_data, uint32_t image_data_size, block_format fmt,
- uint32_t output_block_or_pixel_stride_in_bytes, bool bc1_allow_threecolor_blocks, bool has_alpha, const uint32_t orig_width, const uint32_t orig_height, uint32_t output_row_pitch_in_blocks_or_pixels,
+ bool basisu_lowlevel_uastc_transcoder::transcode_slice(
+ void* pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t* pImage_data, uint32_t image_data_size, block_format fmt,
+ uint32_t output_block_or_pixel_stride_in_bytes, bool bc1_allow_threecolor_blocks, bool has_alpha,
+ const uint32_t orig_width, const uint32_t orig_height, uint32_t output_row_pitch_in_blocks_or_pixels,
basisu_transcoder_state* pState, uint32_t output_rows_in_pixels, int channel0, int channel1, uint32_t decode_flags)
{
BASISU_NOTE_UNUSED(pState);
@@ -9784,6 +9810,317 @@ namespace basist
return status;
}
+
+ //------------------------------------------------------------------------------------------------
+
+ basisu_lowlevel_uastc_hdr_transcoder::basisu_lowlevel_uastc_hdr_transcoder()
+ {
+ }
+
+ bool basisu_lowlevel_uastc_hdr_transcoder::transcode_slice(
+ void* pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t* pImage_data, uint32_t image_data_size, block_format fmt,
+ uint32_t output_block_or_pixel_stride_in_bytes, bool bc1_allow_threecolor_blocks, bool has_alpha,
+ const uint32_t orig_width, const uint32_t orig_height, uint32_t output_row_pitch_in_blocks_or_pixels,
+ basisu_transcoder_state* pState, uint32_t output_rows_in_pixels, int channel0, int channel1, uint32_t decode_flags)
+ {
+ BASISU_NOTE_UNUSED(pState);
+ BASISU_NOTE_UNUSED(bc1_allow_threecolor_blocks);
+ BASISU_NOTE_UNUSED(has_alpha);
+ BASISU_NOTE_UNUSED(channel0);
+ BASISU_NOTE_UNUSED(channel1);
+ BASISU_NOTE_UNUSED(decode_flags);
+
+ assert(g_transcoder_initialized);
+ if (!g_transcoder_initialized)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_slice: Transcoder not globally initialized.\n");
+ return false;
+ }
+
+#if BASISD_SUPPORT_UASTC_HDR
+ const uint32_t total_blocks = num_blocks_x * num_blocks_y;
+
+ if (!output_row_pitch_in_blocks_or_pixels)
+ {
+ if (basis_block_format_is_uncompressed(fmt))
+ output_row_pitch_in_blocks_or_pixels = orig_width;
+ else
+ output_row_pitch_in_blocks_or_pixels = num_blocks_x;
+ }
+
+ if (basis_block_format_is_uncompressed(fmt))
+ {
+ if (!output_rows_in_pixels)
+ output_rows_in_pixels = orig_height;
+ }
+
+ uint32_t total_expected_block_bytes = sizeof(astc_blk) * total_blocks;
+ if (image_data_size < total_expected_block_bytes)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_slice: image_data_size < total_expected_block_bytes The file is corrupted or this is a bug.\n");
+ return false;
+ }
+
+ const astc_blk* pSource_block = reinterpret_cast<const astc_blk*>(pImage_data);
+
+ bool status = false;
+
+ // TODO: Optimize pure memcpy() case.
+
+ for (uint32_t block_y = 0; block_y < num_blocks_y; ++block_y)
+ {
+ void* pDst_block = (uint8_t*)pDst_blocks + block_y * output_row_pitch_in_blocks_or_pixels * output_block_or_pixel_stride_in_bytes;
+
+ for (uint32_t block_x = 0; block_x < num_blocks_x; ++block_x, ++pSource_block, pDst_block = (uint8_t*)pDst_block + output_block_or_pixel_stride_in_bytes)
+ {
+ switch (fmt)
+ {
+ case block_format::cUASTC_HDR_4x4:
+ case block_format::cASTC_HDR_4x4:
+ {
+ // Nothing to do, UASTC HDR is just ASTC.
+ memcpy(pDst_block, pSource_block, sizeof(uastc_block));
+ status = true;
+ break;
+ }
+ case block_format::cBC6H:
+ {
+ status = astc_hdr_transcode_to_bc6h(*pSource_block, *(bc6h_block *)pDst_block);
+ break;
+ }
+ case block_format::cRGB_9E5:
+ {
+ astc_helpers::log_astc_block log_blk;
+ status = astc_helpers::unpack_block(pSource_block, log_blk, 4, 4);
+ if (status)
+ {
+ uint32_t* pDst_pixels = reinterpret_cast<uint32_t*>(
+ static_cast<uint8_t*>(pDst_blocks) + (block_x * 4 + block_y * 4 * output_row_pitch_in_blocks_or_pixels) * sizeof(uint32_t)
+ );
+
+ uint32_t blk_texels[4][4];
+
+ status = astc_helpers::decode_block(log_blk, blk_texels, 4, 4, astc_helpers::cDecodeModeRGB9E5);
+
+ if (status)
+ {
+ const uint32_t max_x = basisu::minimum<int>(4, (int)output_row_pitch_in_blocks_or_pixels - (int)block_x * 4);
+ const uint32_t max_y = basisu::minimum<int>(4, (int)output_rows_in_pixels - (int)block_y * 4);
+
+ for (uint32_t y = 0; y < max_y; y++)
+ {
+ memcpy(pDst_pixels, &blk_texels[y][0], sizeof(uint32_t) * max_x);
+
+ pDst_pixels += output_row_pitch_in_blocks_or_pixels;
+ } // y
+ }
+ }
+
+ break;
+ }
+ case block_format::cRGBA_HALF:
+ {
+ astc_helpers::log_astc_block log_blk;
+ status = astc_helpers::unpack_block(pSource_block, log_blk, 4, 4);
+ if (status)
+ {
+ half_float* pDst_pixels = reinterpret_cast<half_float*>(
+ static_cast<uint8_t*>(pDst_blocks) + (block_x * 4 + block_y * 4 * output_row_pitch_in_blocks_or_pixels) * sizeof(half_float) * 4
+ );
+
+ half_float blk_texels[4][4][4];
+ status = astc_helpers::decode_block(log_blk, blk_texels, 4, 4, astc_helpers::cDecodeModeHDR16);
+
+ if (status)
+ {
+ const uint32_t max_x = basisu::minimum<int>(4, (int)output_row_pitch_in_blocks_or_pixels - (int)block_x * 4);
+ const uint32_t max_y = basisu::minimum<int>(4, (int)output_rows_in_pixels - (int)block_y * 4);
+
+ for (uint32_t y = 0; y < max_y; y++)
+ {
+ for (uint32_t x = 0; x < max_x; x++)
+ {
+ pDst_pixels[0 + 4 * x] = blk_texels[y][x][0];
+ pDst_pixels[1 + 4 * x] = blk_texels[y][x][1];
+ pDst_pixels[2 + 4 * x] = blk_texels[y][x][2];
+ pDst_pixels[3 + 4 * x] = blk_texels[y][x][3];
+ } // x
+
+ pDst_pixels += output_row_pitch_in_blocks_or_pixels * 4;
+ } // y
+ }
+ }
+
+ break;
+ }
+ case block_format::cRGB_HALF:
+ {
+ astc_helpers:: log_astc_block log_blk;
+ status = astc_helpers::unpack_block(pSource_block, log_blk, 4, 4);
+ if (status)
+ {
+ half_float* pDst_pixels =
+ reinterpret_cast<half_float*>(static_cast<uint8_t*>(pDst_blocks) + (block_x * 4 + block_y * 4 * output_row_pitch_in_blocks_or_pixels) * sizeof(half_float) * 3);
+
+ half_float blk_texels[4][4][4];
+ status = astc_helpers::decode_block(log_blk, blk_texels, 4, 4, astc_helpers::cDecodeModeHDR16);
+ if (status)
+ {
+ const uint32_t max_x = basisu::minimum<int>(4, (int)output_row_pitch_in_blocks_or_pixels - (int)block_x * 4);
+ const uint32_t max_y = basisu::minimum<int>(4, (int)output_rows_in_pixels - (int)block_y * 4);
+
+ for (uint32_t y = 0; y < max_y; y++)
+ {
+ for (uint32_t x = 0; x < max_x; x++)
+ {
+ pDst_pixels[0 + 3 * x] = blk_texels[y][x][0];
+ pDst_pixels[1 + 3 * x] = blk_texels[y][x][1];
+ pDst_pixels[2 + 3 * x] = blk_texels[y][x][2];
+ } // x
+
+ pDst_pixels += output_row_pitch_in_blocks_or_pixels * 3;
+ } // y
+ }
+ }
+
+ break;
+ }
+ default:
+ assert(0);
+ break;
+
+ }
+
+ if (!status)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_slice: Transcoder failed to unpack a UASTC HDR block - this is a bug, or the data was corrupted\n"); return false;
+ }
+
+ } // block_x
+
+ } // block_y
+
+ return true;
+#else
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_slice: UASTC_HDR is unsupported\n");
+
+ BASISU_NOTE_UNUSED(decode_flags);
+ BASISU_NOTE_UNUSED(channel0);
+ BASISU_NOTE_UNUSED(channel1);
+ BASISU_NOTE_UNUSED(output_rows_in_pixels);
+ BASISU_NOTE_UNUSED(output_row_pitch_in_blocks_or_pixels);
+ BASISU_NOTE_UNUSED(output_block_or_pixel_stride_in_bytes);
+ BASISU_NOTE_UNUSED(fmt);
+ BASISU_NOTE_UNUSED(image_data_size);
+ BASISU_NOTE_UNUSED(pImage_data);
+ BASISU_NOTE_UNUSED(num_blocks_x);
+ BASISU_NOTE_UNUSED(num_blocks_y);
+ BASISU_NOTE_UNUSED(pDst_blocks);
+
+ return false;
+#endif
+ }
+
+ bool basisu_lowlevel_uastc_hdr_transcoder::transcode_image(
+ transcoder_texture_format target_format,
+ void* pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
+ const uint8_t* pCompressed_data, uint32_t compressed_data_length,
+ uint32_t num_blocks_x, uint32_t num_blocks_y, uint32_t orig_width, uint32_t orig_height, uint32_t level_index,
+ uint32_t slice_offset, uint32_t slice_length,
+ uint32_t decode_flags,
+ bool has_alpha,
+ bool is_video,
+ uint32_t output_row_pitch_in_blocks_or_pixels,
+ basisu_transcoder_state* pState,
+ uint32_t output_rows_in_pixels,
+ int channel0, int channel1)
+ {
+ BASISU_NOTE_UNUSED(is_video);
+ BASISU_NOTE_UNUSED(level_index);
+ BASISU_NOTE_UNUSED(decode_flags);
+
+ if (((uint64_t)slice_offset + slice_length) > (uint64_t)compressed_data_length)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: source data buffer too small\n");
+ return false;
+ }
+
+ const uint32_t bytes_per_block_or_pixel = basis_get_bytes_per_block_or_pixel(target_format);
+ const uint32_t total_slice_blocks = num_blocks_x * num_blocks_y;
+
+ if (!basis_validate_output_buffer_size(target_format, output_blocks_buf_size_in_blocks_or_pixels, orig_width, orig_height, output_row_pitch_in_blocks_or_pixels, output_rows_in_pixels, total_slice_blocks))
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: output buffer size too small\n");
+ return false;
+ }
+
+ bool status = false;
+
+ switch (target_format)
+ {
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA:
+ {
+ status = transcode_slice(pOutput_blocks, num_blocks_x, num_blocks_y, pCompressed_data + slice_offset, slice_length, block_format::cASTC_HDR_4x4,
+ bytes_per_block_or_pixel, false, has_alpha, orig_width, orig_height, output_row_pitch_in_blocks_or_pixels, pState, output_rows_in_pixels, channel0, channel1);
+
+ if (!status)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: transcode_slice() to ASTC_HDR failed\n");
+ }
+ break;
+ }
+ case transcoder_texture_format::cTFBC6H:
+ {
+ status = transcode_slice(pOutput_blocks, num_blocks_x, num_blocks_y, pCompressed_data + slice_offset, slice_length, block_format::cBC6H,
+ bytes_per_block_or_pixel, false, has_alpha, orig_width, orig_height, output_row_pitch_in_blocks_or_pixels, pState, output_rows_in_pixels, channel0, channel1);
+ if (!status)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: transcode_slice() to BC6H failed\n");
+ }
+ break;
+ }
+ case transcoder_texture_format::cTFRGB_HALF:
+ {
+ status = transcode_slice(pOutput_blocks, num_blocks_x, num_blocks_y, pCompressed_data + slice_offset, slice_length, block_format::cRGB_HALF,
+ bytes_per_block_or_pixel, false, has_alpha, orig_width, orig_height, output_row_pitch_in_blocks_or_pixels, pState, output_rows_in_pixels);
+ if (!status)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: transcode_slice() to RGB_HALF failed\n");
+ }
+ break;
+ }
+ case transcoder_texture_format::cTFRGBA_HALF:
+ {
+ status = transcode_slice(pOutput_blocks, num_blocks_x, num_blocks_y, pCompressed_data + slice_offset, slice_length, block_format::cRGBA_HALF,
+ bytes_per_block_or_pixel, false, has_alpha, orig_width, orig_height, output_row_pitch_in_blocks_or_pixels, pState, output_rows_in_pixels);
+ if (!status)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: transcode_slice() to RGBA_HALF failed\n");
+ }
+ break;
+ }
+ case transcoder_texture_format::cTFRGB_9E5:
+ {
+ status = transcode_slice(pOutput_blocks, num_blocks_x, num_blocks_y, pCompressed_data + slice_offset, slice_length, block_format::cRGB_9E5,
+ bytes_per_block_or_pixel, false, has_alpha, orig_width, orig_height, output_row_pitch_in_blocks_or_pixels, pState, output_rows_in_pixels);
+ if (!status)
+ {
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: transcode_slice() to RGBA_HALF failed\n");
+ }
+ break;
+ }
+ default:
+ {
+ assert(0);
+ BASISU_DEVEL_ERROR("basisu_lowlevel_uastc_hdr_transcoder::transcode_image: Invalid format\n");
+ break;
+ }
+ }
+
+ return status;
+ }
+
+ //------------------------------------------------------------------------------------------------
basisu_transcoder::basisu_transcoder() :
m_ready_to_transcode(false)
@@ -10390,7 +10727,7 @@ namespace basist
}
else
{
- // Nothing special to do for UASTC.
+ // Nothing special to do for UASTC/UASTC HDR.
if (m_lowlevel_etc1s_decoder.m_local_endpoints.size())
{
m_lowlevel_etc1s_decoder.clear();
@@ -10510,7 +10847,14 @@ namespace basist
return false;
}
- if (pHeader->m_tex_format == (int)basis_tex_format::cUASTC4x4)
+ if (pHeader->m_tex_format == (int)basis_tex_format::cUASTC_HDR_4x4)
+ {
+ return m_lowlevel_uastc_hdr_decoder.transcode_slice(pOutput_blocks, slice_desc.m_num_blocks_x, slice_desc.m_num_blocks_y,
+ pDataU8 + slice_desc.m_file_ofs, slice_desc.m_file_size,
+ fmt, output_block_or_pixel_stride_in_bytes, (decode_flags & cDecodeFlagsBC1ForbidThreeColorBlocks) == 0, *pHeader, slice_desc, output_row_pitch_in_blocks_or_pixels, pState,
+ output_rows_in_pixels, channel0, channel1, decode_flags);
+ }
+ else if (pHeader->m_tex_format == (int)basis_tex_format::cUASTC4x4)
{
return m_lowlevel_uastc_decoder.transcode_slice(pOutput_blocks, slice_desc.m_num_blocks_x, slice_desc.m_num_blocks_y,
pDataU8 + slice_desc.m_file_ofs, slice_desc.m_file_size,
@@ -10742,7 +11086,18 @@ namespace basist
memset(static_cast<uint8_t*>(pOutput_blocks) + total_slice_blocks * bytes_per_block_or_pixel, 0, (output_blocks_buf_size_in_blocks_or_pixels - total_slice_blocks) * bytes_per_block_or_pixel);
}
- if (pHeader->m_tex_format == (int)basis_tex_format::cUASTC4x4)
+ if (pHeader->m_tex_format == (int)basis_tex_format::cUASTC_HDR_4x4)
+ {
+ const basis_slice_desc* pSlice_desc = &pSlice_descs[slice_index];
+
+ // Use the container independent image transcode method.
+ status = m_lowlevel_uastc_hdr_decoder.transcode_image(fmt,
+ pOutput_blocks, output_blocks_buf_size_in_blocks_or_pixels,
+ (const uint8_t*)pData, data_size, pSlice_desc->m_num_blocks_x, pSlice_desc->m_num_blocks_y, pSlice_desc->m_orig_width, pSlice_desc->m_orig_height, pSlice_desc->m_level_index,
+ pSlice_desc->m_file_ofs, pSlice_desc->m_file_size,
+ decode_flags, basis_file_has_alpha_slices, pHeader->m_tex_type == cBASISTexTypeVideoFrames, output_row_pitch_in_blocks_or_pixels, pState, output_rows_in_pixels);
+ }
+ else if (pHeader->m_tex_format == (int)basis_tex_format::cUASTC4x4)
{
const basis_slice_desc* pSlice_desc = &pSlice_descs[slice_index];
@@ -10808,20 +11163,27 @@ namespace basist
return 8;
case transcoder_texture_format::cTFBC7_RGBA:
case transcoder_texture_format::cTFBC7_ALT:
+ case transcoder_texture_format::cTFBC6H:
case transcoder_texture_format::cTFETC2_RGBA:
case transcoder_texture_format::cTFBC3_RGBA:
case transcoder_texture_format::cTFBC5_RG:
case transcoder_texture_format::cTFASTC_4x4_RGBA:
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA:
case transcoder_texture_format::cTFATC_RGBA:
case transcoder_texture_format::cTFFXT1_RGB:
case transcoder_texture_format::cTFETC2_EAC_RG11:
return 16;
case transcoder_texture_format::cTFRGBA32:
+ case transcoder_texture_format::cTFRGB_9E5:
return sizeof(uint32_t);
case transcoder_texture_format::cTFRGB565:
case transcoder_texture_format::cTFBGR565:
case transcoder_texture_format::cTFRGBA4444:
return sizeof(uint16_t);
+ case transcoder_texture_format::cTFRGB_HALF:
+ return sizeof(half_float) * 3;
+ case transcoder_texture_format::cTFRGBA_HALF:
+ return sizeof(half_float) * 4;
default:
assert(0);
BASISU_DEVEL_ERROR("basis_get_basisu_texture_format: Invalid fmt\n");
@@ -10845,17 +11207,22 @@ namespace basist
case transcoder_texture_format::cTFBC3_RGBA: return "BC3_RGBA";
case transcoder_texture_format::cTFBC5_RG: return "BC5_RG";
case transcoder_texture_format::cTFASTC_4x4_RGBA: return "ASTC_RGBA";
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA: return "ASTC_HDR_RGBA";
case transcoder_texture_format::cTFATC_RGB: return "ATC_RGB";
case transcoder_texture_format::cTFATC_RGBA: return "ATC_RGBA";
case transcoder_texture_format::cTFRGBA32: return "RGBA32";
case transcoder_texture_format::cTFRGB565: return "RGB565";
case transcoder_texture_format::cTFBGR565: return "BGR565";
case transcoder_texture_format::cTFRGBA4444: return "RGBA4444";
+ case transcoder_texture_format::cTFRGBA_HALF: return "RGBA_HALF";
+ case transcoder_texture_format::cTFRGB_9E5: return "RGB_9E5";
+ case transcoder_texture_format::cTFRGB_HALF: return "RGB_HALF";
case transcoder_texture_format::cTFFXT1_RGB: return "FXT1_RGB";
case transcoder_texture_format::cTFPVRTC2_4_RGB: return "PVRTC2_4_RGB";
case transcoder_texture_format::cTFPVRTC2_4_RGBA: return "PVRTC2_4_RGBA";
case transcoder_texture_format::cTFETC2_EAC_R11: return "ETC2_EAC_R11";
case transcoder_texture_format::cTFETC2_EAC_RG11: return "ETC2_EAC_RG11";
+ case transcoder_texture_format::cTFBC6H: return "BC6H";
default:
assert(0);
BASISU_DEVEL_ERROR("basis_get_basisu_texture_format: Invalid fmt\n");
@@ -10881,7 +11248,13 @@ namespace basist
case block_format::cRGB565: return "RGB565";
case block_format::cBGR565: return "BGR565";
case block_format::cRGBA4444: return "RGBA4444";
+ case block_format::cRGBA_HALF: return "RGBA_HALF";
+ case block_format::cRGB_HALF: return "RGB_HALF";
+ case block_format::cRGB_9E5: return "RGB_9E5";
case block_format::cUASTC_4x4: return "UASTC_4x4";
+ case block_format::cUASTC_HDR_4x4: return "UASTC_HDR_4x4";
+ case block_format::cBC6H: return "BC6H";
+ case block_format::cASTC_HDR_4x4: return "ASTC_HDR_4x4";
case block_format::cFXT1_RGB: return "FXT1_RGB";
case block_format::cPVRTC2_4_RGB: return "PVRTC2_4_RGB";
case block_format::cPVRTC2_4_RGBA: return "PVRTC2_4_RGBA";
@@ -10914,11 +11287,13 @@ namespace basist
bool basis_transcoder_format_has_alpha(transcoder_texture_format fmt)
{
+ // TODO: Technically ASTC_HDR does support alpha, but UASTC_HDR doesn't yet support it. Unsure what to do here.
switch (fmt)
{
case transcoder_texture_format::cTFETC2_RGBA:
case transcoder_texture_format::cTFBC3_RGBA:
case transcoder_texture_format::cTFASTC_4x4_RGBA:
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA:
case transcoder_texture_format::cTFBC7_RGBA:
case transcoder_texture_format::cTFBC7_ALT:
case transcoder_texture_format::cTFPVRTC1_4_RGBA:
@@ -10926,6 +11301,23 @@ namespace basist
case transcoder_texture_format::cTFATC_RGBA:
case transcoder_texture_format::cTFRGBA32:
case transcoder_texture_format::cTFRGBA4444:
+ case transcoder_texture_format::cTFRGBA_HALF:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ bool basis_transcoder_format_is_hdr(transcoder_texture_format fmt)
+ {
+ switch (fmt)
+ {
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA:
+ case transcoder_texture_format::cTFBC6H:
+ case transcoder_texture_format::cTFRGBA_HALF:
+ case transcoder_texture_format::cTFRGB_HALF:
+ case transcoder_texture_format::cTFRGB_9E5:
return true;
default:
break;
@@ -10947,13 +11339,18 @@ namespace basist
case transcoder_texture_format::cTFETC2_RGBA: return basisu::texture_format::cETC2_RGBA;
case transcoder_texture_format::cTFBC3_RGBA: return basisu::texture_format::cBC3;
case transcoder_texture_format::cTFBC5_RG: return basisu::texture_format::cBC5;
- case transcoder_texture_format::cTFASTC_4x4_RGBA: return basisu::texture_format::cASTC4x4;
+ case transcoder_texture_format::cTFASTC_4x4_RGBA: return basisu::texture_format::cASTC_LDR_4x4;
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA: return basisu::texture_format::cASTC_HDR_4x4;
+ case transcoder_texture_format::cTFBC6H: return basisu::texture_format::cBC6HUnsigned;
case transcoder_texture_format::cTFATC_RGB: return basisu::texture_format::cATC_RGB;
case transcoder_texture_format::cTFATC_RGBA: return basisu::texture_format::cATC_RGBA_INTERPOLATED_ALPHA;
case transcoder_texture_format::cTFRGBA32: return basisu::texture_format::cRGBA32;
case transcoder_texture_format::cTFRGB565: return basisu::texture_format::cRGB565;
case transcoder_texture_format::cTFBGR565: return basisu::texture_format::cBGR565;
case transcoder_texture_format::cTFRGBA4444: return basisu::texture_format::cRGBA4444;
+ case transcoder_texture_format::cTFRGBA_HALF: return basisu::texture_format::cRGBA_HALF;
+ case transcoder_texture_format::cTFRGB_9E5: return basisu::texture_format::cRGB_9E5;
+ case transcoder_texture_format::cTFRGB_HALF: return basisu::texture_format::cRGB_HALF;
case transcoder_texture_format::cTFFXT1_RGB: return basisu::texture_format::cFXT1_RGB;
case transcoder_texture_format::cTFPVRTC2_4_RGB: return basisu::texture_format::cPVRTC2_4_RGBA;
case transcoder_texture_format::cTFPVRTC2_4_RGBA: return basisu::texture_format::cPVRTC2_4_RGBA;
@@ -10975,6 +11372,9 @@ namespace basist
case transcoder_texture_format::cTFRGB565:
case transcoder_texture_format::cTFBGR565:
case transcoder_texture_format::cTFRGBA4444:
+ case transcoder_texture_format::cTFRGB_HALF:
+ case transcoder_texture_format::cTFRGBA_HALF:
+ case transcoder_texture_format::cTFRGB_9E5:
return true;
default:
break;
@@ -10995,6 +11395,9 @@ namespace basist
case block_format::cRGBA4444_COLOR:
case block_format::cRGBA4444_ALPHA:
case block_format::cRGBA4444_COLOR_OPAQUE:
+ case block_format::cRGBA_HALF:
+ case block_format::cRGB_HALF:
+ case block_format::cRGB_9E5:
return true;
default:
break;
@@ -11007,11 +11410,16 @@ namespace basist
switch (fmt)
{
case transcoder_texture_format::cTFRGBA32:
+ case transcoder_texture_format::cTFRGB_9E5:
return sizeof(uint32_t);
case transcoder_texture_format::cTFRGB565:
case transcoder_texture_format::cTFBGR565:
case transcoder_texture_format::cTFRGBA4444:
return sizeof(uint16_t);
+ case transcoder_texture_format::cTFRGB_HALF:
+ return sizeof(half_float) * 3;
+ case transcoder_texture_format::cTFRGBA_HALF:
+ return sizeof(half_float) * 4;
default:
break;
}
@@ -11038,8 +11446,26 @@ namespace basist
bool basis_is_format_supported(transcoder_texture_format tex_type, basis_tex_format fmt)
{
- if (fmt == basis_tex_format::cUASTC4x4)
+ if (fmt == basis_tex_format::cUASTC_HDR_4x4)
+ {
+ // UASTC HDR
+#if BASISD_SUPPORT_UASTC_HDR
+ switch (tex_type)
+ {
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA:
+ case transcoder_texture_format::cTFBC6H:
+ case transcoder_texture_format::cTFRGBA_HALF:
+ case transcoder_texture_format::cTFRGB_HALF:
+ case transcoder_texture_format::cTFRGB_9E5:
+ return true;
+ default:
+ break;
+ }
+#endif
+ }
+ else if (fmt == basis_tex_format::cUASTC4x4)
{
+ // UASTC LDR
#if BASISD_SUPPORT_UASTC
switch (tex_type)
{
@@ -11049,6 +11475,12 @@ namespace basist
case transcoder_texture_format::cTFATC_RGB:
case transcoder_texture_format::cTFATC_RGBA:
case transcoder_texture_format::cTFFXT1_RGB:
+ // UASTC LDR doesn't support transcoding to HDR formats
+ case transcoder_texture_format::cTFASTC_HDR_4x4_RGBA:
+ case transcoder_texture_format::cTFBC6H:
+ case transcoder_texture_format::cTFRGBA_HALF:
+ case transcoder_texture_format::cTFRGB_HALF:
+ case transcoder_texture_format::cTFRGB_9E5:
return false;
default:
return true;
@@ -11057,6 +11489,7 @@ namespace basist
}
else
{
+ // ETC1S
switch (tex_type)
{
// ETC1 and uncompressed are always supported.
@@ -11812,7 +12245,7 @@ namespace basist
// Encodes 3 values to output, usable for any range that uses quints and bits
static inline void astc_encode_quints(uint32_t* pOutput, const uint8_t* pValues, int& bit_pos, int n)
{
- // First extract the trits and the bits from the 5 input values
+ // First extract the quints and the bits from the 3 input values
int quints = 0, bits[3];
const uint32_t bit_mask = (1 << n) - 1;
for (int i = 0; i < 3; i++)
@@ -12131,11 +12564,13 @@ namespace basist
return bits & ((1U << codesize) - 1U);
}
-
- uint32_t byte_bit_offset = bit_offset & 7U;
- const uint16_t w = *(const uint16_t*)(&pBuf[bit_offset >> 3U]);
- bit_offset += codesize;
- return (w >> byte_bit_offset)& ((1U << codesize) - 1U);
+ else
+ {
+ uint32_t byte_bit_offset = bit_offset & 7U;
+ const uint16_t w = *(const uint16_t*)(&pBuf[bit_offset >> 3U]);
+ bit_offset += codesize;
+ return (w >> byte_bit_offset) & ((1U << codesize) - 1U);
+ }
}
bool unpack_uastc(const uastc_block& blk, unpacked_uastc_block& unpacked, bool blue_contract_check, bool read_hints)
@@ -12170,6 +12605,7 @@ namespace basist
return false;
unpacked.m_mode = mode;
+ unpacked.m_common_pattern = 0;
uint32_t bit_ofs = g_uastc_mode_huff_codes[mode][1];
@@ -16663,10 +17099,12 @@ namespace basist
memcpy(&m_header, pData, sizeof(m_header));
- // We only support UASTC and ETC1S
- if (m_header.m_vk_format != KTX2_VK_FORMAT_UNDEFINED)
+ // We only support UASTC LDR, UASTC HDR and ETC1S.
+ // Note the DFD's contents are what we are guided by for decoding the KTX2 file, not this format field (currently).
+ if ((m_header.m_vk_format != KTX2_VK_FORMAT_UNDEFINED) &&
+ (m_header.m_vk_format != basist::KTX2_FORMAT_UASTC_4x4_SFLOAT_BLOCK))
{
- BASISU_DEVEL_ERROR("ktx2_transcoder::init: KTX2 file must be in ETC1S or UASTC format\n");
+ BASISU_DEVEL_ERROR("ktx2_transcoder::init: KTX2 file must be in ETC1S or UASTC LDR/HDR format\n");
return false;
}
@@ -16890,6 +17328,16 @@ namespace basist
// We're assuming "DATA" means RGBA so it has alpha.
m_has_alpha = (m_dfd_chan0 == KTX2_DF_CHANNEL_UASTC_RGBA) || (m_dfd_chan0 == KTX2_DF_CHANNEL_UASTC_RRRG);
}
+ else if (m_dfd_color_model == KTX2_KDF_DF_MODEL_UASTC_HDR)
+ {
+ m_format = basist::basis_tex_format::cUASTC_HDR_4x4;
+
+ m_dfd_samples = 1;
+ m_dfd_chan0 = (ktx2_df_channel_id)((sample_channel0 >> 24) & 15);
+
+ // We're assuming "DATA" means RGBA so it has alpha.
+ m_has_alpha = (m_dfd_chan0 == KTX2_DF_CHANNEL_UASTC_RGBA) || (m_dfd_chan0 == KTX2_DF_CHANNEL_UASTC_RRRG);
+ }
else
{
// Unsupported DFD color model.
@@ -17167,7 +17615,8 @@ namespace basist
return false;
}
}
- else if (m_format == basist::basis_tex_format::cUASTC4x4)
+ else if ((m_format == basist::basis_tex_format::cUASTC4x4) ||
+ (m_format == basist::basis_tex_format::cUASTC_HDR_4x4))
{
// Compute length and offset to uncompressed 2D UASTC texture data, given the face/layer indices.
assert(uncomp_level_data_size == m_levels[level_index].m_uncompressed_byte_length);
@@ -17188,14 +17637,29 @@ namespace basist
return false;
}
- if (!m_uastc_transcoder.transcode_image(fmt,
- pOutput_blocks, output_blocks_buf_size_in_blocks_or_pixels,
- (const uint8_t*)pUncomp_level_data + uncomp_ofs, (uint32_t)total_2D_image_size, num_blocks_x, num_blocks_y, level_width, level_height, level_index,
- 0, (uint32_t)total_2D_image_size,
- decode_flags, m_has_alpha, m_is_video, output_row_pitch_in_blocks_or_pixels, nullptr, output_rows_in_pixels, channel0, channel1))
+ if (m_format == basist::basis_tex_format::cUASTC_HDR_4x4)
{
- BASISU_DEVEL_ERROR("ktx2_transcoder::transcode_image_2D: UASTC transcode_image() failed, this is either a bug or the file is corrupted/invalid\n");
- return false;
+ if (!m_uastc_hdr_transcoder.transcode_image(fmt,
+ pOutput_blocks, output_blocks_buf_size_in_blocks_or_pixels,
+ (const uint8_t*)pUncomp_level_data + uncomp_ofs, (uint32_t)total_2D_image_size, num_blocks_x, num_blocks_y, level_width, level_height, level_index,
+ 0, (uint32_t)total_2D_image_size,
+ decode_flags, m_has_alpha, m_is_video, output_row_pitch_in_blocks_or_pixels, nullptr, output_rows_in_pixels, channel0, channel1))
+ {
+ BASISU_DEVEL_ERROR("ktx2_transcoder::transcode_image_2D: UASTC HDR transcode_image() failed, this is either a bug or the file is corrupted/invalid\n");
+ return false;
+ }
+ }
+ else
+ {
+ if (!m_uastc_transcoder.transcode_image(fmt,
+ pOutput_blocks, output_blocks_buf_size_in_blocks_or_pixels,
+ (const uint8_t*)pUncomp_level_data + uncomp_ofs, (uint32_t)total_2D_image_size, num_blocks_x, num_blocks_y, level_width, level_height, level_index,
+ 0, (uint32_t)total_2D_image_size,
+ decode_flags, m_has_alpha, m_is_video, output_row_pitch_in_blocks_or_pixels, nullptr, output_rows_in_pixels, channel0, channel1))
+ {
+ BASISU_DEVEL_ERROR("ktx2_transcoder::transcode_image_2D: UASTC transcode_image() failed, this is either a bug or the file is corrupted/invalid\n");
+ return false;
+ }
}
}
else
@@ -17476,4 +17940,1531 @@ namespace basist
#endif
}
+ //-------------------------------
+
+#ifdef BASISD_SUPPORT_UASTC_HDR
+ // This float->half conversion matches how "F32TO16" works on Intel GPU's.
+ basist::half_float float_to_half(float val)
+ {
+ union { float f; int32_t i; uint32_t u; } fi = { val };
+ const int flt_m = fi.i & 0x7FFFFF, flt_e = (fi.i >> 23) & 0xFF, flt_s = (fi.i >> 31) & 0x1;
+ int s = flt_s, e = 0, m = 0;
+
+ // inf/NaN
+ if (flt_e == 0xff)
+ {
+ e = 31;
+ if (flt_m != 0) // NaN
+ m = 1;
+ }
+ // not zero or denormal
+ else if (flt_e != 0)
+ {
+ int new_exp = flt_e - 127;
+ if (new_exp > 15)
+ e = 31;
+ else if (new_exp < -14)
+ m = lrintf((1 << 24) * fabsf(fi.f));
+ else
+ {
+ e = new_exp + 15;
+ m = lrintf(flt_m * (1.0f / ((float)(1 << 13))));
+ }
+ }
+
+ assert((0 <= m) && (m <= 1024));
+ if (m == 1024)
+ {
+ e++;
+ m = 0;
+ }
+
+ assert((s >= 0) && (s <= 1));
+ assert((e >= 0) && (e <= 31));
+ assert((m >= 0) && (m <= 1023));
+
+ basist::half_float result = (basist::half_float)((s << 15) | (e << 10) | m);
+ return result;
+ }
+
+ //------------------------------------------------------------------------------------------------
+ // HDR support
+ //
+ // Originally from bc6h_enc.cpp
+ // BC6H decoder fuzzed vs. DirectXTex's for unsigned/signed
+
+ const uint8_t g_bc6h_mode_sig_bits[NUM_BC6H_MODES][4] = // base bits, r, g, b
+ {
+ // 2 subsets
+ { 10, 5, 5, 5, }, // 0, mode 1 in MS/D3D docs
+ { 7, 6, 6, 6, }, // 1
+ { 11, 5, 4, 4, }, // 2
+ { 11, 4, 5, 4, }, // 3
+ { 11, 4, 4, 5, }, // 4
+ { 9, 5, 5, 5, }, // 5
+ { 8, 6, 5, 5, }, // 6
+ { 8, 5, 6, 5, }, // 7
+ { 8, 5, 5, 6, }, // 8
+ { 6, 6, 6, 6, }, // 9, endpoints not delta encoded, mode 10 in MS/D3D docs
+ // 1 subset
+ { 10, 10, 10, 10, }, // 10, endpoints not delta encoded, mode 11 in MS/D3D docs
+ { 11, 9, 9, 9, }, // 11
+ { 12, 8, 8, 8, }, // 12
+ { 16, 4, 4, 4, } // 13, also useful for solid blocks
+ };
+
+ const int8_t g_bc6h_mode_lookup[32] = { 0, 1, 2, 10, 0, 1, 3, 11, 0, 1, 4, 12, 0, 1, 5, 13, 0, 1, 6, -1, 0, 1, 7, -1, 0, 1, 8, -1, 0, 1, 9, -1 };
+
+ const bc6h_bit_layout g_bc6h_bit_layouts[NUM_BC6H_MODES][MAX_BC6H_LAYOUT_INDEX] =
+ {
+ // comp_index, subset*2+lh_index, last_bit, first_bit
+ //------------------------ mode 0: 2 subsets, Weight bits: 46 bits, Endpoint bits: 75 bits (10.555, 10.555, 10.555), delta
+ { { 1, 2, 4, -1 }, { 2, 2, 4, -1 }, { 2, 3, 4, -1 }, { 0, 0, 9, 0 }, { 1, 0, 9, 0 }, { 2, 0, 9, 0 }, { 0, 1, 4, 0 },
+ { 1, 3, 4, -1 }, { 1, 2, 3, 0 }, { 1, 1, 4, 0 }, { 2, 3, 0, -1 }, { 1, 3, 3, 0 }, { 2, 1, 4, 0 }, { 2, 3, 1, -1 },
+ { 2, 2, 3, 0 }, { 0, 2, 4, 0 }, { 2, 3, 2, -1 }, { 0, 3, 4, 0 }, { 2, 3, 3, -1 }, { 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 1: 2 subsets, Weight bits: 46 bits, Endpoint bits: 75 bits (7.666, 7.666, 7.666), delta
+ { { 1, 2, 5, -1 },{ 1, 3, 4, -1 },{ 1, 3, 5, -1 },{ 0, 0, 6, 0 },{ 2, 3, 0, -1 },{ 2, 3, 1, -1 },{ 2, 2, 4, -1 },
+ { 1, 0, 6, 0 },{ 2, 2, 5, -1 },{ 2, 3, 2, -1 },{ 1, 2, 4, -1 },{ 2, 0, 6, 0 },{ 2, 3, 3, -1 },{ 2, 3, 5, -1 },
+ { 2, 3, 4, -1 },{ 0, 1, 5, 0 },{ 1, 2, 3, 0 },{ 1, 1, 5, 0 },{ 1, 3, 3, 0 },{ 2, 1, 5, 0 },{ 2, 2, 3, 0 },{ 0, 2, 5, 0 },
+ { 0, 3, 5, 0 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 2: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (11.555, 11.444, 11.444), delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 4, 0 },{ 0, 0, 10, -1 },{ 1, 2, 3, 0 },{ 1, 1, 3, 0 },{ 1, 0, 10, -1 },
+ { 2, 3, 0, -1 },{ 1, 3, 3, 0 },{ 2, 1, 3, 0 },{ 2, 0, 10, -1 },{ 2, 3, 1, -1 },{ 2, 2, 3, 0 },{ 0, 2, 4, 0 },{ 2, 3, 2, -1 },
+ { 0, 3, 4, 0 },{ 2, 3, 3, -1 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 3: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (11.444, 11.555, 11.444), delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 3, 0 },{ 0, 0, 10, -1 },{ 1, 3, 4, -1 },{ 1, 2, 3, 0 },{ 1, 1, 4, 0 },
+ { 1, 0, 10, -1 },{ 1, 3, 3, 0 },{ 2, 1, 3, 0 },{ 2, 0, 10, -1 },{ 2, 3, 1, -1 },{ 2, 2, 3, 0 },{ 0, 2, 3, 0 },{ 2, 3, 0, -1 },
+ { 2, 3, 2, -1 },{ 0, 3, 3, 0 },{ 1, 2, 4, -1 },{ 2, 3, 3, -1 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 4: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (11.444, 11.444, 11.555), delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 3, 0 },{ 0, 0, 10, -1 },{ 2, 2, 4, -1 },{ 1, 2, 3, 0 },{ 1, 1, 3, 0 },
+ { 1, 0, 10, -1 },{ 2, 3, 0, -1 },{ 1, 3, 3, 0 },{ 2, 1, 4, 0 },{ 2, 0, 10, -1 },{ 2, 2, 3, 0 },{ 0, 2, 3, 0 },{ 2, 3, 1, -1 },
+ { 2, 3, 2, -1 },{ 0, 3, 3, 0 },{ 2, 3, 4, -1 },{ 2, 3, 3, -1 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 5: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (9.555, 9.555, 9.555), delta
+ { { 0, 0, 8, 0 },{ 2, 2, 4, -1 },{ 1, 0, 8, 0 },{ 1, 2, 4, -1 },{ 2, 0, 8, 0 },{ 2, 3, 4, -1 },{ 0, 1, 4, 0 },{ 1, 3, 4, -1 },
+ { 1, 2, 3, 0 },{ 1, 1, 4, 0 },{ 2, 3, 0, -1 },{ 1, 3, 3, 0 },{ 2, 1, 4, 0 },{ 2, 3, 1, -1 },{ 2, 2, 3, 0 },{ 0, 2, 4, 0 },
+ { 2, 3, 2, -1 },{ 0, 3, 4, 0 },{ 2, 3, 3, -1 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 6: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (8.666, 8.555, 8.555), delta
+ { { 0, 0, 7, 0 },{ 1, 3, 4, -1 },{ 2, 2, 4, -1 },{ 1, 0, 7, 0 },{ 2, 3, 2, -1 },{ 1, 2, 4, -1 },{ 2, 0, 7, 0 },{ 2, 3, 3, -1 },
+ { 2, 3, 4, -1 },{ 0, 1, 5, 0 },{ 1, 2, 3, 0 },{ 1, 1, 4, 0 },{ 2, 3, 0, -1 },{ 1, 3, 3, 0 },{ 2, 1, 4, 0 },{ 2, 3, 1, -1 },
+ { 2, 2, 3, 0 },{ 0, 2, 5, 0 },{ 0, 3, 5, 0 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 7: 2 subsets, Weight bits: 46 bits, Endpoints bits: 72 bits (8.555, 8.666, 8.555), delta
+ { { 0, 0, 7, 0 },{ 2, 3, 0, -1 },{ 2, 2, 4, -1 },{ 1, 0, 7, 0 },{ 1, 2, 5, -1 },{ 1, 2, 4, -1 },{ 2, 0, 7, 0 },{ 1, 3, 5, -1 },
+ { 2, 3, 4, -1 },{ 0, 1, 4, 0 },{ 1, 3, 4, -1 },{ 1, 2, 3, 0 },{ 1, 1, 5, 0 },{ 1, 3, 3, 0 },{ 2, 1, 4, 0 },{ 2, 3, 1, -1 },
+ { 2, 2, 3, 0 },{ 0, 2, 4, 0 },{ 2, 3, 2, -1 },{ 0, 3, 4, 0 },{ 2, 3, 3, -1 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 8: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (8.555, 8.555, 8.666), delta
+ { { 0, 0, 7, 0 },{ 2, 3, 1, -1 },{ 2, 2, 4, -1 },{ 1, 0, 7, 0 },{ 2, 2, 5, -1 },{ 1, 2, 4, -1 },{ 2, 0, 7, 0 },{ 2, 3, 5, -1 },
+ { 2, 3, 4, -1 },{ 0, 1, 4, 0 },{ 1, 3, 4, -1 },{ 1, 2, 3, 0 },{ 1, 1, 4, 0 },{ 2, 3, 0, -1 },{ 1, 3, 3, 0 },{ 2, 1, 5, 0 },
+ { 2, 2, 3, 0 },{ 0, 2, 4, 0 },{ 2, 3, 2, -1 },{ 0, 3, 4, 0 },{ 2, 3, 3, -1 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 9: 2 subsets, Weight bits: 46 bits, Endpoint bits: 72 bits (6.6.6.6, 6.6.6.6, 6.6.6.6), NO delta
+ { { 0, 0, 5, 0 },{ 1, 3, 4, -1 },{ 2, 3, 0, -1 },{ 2, 3, 1, -1 },{ 2, 2, 4, -1 },{ 1, 0, 5, 0 },{ 1, 2, 5, -1 },{ 2, 2, 5, -1 },
+ { 2, 3, 2, -1 },{ 1, 2, 4, -1 },{ 2, 0, 5, 0 },{ 1, 3, 5, -1 },{ 2, 3, 3, -1 },{ 2, 3, 5, -1 },{ 2, 3, 4, -1 },{ 0, 1, 5, 0 },
+ { 1, 2, 3, 0 },{ 1, 1, 5, 0 },{ 1, 3, 3, 0 },{ 2, 1, 5, 0 },{ 2, 2, 3, 0 },{ 0, 2, 5, 0 },{ 0, 3, 5, 0 },{ 3, -1, 4, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 10: 1 subset, Weight bits: 63 bits, Endpoint bits: 60 bits (10.10, 10.10, 10.10), NO delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 9, 0 },{ 1, 1, 9, 0 },{ 2, 1, 9, 0 }, {-1, 0, 0, 0} },
+ //------------------------ mode 11: 1 subset, Weight bits: 63 bits, Endpoint bits: 60 bits (11.9, 11.9, 11.9), delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 8, 0 },{ 0, 0, 10, -1 },{ 1, 1, 8, 0 },{ 1, 0, 10, -1 },{ 2, 1, 8, 0 },{ 2, 0, 10, -1 }, {-1, 0, 0, 0} },
+ //------------------------ mode 12: 1 subset, Weight bits: 63 bits, Endpoint bits: 60 bits (12.8, 12.8, 12.8), delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 7, 0 },{ 0, 0, 10, 11 },{ 1, 1, 7, 0 },{ 1, 0, 10, 11 },{ 2, 1, 7, 0 },{ 2, 0, 10, 11 }, {-1, 0, 0, 0} },
+ //------------------------ mode 13: 1 subset, Weight bits: 63 bits, Endpoint bits: 60 bits (16.4, 16.4, 16.4), delta
+ { { 0, 0, 9, 0 },{ 1, 0, 9, 0 },{ 2, 0, 9, 0 },{ 0, 1, 3, 0 },{ 0, 0, 10, 15 },{ 1, 1, 3, 0 },{ 1, 0, 10, 15 },{ 2, 1, 3, 0 },{ 2, 0, 10, 15 }, {-1, 0, 0, 0} }
+ };
+
+ // The same as the first 32 2-subset patterns in BC7.
+ // Bit 7 is a flag indicating that the weight uses 1 less bit than usual.
+ const uint8_t g_bc6h_2subset_patterns[TOTAL_BC6H_PARTITION_PATTERNS][4][4] = // [pat][y][x]
+ {
+ { {0x80, 0, 1, 1}, { 0, 0, 1, 1 }, { 0, 0, 1, 1 }, { 0, 0, 1, 0x81 }}, { {0x80, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 0x81} },
+ { {0x80, 1, 1, 1}, {0, 1, 1, 1}, {0, 1, 1, 1}, {0, 1, 1, 0x81} }, { {0x80, 0, 0, 1}, {0, 0, 1, 1}, {0, 0, 1, 1}, {0, 1, 1, 0x81} },
+ { {0x80, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 1, 0x81} }, { {0x80, 0, 1, 1}, {0, 1, 1, 1}, {0, 1, 1, 1}, {1, 1, 1, 0x81} },
+ { {0x80, 0, 0, 1}, {0, 0, 1, 1}, {0, 1, 1, 1}, {1, 1, 1, 0x81} }, { {0x80, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 1}, {0, 1, 1, 0x81} },
+ { {0x80, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0x81} }, { {0x80, 0, 1, 1}, {0, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 0x81} },
+ { {0x80, 0, 0, 0}, {0, 0, 0, 1}, {0, 1, 1, 1}, {1, 1, 1, 0x81} }, { {0x80, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 1}, {0, 1, 1, 0x81} },
+ { {0x80, 0, 0, 1}, {0, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 0x81} }, { {0x80, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {1, 1, 1, 0x81} },
+ { {0x80, 0, 0, 0}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 0x81} }, { {0x80, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 0x81} },
+ { {0x80, 0, 0, 0}, {1, 0, 0, 0}, {1, 1, 1, 0}, {1, 1, 1, 0x81} }, { {0x80, 1, 0x81, 1}, {0, 0, 0, 1}, {0, 0, 0, 0}, {0, 0, 0, 0} },
+ { {0x80, 0, 0, 0}, {0, 0, 0, 0}, {0x81, 0, 0, 0}, {1, 1, 1, 0} }, { {0x80, 1, 0x81, 1}, {0, 0, 1, 1}, {0, 0, 0, 1}, {0, 0, 0, 0} },
+ { {0x80, 0, 0x81, 1}, {0, 0, 0, 1}, {0, 0, 0, 0}, {0, 0, 0, 0} }, { {0x80, 0, 0, 0}, {1, 0, 0, 0}, {0x81, 1, 0, 0}, {1, 1, 1, 0} },
+ { {0x80, 0, 0, 0}, {0, 0, 0, 0}, {0x81, 0, 0, 0}, {1, 1, 0, 0} }, { {0x80, 1, 1, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 0, 0, 0x81} },
+ { {0x80, 0, 0x81, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 0} }, { {0x80, 0, 0, 0}, {1, 0, 0, 0}, {0x81, 0, 0, 0}, {1, 1, 0, 0} },
+ { {0x80, 1, 0x81, 0}, {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 1, 1, 0} }, { {0x80, 0, 0x81, 1}, {0, 1, 1, 0}, {0, 1, 1, 0}, {1, 1, 0, 0} },
+ { {0x80, 0, 0, 1}, {0, 1, 1, 1}, {0x81, 1, 1, 0}, {1, 0, 0, 0} }, { {0x80, 0, 0, 0}, {1, 1, 1, 1}, {0x81, 1, 1, 1}, {0, 0, 0, 0} },
+ { {0x80, 1, 0x81, 1}, {0, 0, 0, 1}, {1, 0, 0, 0}, {1, 1, 1, 0} }, { {0x80, 0, 0x81, 1}, {1, 0, 0, 1}, {1, 0, 0, 1}, {1, 1, 0, 0} }
+ };
+
+ const uint8_t g_bc6h_weight3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 };
+ const uint8_t g_bc6h_weight4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 };
+
+ struct bc6h_logical_block
+ {
+ uint32_t m_mode;
+ uint32_t m_partition_pattern; // must be 0 if 1 subset
+ uint32_t m_endpoints[3][4]; // [comp][subset*2+lh_index] - must be already properly packed
+ uint8_t m_weights[16]; // weights must be of the proper size, taking into account skipped MSB's which must be 0
+
+ void clear()
+ {
+ basisu::clear_obj(*this);
+ }
+ };
+
+ static inline void write_bits(uint64_t val, uint32_t num_bits, uint32_t& bit_pos, uint64_t& l, uint64_t& h)
+ {
+ assert((num_bits) && (num_bits < 64) && (bit_pos < 128));
+ assert(val < (1ULL << num_bits));
+
+ if (bit_pos < 64)
+ {
+ l |= (val << bit_pos);
+
+ if ((bit_pos + num_bits) > 64)
+ h |= (val >> (64 - bit_pos));
+ }
+ else
+ {
+ h |= (val << (bit_pos - 64));
+ }
+
+ bit_pos += num_bits;
+ assert(bit_pos <= 128);
+ }
+
+ static inline void write_rev_bits(uint64_t val, uint32_t num_bits, uint32_t& bit_pos, uint64_t& l, uint64_t& h)
+ {
+ assert((num_bits) && (num_bits < 64) && (bit_pos < 128));
+ assert(val < (1ULL << num_bits));
+
+ for (uint32_t i = 0; i < num_bits; i++)
+ write_bits((val >> (num_bits - 1u - i)) & 1, 1, bit_pos, l, h);
+ }
+
+ static void pack_bc6h_block(bc6h_block& dst_blk, bc6h_logical_block& log_blk)
+ {
+ const uint8_t s_mode_bits[NUM_BC6H_MODES] = { 0b00, 0b01, 0b00010, 0b00110, 0b01010, 0b01110, 0b10010, 0b10110, 0b11010, 0b11110, 0b00011, 0b00111, 0b01011, 0b01111 };
+
+ const uint32_t mode = log_blk.m_mode;
+ assert(mode < NUM_BC6H_MODES);
+
+ uint64_t l = s_mode_bits[mode], h = 0;
+ uint32_t bit_pos = (mode >= 2) ? 5 : 2;
+
+ const uint32_t num_subsets = (mode >= BC6H_FIRST_1SUBSET_MODE_INDEX) ? 1 : 2;
+
+ assert(((num_subsets == 2) && (log_blk.m_partition_pattern < TOTAL_BC6H_PARTITION_PATTERNS)) ||
+ ((num_subsets == 1) && (!log_blk.m_partition_pattern)));
+
+ // Sanity checks
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ assert(log_blk.m_endpoints[c][0] < (1u << g_bc6h_mode_sig_bits[mode][0])); // 1st subset l, base bits
+ assert(log_blk.m_endpoints[c][1] < (1u << g_bc6h_mode_sig_bits[mode][c + 1])); // 1st subset h, these are deltas except for modes 9,10
+ assert(log_blk.m_endpoints[c][2] < (1u << g_bc6h_mode_sig_bits[mode][c + 1])); // 2nd subset l
+ assert(log_blk.m_endpoints[c][3] < (1u << g_bc6h_mode_sig_bits[mode][c + 1])); // 2nd subset h
+ }
+
+ const bc6h_bit_layout* pLayout = &g_bc6h_bit_layouts[mode][0];
+
+ while (pLayout->m_comp != -1)
+ {
+ uint32_t v = (pLayout->m_comp == 3) ? log_blk.m_partition_pattern : log_blk.m_endpoints[pLayout->m_comp][pLayout->m_index];
+
+ if (pLayout->m_first_bit == -1)
+ {
+ write_bits((v >> pLayout->m_last_bit) & 1, 1, bit_pos, l, h);
+ }
+ else
+ {
+ const uint32_t total_bits = basisu::iabs(pLayout->m_last_bit - pLayout->m_first_bit) + 1;
+
+ v >>= basisu::minimum(pLayout->m_first_bit, pLayout->m_last_bit);
+ v &= ((1 << total_bits) - 1);
+
+ if (pLayout->m_first_bit > pLayout->m_last_bit)
+ write_rev_bits(v, total_bits, bit_pos, l, h);
+ else
+ write_bits(v, total_bits, bit_pos, l, h);
+ }
+
+ pLayout++;
+ }
+
+ const uint32_t num_mode_sel_bits = (num_subsets == 1) ? 4 : 3;
+ const uint8_t* pPat = &g_bc6h_2subset_patterns[log_blk.m_partition_pattern][0][0];
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ const uint32_t sel = log_blk.m_weights[i];
+
+ uint32_t num_bits = num_mode_sel_bits;
+ if (num_subsets == 2)
+ {
+ const uint32_t subset_index = pPat[i];
+ num_bits -= (subset_index >> 7);
+ }
+ else if (!i)
+ {
+ num_bits--;
+ }
+
+ assert(sel < (1u << num_bits));
+
+ write_bits(sel, num_bits, bit_pos, l, h);
+ }
+
+ assert(bit_pos == 128);
+
+ basisu::write_le_dword(&dst_blk.m_bytes[0], (uint32_t)l);
+ basisu::write_le_dword(&dst_blk.m_bytes[4], (uint32_t)(l >> 32u));
+ basisu::write_le_dword(&dst_blk.m_bytes[8], (uint32_t)h);
+ basisu::write_le_dword(&dst_blk.m_bytes[12], (uint32_t)(h >> 32u));
+ }
+
+#if 0
+ static inline uint32_t bc6h_blog_dequantize_to_blog16(uint32_t comp, uint32_t bits_per_comp)
+ {
+ int unq;
+
+ if (bits_per_comp >= 15)
+ unq = comp;
+ else if (comp == 0)
+ unq = 0;
+ else if (comp == ((1u << bits_per_comp) - 1u))
+ unq = 0xFFFFu;
+ else
+ unq = ((comp << 16u) + 0x8000u) >> bits_per_comp;
+
+ return unq;
+ }
+#endif
+
+ // Suboptimal, but very close.
+ static inline uint32_t bc6h_half_to_blog(half_float h, uint32_t num_bits)
+ {
+ assert(h <= MAX_BC6H_HALF_FLOAT_AS_UINT);
+ return (h * 64 + 30) / (31 * (1 << (16 - num_bits)));
+ }
+
+ // 6,7,8,9,10,11,12
+ const uint32_t BC6H_BLOG_TAB_MIN = 6;
+ const uint32_t BC6H_BLOG_TAB_MAX = 12;
+ //const uint32_t BC6H_BLOG_TAB_NUM = BC6H_BLOG_TAB_MAX - BC6H_BLOG_TAB_MIN + 1;
+
+ // Handles 16, or 6-12 bits. Others assert.
+ static inline uint32_t half_to_blog_tab(half_float h, uint32_t num_bits)
+ {
+ BASISU_NOTE_UNUSED(BC6H_BLOG_TAB_MIN);
+ BASISU_NOTE_UNUSED(BC6H_BLOG_TAB_MAX);
+
+ assert(h <= MAX_BC6H_HALF_FLOAT_AS_UINT);
+
+ if (num_bits == 16)
+ {
+ return bc6h_half_to_blog(h, 16);
+ }
+ else
+ {
+ assert((num_bits >= BC6H_BLOG_TAB_MIN) && (num_bits <= BC6H_BLOG_TAB_MAX));
+
+ // Note: This used to be done using a table lookup, but it required ~224KB of tables. This isn't quite as accurate, but the error is very slight (+-1 half values as ints).
+ return bc6h_half_to_blog(h, num_bits);
+ }
+ }
+
+ bool g_bc6h_enc_initialized;
+
+ void bc6h_enc_init()
+ {
+ if (g_bc6h_enc_initialized)
+ return;
+
+ g_bc6h_enc_initialized = true;
+ }
+
+ // mode 10, 4-bit weights
+ void bc6h_enc_block_mode10(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights)
+ {
+ assert(g_bc6h_enc_initialized);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ assert(pWeights[i] <= 15);
+ }
+
+ bc6h_logical_block log_blk;
+ log_blk.clear();
+
+ // Convert half endpoints to blog10 (mode 10 doesn't use delta encoding)
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ log_blk.m_endpoints[c][0] = half_to_blog_tab(pEndpoints[c][0], 10);
+ log_blk.m_endpoints[c][1] = half_to_blog_tab(pEndpoints[c][1], 10);
+ }
+
+ memcpy(log_blk.m_weights, pWeights, 16);
+
+ if (log_blk.m_weights[0] & 8)
+ {
+ for (uint32_t i = 0; i < 16; i++)
+ log_blk.m_weights[i] = 15 - log_blk.m_weights[i];
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ std::swap(log_blk.m_endpoints[c][0], log_blk.m_endpoints[c][1]);
+ }
+ }
+
+ log_blk.m_mode = BC6H_FIRST_1SUBSET_MODE_INDEX;
+ pack_bc6h_block(*pPacked_block, log_blk);
+ }
+
+ // Tries modes 11-13 (delta endpoint) encoding, falling back to mode 10 only when necessary, 4-bit weights
+ void bc6h_enc_block_1subset_4bit_weights(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights)
+ {
+ assert(g_bc6h_enc_initialized);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ assert(pWeights[i] <= 15);
+ }
+
+ bc6h_logical_block log_blk;
+ log_blk.clear();
+
+ for (uint32_t mode = BC6H_LAST_MODE_INDEX; mode > BC6H_FIRST_1SUBSET_MODE_INDEX; mode--)
+ {
+ const uint32_t num_base_bits = g_bc6h_mode_sig_bits[mode][0], num_delta_bits = g_bc6h_mode_sig_bits[mode][1];
+ const int base_bitmask = (1 << num_base_bits) - 1;
+ const int delta_bitmask = (1 << num_delta_bits) - 1;
+ BASISU_NOTE_UNUSED(base_bitmask);
+
+ assert(num_delta_bits < num_base_bits);
+ assert((num_delta_bits == g_bc6h_mode_sig_bits[mode][2]) && (num_delta_bits == g_bc6h_mode_sig_bits[mode][3]));
+
+ uint32_t blog_endpoints[3][2];
+
+ // Convert half endpoints to blog 16, 12, or 11
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ blog_endpoints[c][0] = half_to_blog_tab(pEndpoints[c][0], num_base_bits);
+ assert((int)blog_endpoints[c][0] <= base_bitmask);
+
+ blog_endpoints[c][1] = half_to_blog_tab(pEndpoints[c][1], num_base_bits);
+ assert((int)blog_endpoints[c][1] <= base_bitmask);
+ }
+
+ // Copy weights
+ memcpy(log_blk.m_weights, pWeights, 16);
+
+ // Ensure first weight MSB is 0
+ if (log_blk.m_weights[0] & 8)
+ {
+ // Invert weights
+ for (uint32_t i = 0; i < 16; i++)
+ log_blk.m_weights[i] = 15 - log_blk.m_weights[i];
+
+ // Swap blog quantized endpoints
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ std::swap(blog_endpoints[c][0], blog_endpoints[c][1]);
+ }
+ }
+
+ const int max_delta = (1 << (num_delta_bits - 1)) - 1;
+ const int min_delta = -(max_delta + 1);
+ assert((max_delta - min_delta) == delta_bitmask);
+
+ bool failed_flag = false;
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ log_blk.m_endpoints[c][0] = blog_endpoints[c][0];
+
+ int delta = (int)blog_endpoints[c][1] - (int)blog_endpoints[c][0];
+ if ((delta < min_delta) || (delta > max_delta))
+ {
+ failed_flag = true;
+ break;
+ }
+
+ log_blk.m_endpoints[c][1] = delta & delta_bitmask;
+ }
+
+ if (failed_flag)
+ continue;
+
+ log_blk.m_mode = mode;
+ pack_bc6h_block(*pPacked_block, log_blk);
+
+ return;
+ }
+
+ // Worst case fall back to mode 10, which can handle any endpoints
+ bc6h_enc_block_mode10(pPacked_block, pEndpoints, pWeights);
+ }
+
+ // Mode 9 (direct endpoint encoding), 3-bit weights, but only 1 subset
+ void bc6h_enc_block_1subset_mode9_3bit_weights(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights)
+ {
+ assert(g_bc6h_enc_initialized);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ assert(pWeights[i] <= 7);
+ }
+
+ bc6h_logical_block log_blk;
+ log_blk.clear();
+
+ // Convert half endpoints to blog6 (mode 9 doesn't use delta encoding)
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ log_blk.m_endpoints[c][0] = half_to_blog_tab(pEndpoints[c][0], 6);
+ log_blk.m_endpoints[c][2] = log_blk.m_endpoints[c][0];
+
+ log_blk.m_endpoints[c][1] = half_to_blog_tab(pEndpoints[c][1], 6);
+ log_blk.m_endpoints[c][3] = log_blk.m_endpoints[c][1];
+ }
+
+ memcpy(log_blk.m_weights, pWeights, 16);
+
+ const uint32_t pat_index = 0;
+ const uint8_t* pPat = &g_bc6h_2subset_patterns[pat_index][0][0];
+
+ if (log_blk.m_weights[0] & 4)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(log_blk.m_endpoints[c][0], log_blk.m_endpoints[c][1]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 0)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ if (log_blk.m_weights[15] & 4)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(log_blk.m_endpoints[c][2], log_blk.m_endpoints[c][3]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 1)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ log_blk.m_mode = 9;
+ log_blk.m_partition_pattern = pat_index;
+ pack_bc6h_block(*pPacked_block, log_blk);
+ }
+
+ // Tries modes 0-8, falls back to mode 9
+ void bc6h_enc_block_1subset_3bit_weights(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights)
+ {
+ assert(g_bc6h_enc_initialized);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ assert(pWeights[i] <= 7);
+ }
+
+ bc6h_logical_block log_blk;
+ log_blk.clear();
+
+ for (uint32_t mode_iter = 0; mode_iter <= 8; mode_iter++)
+ {
+ static const int s_mode_order[9] = { 2, 3, 4, 0, 5, 6, 7, 8, 1 }; // ordered from largest base bits to least
+ const uint32_t mode = s_mode_order[mode_iter];
+
+ const uint32_t num_base_bits = g_bc6h_mode_sig_bits[mode][0];
+ const int base_bitmask = (1 << num_base_bits) - 1;
+ BASISU_NOTE_UNUSED(base_bitmask);
+
+ const uint32_t num_delta_bits[3] = { g_bc6h_mode_sig_bits[mode][1], g_bc6h_mode_sig_bits[mode][2], g_bc6h_mode_sig_bits[mode][3] };
+ const int delta_bitmasks[3] = { (1 << num_delta_bits[0]) - 1, (1 << num_delta_bits[1]) - 1, (1 << num_delta_bits[2]) - 1 };
+
+ uint32_t blog_endpoints[3][4];
+
+ // Convert half endpoints to blog 7-11
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ blog_endpoints[c][0] = half_to_blog_tab(pEndpoints[c][0], num_base_bits);
+ blog_endpoints[c][2] = blog_endpoints[c][0];
+ assert((int)blog_endpoints[c][0] <= base_bitmask);
+
+ blog_endpoints[c][1] = half_to_blog_tab(pEndpoints[c][1], num_base_bits);
+ blog_endpoints[c][3] = blog_endpoints[c][1];
+ assert((int)blog_endpoints[c][1] <= base_bitmask);
+ }
+
+ const uint32_t pat_index = 0;
+ const uint8_t* pPat = &g_bc6h_2subset_patterns[pat_index][0][0];
+
+ memcpy(log_blk.m_weights, pWeights, 16);
+
+ if (log_blk.m_weights[0] & 4)
+ {
+ // Swap part 0's endpoints/weights
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(blog_endpoints[c][0], blog_endpoints[c][1]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 0)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ if (log_blk.m_weights[15] & 4)
+ {
+ // Swap part 1's endpoints/weights
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(blog_endpoints[c][2], blog_endpoints[c][3]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 1)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ bool failed_flag = false;
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ const int max_delta = (1 << (num_delta_bits[c] - 1)) - 1;
+
+ const int min_delta = -(max_delta + 1);
+ assert((max_delta - min_delta) == delta_bitmasks[c]);
+
+ log_blk.m_endpoints[c][0] = blog_endpoints[c][0];
+
+ int delta0 = (int)blog_endpoints[c][1] - (int)blog_endpoints[c][0];
+ int delta1 = (int)blog_endpoints[c][2] - (int)blog_endpoints[c][0];
+ int delta2 = (int)blog_endpoints[c][3] - (int)blog_endpoints[c][0];
+
+ if ((delta0 < min_delta) || (delta0 > max_delta) ||
+ (delta1 < min_delta) || (delta1 > max_delta) ||
+ (delta2 < min_delta) || (delta2 > max_delta))
+ {
+ failed_flag = true;
+ break;
+ }
+
+ log_blk.m_endpoints[c][1] = delta0 & delta_bitmasks[c];
+ log_blk.m_endpoints[c][2] = delta1 & delta_bitmasks[c];
+ log_blk.m_endpoints[c][3] = delta2 & delta_bitmasks[c];
+
+ if (failed_flag)
+ break;
+ }
+ if (failed_flag)
+ continue;
+
+ log_blk.m_mode = mode;
+ log_blk.m_partition_pattern = pat_index;
+ pack_bc6h_block(*pPacked_block, log_blk);
+
+ return;
+
+ } // mode_iter
+
+ bc6h_enc_block_1subset_mode9_3bit_weights(pPacked_block, pEndpoints, pWeights);
+ }
+
+ // pEndpoints[subset][comp][lh_index]
+ void bc6h_enc_block_2subset_mode9_3bit_weights(bc6h_block* pPacked_block, uint32_t common_part_index, const half_float pEndpoints[2][3][2], const uint8_t* pWeights)
+ {
+ assert(g_bc6h_enc_initialized);
+ assert(common_part_index < basist::TOTAL_ASTC_BC7_COMMON_PARTITIONS2);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ assert(pWeights[i] <= 7);
+ }
+
+ bc6h_logical_block log_blk;
+ log_blk.clear();
+
+ // Convert half endpoints to blog6 (mode 9 doesn't use delta encoding)
+ for (uint32_t s = 0; s < 2; s++)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ log_blk.m_endpoints[c][0 + s * 2] = half_to_blog_tab(pEndpoints[s][c][0], 6);
+ log_blk.m_endpoints[c][1 + s * 2] = half_to_blog_tab(pEndpoints[s][c][1], 6);
+ }
+ }
+
+ memcpy(log_blk.m_weights, pWeights, 16);
+
+ //const uint32_t astc_pattern = basist::g_astc_bc7_common_partitions2[common_part_index].m_astc;
+ const uint32_t bc7_pattern = basist::g_astc_bc7_common_partitions2[common_part_index].m_bc7;
+
+ const bool invert_flag = basist::g_astc_bc7_common_partitions2[common_part_index].m_invert;
+ if (invert_flag)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ std::swap(log_blk.m_endpoints[c][0], log_blk.m_endpoints[c][2]);
+ std::swap(log_blk.m_endpoints[c][1], log_blk.m_endpoints[c][3]);
+ }
+ }
+
+ const uint32_t pat_index = bc7_pattern;
+ assert(pat_index < 32);
+ const uint8_t* pPat = &g_bc6h_2subset_patterns[pat_index][0][0];
+
+ bool swap_flags[2] = { false, false };
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ if ((pPat[i] & 0x80) == 0)
+ continue;
+
+ if (log_blk.m_weights[i] & 4)
+ {
+ const uint32_t p = pPat[i] & 1;
+ swap_flags[p] = true;
+ }
+ }
+
+ if (swap_flags[0])
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(log_blk.m_endpoints[c][0], log_blk.m_endpoints[c][1]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 0)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ if (swap_flags[1])
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(log_blk.m_endpoints[c][2], log_blk.m_endpoints[c][3]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 1)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ log_blk.m_mode = 9;
+ log_blk.m_partition_pattern = pat_index;
+ pack_bc6h_block(*pPacked_block, log_blk);
+ }
+
+ void bc6h_enc_block_2subset_3bit_weights(bc6h_block* pPacked_block, uint32_t common_part_index, const half_float pEndpoints[2][3][2], const uint8_t* pWeights)
+ {
+ assert(g_bc6h_enc_initialized);
+
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ assert(pWeights[i] <= 7);
+ }
+
+ bc6h_logical_block log_blk;
+ log_blk.clear();
+
+ for (uint32_t mode_iter = 0; mode_iter <= 8; mode_iter++)
+ {
+ static const int s_mode_order[9] = { 2, 3, 4, 0, 5, 6, 7, 8, 1 }; // ordered from largest base bits to least
+ const uint32_t mode = s_mode_order[mode_iter];
+
+ const uint32_t num_base_bits = g_bc6h_mode_sig_bits[mode][0];
+ const int base_bitmask = (1 << num_base_bits) - 1;
+ BASISU_NOTE_UNUSED(base_bitmask);
+
+ const uint32_t num_delta_bits[3] = { g_bc6h_mode_sig_bits[mode][1], g_bc6h_mode_sig_bits[mode][2], g_bc6h_mode_sig_bits[mode][3] };
+ const int delta_bitmasks[3] = { (1 << num_delta_bits[0]) - 1, (1 << num_delta_bits[1]) - 1, (1 << num_delta_bits[2]) - 1 };
+
+ uint32_t blog_endpoints[3][4];
+
+ // Convert half endpoints to blog 7-11
+ for (uint32_t s = 0; s < 2; s++)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ blog_endpoints[c][0 + s * 2] = half_to_blog_tab(pEndpoints[s][c][0], num_base_bits);
+ blog_endpoints[c][1 + s * 2] = half_to_blog_tab(pEndpoints[s][c][1], num_base_bits);
+ }
+ }
+
+ memcpy(log_blk.m_weights, pWeights, 16);
+
+ //const uint32_t astc_pattern = basist::g_astc_bc7_common_partitions2[common_part_index].m_astc;
+ const uint32_t bc7_pattern = basist::g_astc_bc7_common_partitions2[common_part_index].m_bc7;
+
+ const bool invert_flag = basist::g_astc_bc7_common_partitions2[common_part_index].m_invert;
+ if (invert_flag)
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ std::swap(blog_endpoints[c][0], blog_endpoints[c][2]);
+ std::swap(blog_endpoints[c][1], blog_endpoints[c][3]);
+ }
+ }
+
+ const uint32_t pat_index = bc7_pattern;
+ assert(pat_index < 32);
+ const uint8_t* pPat = &g_bc6h_2subset_patterns[pat_index][0][0];
+
+ bool swap_flags[2] = { false, false };
+ for (uint32_t i = 0; i < 16; i++)
+ {
+ if ((pPat[i] & 0x80) == 0)
+ continue;
+
+ if (log_blk.m_weights[i] & 4)
+ {
+ const uint32_t p = pPat[i] & 1;
+ swap_flags[p] = true;
+ }
+ }
+
+ if (swap_flags[0])
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(blog_endpoints[c][0], blog_endpoints[c][1]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 0)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ if (swap_flags[1])
+ {
+ for (uint32_t c = 0; c < 3; c++)
+ std::swap(blog_endpoints[c][2], blog_endpoints[c][3]);
+
+ for (uint32_t i = 0; i < 16; i++)
+ if ((pPat[i] & 0x7F) == 1)
+ log_blk.m_weights[i] = 7 - log_blk.m_weights[i];
+ }
+
+ // Try packing the endpoints
+ bool failed_flag = false;
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ const int max_delta = (1 << (num_delta_bits[c] - 1)) - 1;
+
+ const int min_delta = -(max_delta + 1);
+ assert((max_delta - min_delta) == delta_bitmasks[c]);
+
+ log_blk.m_endpoints[c][0] = blog_endpoints[c][0];
+
+ int delta0 = (int)blog_endpoints[c][1] - (int)blog_endpoints[c][0];
+ int delta1 = (int)blog_endpoints[c][2] - (int)blog_endpoints[c][0];
+ int delta2 = (int)blog_endpoints[c][3] - (int)blog_endpoints[c][0];
+
+ if ((delta0 < min_delta) || (delta0 > max_delta) ||
+ (delta1 < min_delta) || (delta1 > max_delta) ||
+ (delta2 < min_delta) || (delta2 > max_delta))
+ {
+ failed_flag = true;
+ break;
+ }
+
+ log_blk.m_endpoints[c][1] = delta0 & delta_bitmasks[c];
+ log_blk.m_endpoints[c][2] = delta1 & delta_bitmasks[c];
+ log_blk.m_endpoints[c][3] = delta2 & delta_bitmasks[c];
+
+ if (failed_flag)
+ break;
+ }
+ if (failed_flag)
+ continue;
+
+ log_blk.m_mode = mode;
+ log_blk.m_partition_pattern = pat_index;
+ pack_bc6h_block(*pPacked_block, log_blk);
+
+ //half_float blk[16 * 3];
+ //unpack_bc6h(pPacked_block, blk, false);
+
+ return;
+ }
+
+ bc6h_enc_block_2subset_mode9_3bit_weights(pPacked_block, common_part_index, pEndpoints, pWeights);
+ }
+
+ bool bc6h_enc_block_solid_color(bc6h_block* pPacked_block, const half_float pColor[3])
+ {
+ assert(g_bc6h_enc_initialized);
+
+ if ((pColor[0] | pColor[1] | pColor[2]) & 0x8000)
+ return false;
+
+ // ASTC block unpacker won't allow Inf/NaN's to come through.
+ //if (is_half_inf_or_nan(pColor[0]) || is_half_inf_or_nan(pColor[1]) || is_half_inf_or_nan(pColor[2]))
+ // return false;
+
+ uint8_t weights[16];
+ memset(weights, 0, sizeof(weights));
+
+ half_float endpoints[3][2];
+ endpoints[0][0] = pColor[0];
+ endpoints[0][1] = pColor[0];
+
+ endpoints[1][0] = pColor[1];
+ endpoints[1][1] = pColor[1];
+
+ endpoints[2][0] = pColor[2];
+ endpoints[2][1] = pColor[2];
+
+ bc6h_enc_block_1subset_4bit_weights(pPacked_block, endpoints, weights);
+
+ return true;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+ // basisu_astc_hdr_core.cpp
+
+ static bool g_astc_hdr_core_initialized;
+ static int8_t g_astc_partition_id_to_common_bc7_pat_index[1024];
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ void astc_hdr_core_init()
+ {
+ if (g_astc_hdr_core_initialized)
+ return;
+
+ memset(g_astc_partition_id_to_common_bc7_pat_index, 0xFF, sizeof(g_astc_partition_id_to_common_bc7_pat_index));
+
+ for (uint32_t part_index = 0; part_index < basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2; ++part_index)
+ {
+ const uint32_t astc_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_astc;
+ //const uint32_t bc7_pattern = basist::g_astc_bc7_common_partitions2[part_index].m_bc7;
+
+ assert(astc_pattern < 1024);
+ g_astc_partition_id_to_common_bc7_pat_index[astc_pattern] = (int8_t)part_index;
+ }
+
+ g_astc_hdr_core_initialized = true;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ static inline int astc_hdr_sign_extend(int src, int num_src_bits)
+ {
+ assert(basisu::in_range(num_src_bits, 2, 31));
+
+ const bool negative = (src & (1 << (num_src_bits - 1))) != 0;
+ if (negative)
+ return src | ~((1 << num_src_bits) - 1);
+ else
+ return src & ((1 << num_src_bits) - 1);
+ }
+
+ static inline void astc_hdr_pack_bit(
+ int& dst, int dst_bit,
+ int src_val, int src_bit = 0)
+ {
+ assert(dst_bit >= 0 && dst_bit <= 31);
+ int bit = basisu::get_bit(src_val, src_bit);
+ dst |= (bit << dst_bit);
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ void decode_mode7_to_qlog12_ise20(
+ const uint8_t* pEndpoints,
+ int e[2][3],
+ int* pScale)
+ {
+ assert(g_astc_hdr_core_initialized);
+
+ for (uint32_t i = 0; i < NUM_MODE7_ENDPOINTS; i++)
+ {
+ assert(pEndpoints[i] <= 255);
+ }
+
+ const int v0 = pEndpoints[0], v1 = pEndpoints[1], v2 = pEndpoints[2], v3 = pEndpoints[3];
+
+ // Extract mode bits and unpack to major component and mode.
+ const int modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4);
+
+ int majcomp, mode;
+ if ((modeval & 0xC) != 0xC)
+ {
+ majcomp = modeval >> 2;
+ mode = modeval & 3;
+ }
+ else if (modeval != 0xF)
+ {
+ majcomp = modeval & 3;
+ mode = 4;
+ }
+ else
+ {
+ majcomp = 0;
+ mode = 5;
+ }
+
+ // Extract low-order bits of r, g, b, and s.
+ int red = v0 & 0x3f;
+ int green = v1 & 0x1f;
+ int blue = v2 & 0x1f;
+ int scale = v3 & 0x1f;
+
+ // Extract high-order bits, which may be assigned depending on mode
+ int x0 = (v1 >> 6) & 1;
+ int x1 = (v1 >> 5) & 1;
+ int x2 = (v2 >> 6) & 1;
+ int x3 = (v2 >> 5) & 1;
+ int x4 = (v3 >> 7) & 1;
+ int x5 = (v3 >> 6) & 1;
+ int x6 = (v3 >> 5) & 1;
+
+ // Now move the high-order xs into the right place.
+ const int ohm = 1 << mode;
+ if (ohm & 0x30) green |= x0 << 6;
+ if (ohm & 0x3A) green |= x1 << 5;
+ if (ohm & 0x30) blue |= x2 << 6;
+ if (ohm & 0x3A) blue |= x3 << 5;
+ if (ohm & 0x3D) scale |= x6 << 5;
+ if (ohm & 0x2D) scale |= x5 << 6;
+ if (ohm & 0x04) scale |= x4 << 7;
+ if (ohm & 0x3B) red |= x4 << 6;
+ if (ohm & 0x04) red |= x3 << 6;
+ if (ohm & 0x10) red |= x5 << 7;
+ if (ohm & 0x0F) red |= x2 << 7;
+ if (ohm & 0x05) red |= x1 << 8;
+ if (ohm & 0x0A) red |= x0 << 8;
+ if (ohm & 0x05) red |= x0 << 9;
+ if (ohm & 0x02) red |= x6 << 9;
+ if (ohm & 0x01) red |= x3 << 10;
+ if (ohm & 0x02) red |= x5 << 10;
+
+ // Shift the bits to the top of the 12-bit result.
+ static const int s_shamts[6] = { 1,1,2,3,4,5 };
+
+ const int shamt = s_shamts[mode];
+ red <<= shamt;
+ green <<= shamt;
+ blue <<= shamt;
+ scale <<= shamt;
+
+ // Minor components are stored as differences
+ if (mode != 5)
+ {
+ green = red - green;
+ blue = red - blue;
+ }
+
+ // Swizzle major component into place
+ if (majcomp == 1)
+ std::swap(red, green);
+
+ if (majcomp == 2)
+ std::swap(red, blue);
+
+ // Clamp output values, set alpha to 1.0
+ e[1][0] = basisu::clamp(red, 0, 0xFFF);
+ e[1][1] = basisu::clamp(green, 0, 0xFFF);
+ e[1][2] = basisu::clamp(blue, 0, 0xFFF);
+
+ e[0][0] = basisu::clamp(red - scale, 0, 0xFFF);
+ e[0][1] = basisu::clamp(green - scale, 0, 0xFFF);
+ e[0][2] = basisu::clamp(blue - scale, 0, 0xFFF);
+
+ if (pScale)
+ *pScale = scale;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ bool decode_mode7_to_qlog12(
+ const uint8_t* pEndpoints,
+ int e[2][3],
+ int* pScale,
+ uint32_t ise_endpoint_range)
+ {
+ assert(g_astc_hdr_core_initialized);
+
+ if (ise_endpoint_range == astc_helpers::BISE_256_LEVELS)
+ {
+ decode_mode7_to_qlog12_ise20(pEndpoints, e, pScale);
+ }
+ else
+ {
+ uint8_t dequantized_endpoints[NUM_MODE7_ENDPOINTS];
+
+ for (uint32_t i = 0; i < NUM_MODE7_ENDPOINTS; i++)
+ dequantized_endpoints[i] = astc_helpers::g_dequant_tables.get_endpoint_tab(ise_endpoint_range).m_ISE_to_val[pEndpoints[i]];
+
+ decode_mode7_to_qlog12_ise20(dequantized_endpoints, e, pScale);
+ }
+
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ if (e[i][0] > (int)MAX_QLOG12)
+ return false;
+
+ if (e[i][1] > (int)MAX_QLOG12)
+ return false;
+
+ if (e[i][2] > (int)MAX_QLOG12)
+ return false;
+ }
+
+ return true;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ void decode_mode11_to_qlog12_ise20(
+ const uint8_t* pEndpoints,
+ int e[2][3])
+ {
+#ifdef _DEBUG
+ for (uint32_t i = 0; i < NUM_MODE11_ENDPOINTS; i++)
+ {
+ assert(pEndpoints[i] <= 255);
+ }
+#endif
+
+ const uint32_t maj_comp = basisu::get_bit(pEndpoints[4], 7) | (basisu::get_bit(pEndpoints[5], 7) << 1);
+
+ if (maj_comp == 3)
+ {
+ // Direct, qlog8 and qlog7
+ e[0][0] = pEndpoints[0] << 4;
+ e[1][0] = pEndpoints[1] << 4;
+
+ e[0][1] = pEndpoints[2] << 4;
+ e[1][1] = pEndpoints[3] << 4;
+
+ e[0][2] = (pEndpoints[4] & 127) << 5;
+ e[1][2] = (pEndpoints[5] & 127) << 5;
+ }
+ else
+ {
+ int v0 = pEndpoints[0];
+ int v1 = pEndpoints[1];
+ int v2 = pEndpoints[2];
+ int v3 = pEndpoints[3];
+ int v4 = pEndpoints[4];
+ int v5 = pEndpoints[5];
+
+ int mode = 0;
+ astc_hdr_pack_bit(mode, 0, v1, 7);
+ astc_hdr_pack_bit(mode, 1, v2, 7);
+ astc_hdr_pack_bit(mode, 2, v3, 7);
+
+ int va = v0;
+ astc_hdr_pack_bit(va, 8, v1, 6);
+
+ int vb0 = v2 & 63;
+ int vb1 = v3 & 63;
+ int vc = v1 & 63;
+
+ int vd0 = v4 & 0x7F; // this takes more bits than is sometimes needed
+ int vd1 = v5 & 0x7F; // this takes more bits than is sometimes needed
+ static const int8_t dbitstab[8] = { 7,6,7,6,5,6,5,6 };
+ vd0 = astc_hdr_sign_extend(vd0, dbitstab[mode]);
+ vd1 = astc_hdr_sign_extend(vd1, dbitstab[mode]);
+
+ int x0 = basisu::get_bit(v2, 6);
+ int x1 = basisu::get_bit(v3, 6);
+ int x2 = basisu::get_bit(v4, 6);
+ int x3 = basisu::get_bit(v5, 6);
+ int x4 = basisu::get_bit(v4, 5);
+ int x5 = basisu::get_bit(v5, 5);
+
+ const uint32_t ohm = 1U << mode;
+ if (ohm & 0xA4) va |= (x0 << 9);
+ if (ohm & 0x08) va |= (x2 << 9);
+ if (ohm & 0x50) va |= (x4 << 9);
+ if (ohm & 0x50) va |= (x5 << 10);
+ if (ohm & 0xA0) va |= (x1 << 10);
+ if (ohm & 0xC0) va |= (x2 << 11);
+ if (ohm & 0x04) vc |= (x1 << 6);
+ if (ohm & 0xE8) vc |= (x3 << 6);
+ if (ohm & 0x20) vc |= (x2 << 7);
+ if (ohm & 0x5B) vb0 |= (x0 << 6);
+ if (ohm & 0x5B) vb1 |= (x1 << 6);
+ if (ohm & 0x12) vb0 |= (x2 << 7);
+ if (ohm & 0x12) vb1 |= (x3 << 7);
+
+ const int shamt = (mode >> 1) ^ 3;
+
+ va = (uint32_t)va << shamt;
+ vb0 = (uint32_t)vb0 << shamt;
+ vb1 = (uint32_t)vb1 << shamt;
+ vc = (uint32_t)vc << shamt;
+ vd0 = (uint32_t)vd0 << shamt;
+ vd1 = (uint32_t)vd1 << shamt;
+
+ // qlog12
+ e[1][0] = basisu::clamp<int>(va, 0, 0xFFF);
+ e[1][1] = basisu::clamp<int>(va - vb0, 0, 0xFFF);
+ e[1][2] = basisu::clamp<int>(va - vb1, 0, 0xFFF);
+
+ e[0][0] = basisu::clamp<int>(va - vc, 0, 0xFFF);
+ e[0][1] = basisu::clamp<int>(va - vb0 - vc - vd0, 0, 0xFFF);
+ e[0][2] = basisu::clamp<int>(va - vb1 - vc - vd1, 0, 0xFFF);
+
+ if (maj_comp)
+ {
+ std::swap(e[0][0], e[0][maj_comp]);
+ std::swap(e[1][0], e[1][maj_comp]);
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ bool decode_mode11_to_qlog12(
+ const uint8_t* pEndpoints,
+ int e[2][3],
+ uint32_t ise_endpoint_range)
+ {
+ assert(g_astc_hdr_core_initialized);
+ assert((ise_endpoint_range >= astc_helpers::FIRST_VALID_ENDPOINT_ISE_RANGE) && (ise_endpoint_range <= astc_helpers::LAST_VALID_ENDPOINT_ISE_RANGE));
+
+ if (ise_endpoint_range == astc_helpers::BISE_256_LEVELS)
+ {
+ decode_mode11_to_qlog12_ise20(pEndpoints, e);
+ }
+ else
+ {
+ uint8_t dequantized_endpoints[NUM_MODE11_ENDPOINTS];
+
+ for (uint32_t i = 0; i < NUM_MODE11_ENDPOINTS; i++)
+ dequantized_endpoints[i] = astc_helpers::g_dequant_tables.get_endpoint_tab(ise_endpoint_range).m_ISE_to_val[pEndpoints[i]];
+
+ decode_mode11_to_qlog12_ise20(dequantized_endpoints, e);
+ }
+
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ if (e[i][0] > (int)MAX_QLOG12)
+ return false;
+
+ if (e[i][1] > (int)MAX_QLOG12)
+ return false;
+
+ if (e[i][2] > (int)MAX_QLOG12)
+ return false;
+ }
+
+ return true;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ bool transcode_bc6h_1subset(half_float h_e[3][2], const astc_helpers::log_astc_block& best_blk, bc6h_block& transcoded_bc6h_blk)
+ {
+ assert(g_astc_hdr_core_initialized);
+ assert((best_blk.m_weight_ise_range >= 1) && (best_blk.m_weight_ise_range <= 8));
+
+ if (best_blk.m_weight_ise_range == 5)
+ {
+ // Use 3-bit BC6H weights which are a perfect match for 3-bit ASTC weights, but encode 1-subset as 2 equal subsets
+ bc6h_enc_block_1subset_3bit_weights(&transcoded_bc6h_blk, h_e, best_blk.m_weights);
+ }
+ else
+ {
+ uint8_t bc6h_weights[16];
+
+ if (best_blk.m_weight_ise_range == 1)
+ {
+ // weight ISE 1: 3 levels
+ static const uint8_t s_astc1_to_bc6h_3[3] = { 0, 8, 15 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc1_to_bc6h_3[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 2)
+ {
+ // weight ISE 2: 4 levels
+ static const uint8_t s_astc2_to_bc6h_4[4] = { 0, 5, 10, 15 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc2_to_bc6h_4[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 3)
+ {
+ // weight ISE 3: 5 levels
+ static const uint8_t s_astc3_to_bc6h_4[5] = { 0, 4, 7, 11, 15 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc3_to_bc6h_4[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 4)
+ {
+ // weight ISE 4: 6 levels
+ static const uint8_t s_astc4_to_bc6h_4[6] = { 0, 15, 3, 12, 6, 9 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc4_to_bc6h_4[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 6)
+ {
+ // weight ISE 6: 10 levels
+ static const uint8_t s_astc6_to_bc6h_4[10] = { 0, 15, 2, 13, 3, 12, 5, 10, 6, 9 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc6_to_bc6h_4[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 7)
+ {
+ // weight ISE 7: 12 levels
+ static const uint8_t s_astc7_to_bc6h_4[12] = { 0, 15, 4, 11, 1, 14, 5, 10, 2, 13, 6, 9 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc7_to_bc6h_4[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 8)
+ {
+ // 16 levels
+ memcpy(bc6h_weights, best_blk.m_weights, 16);
+ }
+ else
+ {
+ assert(0);
+ return false;
+ }
+
+ bc6h_enc_block_1subset_4bit_weights(&transcoded_bc6h_blk, h_e, bc6h_weights);
+ }
+
+ return true;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+
+ bool transcode_bc6h_2subsets(uint32_t common_part_index, const astc_helpers::log_astc_block& best_blk, bc6h_block& transcoded_bc6h_blk)
+ {
+ assert(g_astc_hdr_core_initialized);
+ assert(best_blk.m_num_partitions == 2);
+ assert(common_part_index < basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2);
+
+ half_float bc6h_endpoints[2][3][2]; // [subset][comp][lh_index]
+
+ // UASTC HDR checks
+ // Both CEM's must be equal in 2-subset UASTC HDR.
+ if (best_blk.m_color_endpoint_modes[0] != best_blk.m_color_endpoint_modes[1])
+ return false;
+ if ((best_blk.m_color_endpoint_modes[0] != 7) && (best_blk.m_color_endpoint_modes[0] != 11))
+ return false;
+
+ if (best_blk.m_color_endpoint_modes[0] == 7)
+ {
+ if (!(((best_blk.m_weight_ise_range == 1) && (best_blk.m_endpoint_ise_range == 20)) ||
+ ((best_blk.m_weight_ise_range == 2) && (best_blk.m_endpoint_ise_range == 20)) ||
+ ((best_blk.m_weight_ise_range == 3) && (best_blk.m_endpoint_ise_range == 19)) ||
+ ((best_blk.m_weight_ise_range == 4) && (best_blk.m_endpoint_ise_range == 17)) ||
+ ((best_blk.m_weight_ise_range == 5) && (best_blk.m_endpoint_ise_range == 15))))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!(((best_blk.m_weight_ise_range == 1) && (best_blk.m_endpoint_ise_range == 14)) ||
+ ((best_blk.m_weight_ise_range == 2) && (best_blk.m_endpoint_ise_range == 12))))
+ {
+ return false;
+ }
+ }
+
+ for (uint32_t s = 0; s < 2; s++)
+ {
+ int e[2][3];
+ if (best_blk.m_color_endpoint_modes[0] == 7)
+ {
+ bool success = decode_mode7_to_qlog12(best_blk.m_endpoints + s * NUM_MODE7_ENDPOINTS, e, nullptr, best_blk.m_endpoint_ise_range);
+ if (!success)
+ return false;
+ }
+ else
+ {
+ bool success = decode_mode11_to_qlog12(best_blk.m_endpoints + s * NUM_MODE11_ENDPOINTS, e, best_blk.m_endpoint_ise_range);
+ if (!success)
+ return false;
+ }
+
+ for (uint32_t c = 0; c < 3; c++)
+ {
+ bc6h_endpoints[s][c][0] = qlog_to_half_slow(e[0][c], 12);
+ if (is_half_inf_or_nan(bc6h_endpoints[s][c][0]))
+ return false;
+
+ bc6h_endpoints[s][c][1] = qlog_to_half_slow(e[1][c], 12);
+ if (is_half_inf_or_nan(bc6h_endpoints[s][c][1]))
+ return false;
+ }
+ }
+
+ uint8_t bc6h_weights[16];
+ if (best_blk.m_weight_ise_range == 1)
+ {
+ static const uint8_t s_astc1_to_bc6h_3[3] = { 0, 4, 7 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc1_to_bc6h_3[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 2)
+ {
+ static const uint8_t s_astc2_to_bc6h_3[4] = { 0, 2, 5, 7 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc2_to_bc6h_3[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 3)
+ {
+ static const uint8_t s_astc3_to_bc6h_3[5] = { 0, 2, 4, 5, 7 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc3_to_bc6h_3[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 4)
+ {
+ static const uint8_t s_astc4_to_bc6h_3[6] = { 0, 7, 1, 6, 3, 4 };
+
+ for (uint32_t i = 0; i < 16; i++)
+ bc6h_weights[i] = s_astc4_to_bc6h_3[best_blk.m_weights[i]];
+ }
+ else if (best_blk.m_weight_ise_range == 5)
+ {
+ memcpy(bc6h_weights, best_blk.m_weights, 16);
+ }
+ else
+ {
+ assert(0);
+ return false;
+ }
+
+ bc6h_enc_block_2subset_3bit_weights(&transcoded_bc6h_blk, common_part_index, bc6h_endpoints, bc6h_weights);
+
+ return true;
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+ // Transcodes an UASTC HDR block to BC6H. Must have been encoded to UASTC HDR, or this fails.
+ bool astc_hdr_transcode_to_bc6h(const astc_blk& src_blk, bc6h_block& dst_blk)
+ {
+ assert(g_astc_hdr_core_initialized);
+ if (!g_astc_hdr_core_initialized)
+ {
+ assert(0);
+ return false;
+ }
+
+ astc_helpers::log_astc_block log_blk;
+
+ if (!astc_helpers::unpack_block(&src_blk, log_blk, 4, 4))
+ {
+ // Failed unpacking ASTC data
+ return false;
+ }
+
+ return astc_hdr_transcode_to_bc6h(log_blk, dst_blk);
+ }
+
+ //--------------------------------------------------------------------------------------------------------------------------
+ // Transcodes an UASTC HDR block to BC6H. Must have been encoded to UASTC HDR, or this fails.
+ bool astc_hdr_transcode_to_bc6h(const astc_helpers::log_astc_block& log_blk, bc6h_block& dst_blk)
+ {
+ assert(g_astc_hdr_core_initialized);
+ if (!g_astc_hdr_core_initialized)
+ {
+ assert(0);
+ return false;
+ }
+
+ if (log_blk.m_solid_color_flag_ldr)
+ {
+ // Don't support LDR solid colors.
+ return false;
+ }
+
+ if (log_blk.m_solid_color_flag_hdr)
+ {
+ // Solid color HDR block
+ return bc6h_enc_block_solid_color(&dst_blk, log_blk.m_solid_color);
+ }
+
+ // Only support 4x4 grid sizes
+ if ((log_blk.m_grid_width != 4) || (log_blk.m_grid_height != 4))
+ return false;
+
+ // Don't support dual plane encoding
+ if (log_blk.m_dual_plane)
+ return false;
+
+ if (log_blk.m_num_partitions == 1)
+ {
+ // Handle 1 partition (or subset)
+
+ // UASTC HDR checks
+ if ((log_blk.m_weight_ise_range < 1) || (log_blk.m_weight_ise_range > 8))
+ return false;
+
+ int e[2][3];
+ bool success;
+
+ if (log_blk.m_color_endpoint_modes[0] == 7)
+ {
+ if (log_blk.m_endpoint_ise_range != 20)
+ return false;
+
+ success = decode_mode7_to_qlog12(log_blk.m_endpoints, e, nullptr, log_blk.m_endpoint_ise_range);
+ }
+ else if (log_blk.m_color_endpoint_modes[0] == 11)
+ {
+ // UASTC HDR checks
+ if (log_blk.m_weight_ise_range <= 7)
+ {
+ if (log_blk.m_endpoint_ise_range != 20)
+ return false;
+ }
+ else if (log_blk.m_endpoint_ise_range != 19)
+ {
+ return false;
+ }
+
+ success = decode_mode11_to_qlog12(log_blk.m_endpoints, e, log_blk.m_endpoint_ise_range);
+ }
+ else
+ {
+ return false;
+ }
+
+ if (!success)
+ return false;
+
+ // Transform endpoints to half float
+ half_float h_e[3][2] =
+ {
+ { qlog_to_half_slow(e[0][0], 12), qlog_to_half_slow(e[1][0], 12) },
+ { qlog_to_half_slow(e[0][1], 12), qlog_to_half_slow(e[1][1], 12) },
+ { qlog_to_half_slow(e[0][2], 12), qlog_to_half_slow(e[1][2], 12) }
+ };
+
+ // Sanity check for NaN/Inf
+ for (uint32_t i = 0; i < 2; i++)
+ if (is_half_inf_or_nan(h_e[0][i]) || is_half_inf_or_nan(h_e[1][i]) || is_half_inf_or_nan(h_e[2][i]))
+ return false;
+
+ // Transcode to bc6h
+ if (!transcode_bc6h_1subset(h_e, log_blk, dst_blk))
+ return false;
+ }
+ else if (log_blk.m_num_partitions == 2)
+ {
+ // Handle 2 partition (or subset)
+ int common_bc7_pat_index = g_astc_partition_id_to_common_bc7_pat_index[log_blk.m_partition_id];
+ if (common_bc7_pat_index < 0)
+ return false;
+
+ assert(common_bc7_pat_index < (int)basist::TOTAL_ASTC_BC6H_COMMON_PARTITIONS2);
+
+ if (!transcode_bc6h_2subsets(common_bc7_pat_index, log_blk, dst_blk))
+ return false;
+ }
+ else
+ {
+ // Only supports 1 or 2 partitions (or subsets)
+ return false;
+ }
+
+ return true;
+ }
+#endif // BASISD_SUPPORT_UASTC_HDR
+
} // namespace basist
diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder.h b/thirdparty/basis_universal/transcoder/basisu_transcoder.h
index 3327e8ddb7..8324e99698 100644
--- a/thirdparty/basis_universal/transcoder/basisu_transcoder.h
+++ b/thirdparty/basis_universal/transcoder/basisu_transcoder.h
@@ -1,5 +1,5 @@
// basisu_transcoder.h
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
// Important: If compiling with gcc, be sure strict aliasing is disabled: -fno-strict-aliasing
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -29,6 +29,7 @@
// Set BASISU_FORCE_DEVEL_MESSAGES to 1 to enable debug printf()'s whenever an error occurs, for easier debugging during development.
#ifndef BASISU_FORCE_DEVEL_MESSAGES
+ // TODO - disable before checking in
#define BASISU_FORCE_DEVEL_MESSAGES 0
#endif
@@ -55,7 +56,7 @@ namespace basist
cTFETC2_RGBA = 1, // Opaque+alpha, ETC2_EAC_A8 block followed by a ETC1 block, alpha channel will be opaque for opaque .basis files
// BC1-5, BC7 (desktop, some mobile devices)
- cTFBC1_RGB = 2, // Opaque only, no punchthrough alpha support yet, transcodes alpha slice if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
+ cTFBC1_RGB = 2, // Opaque only, no punchthrough alpha support yet, transcodes alpha slice if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
cTFBC3_RGBA = 3, // Opaque+alpha, BC4 followed by a BC1 block, alpha channel will be opaque for opaque .basis files
cTFBC4_R = 4, // Red only, alpha slice is transcoded to output if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
cTFBC5_RG = 5, // XY: Two BC4 blocks, X=R and Y=Alpha, .basis file should have alpha data (if not Y will be all 255's)
@@ -63,10 +64,11 @@ namespace basist
// PVRTC1 4bpp (mobile, PowerVR devices)
cTFPVRTC1_4_RGB = 8, // Opaque only, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified, nearly lowest quality of any texture format.
- cTFPVRTC1_4_RGBA = 9, // Opaque+alpha, most useful for simple opacity maps. If .basis file doesn't have alpha cTFPVRTC1_4_RGB will be used instead. Lowest quality of any supported texture format.
+ cTFPVRTC1_4_RGBA = 9, // Opaque+alpha, most useful for simple opacity maps. If .basis file doesn't have alpha cTFPVRTC1_4_RGB will be used instead. Lowest quality of any supported texture format.
// ASTC (mobile, Intel devices, hopefully all desktop GPU's one day)
- cTFASTC_4x4_RGBA = 10, // Opaque+alpha, ASTC 4x4, alpha channel will be opaque for opaque .basis files. Transcoder uses RGB/RGBA/L/LA modes, void extent, and up to two ([0,47] and [0,255]) endpoint precisions.
+ cTFASTC_4x4_RGBA = 10, // LDR. Opaque+alpha, ASTC 4x4, alpha channel will be opaque for opaque .basis files.
+ // LDR: Transcoder uses RGB/RGBA/L/LA modes, void extent, and up to two ([0,47] and [0,255]) endpoint precisions.
// ATC (mobile, Adreno devices, this is a niche format)
cTFATC_RGB = 11, // Opaque, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified. ATI ATC (GL_ATC_RGB_AMD)
@@ -74,8 +76,8 @@ namespace basist
// FXT1 (desktop, Intel devices, this is a super obscure format)
cTFFXT1_RGB = 17, // Opaque only, uses exclusively CC_MIXED blocks. Notable for having a 8x4 block size. GL_3DFX_texture_compression_FXT1 is supported on Intel integrated GPU's (such as HD 630).
- // Punch-through alpha is relatively easy to support, but full alpha is harder. This format is only here for completeness so opaque-only is fine for now.
- // See the BASISU_USE_ORIGINAL_3DFX_FXT1_ENCODING macro in basisu_transcoder_internal.h.
+ // Punch-through alpha is relatively easy to support, but full alpha is harder. This format is only here for completeness so opaque-only is fine for now.
+ // See the BASISU_USE_ORIGINAL_3DFX_FXT1_ENCODING macro in basisu_transcoder_internal.h.
cTFPVRTC2_4_RGB = 18, // Opaque-only, almost BC1 quality, much faster to transcode and supports arbitrary texture dimensions (unlike PVRTC1 RGB).
cTFPVRTC2_4_RGBA = 19, // Opaque+alpha, slower to encode than cTFPVRTC2_4_RGB. Premultiplied alpha is highly recommended, otherwise the color channel can leak into the alpha channel on transparent blocks.
@@ -83,13 +85,22 @@ namespace basist
cTFETC2_EAC_R11 = 20, // R only (ETC2 EAC R11 unsigned)
cTFETC2_EAC_RG11 = 21, // RG only (ETC2 EAC RG11 unsigned), R=opaque.r, G=alpha - for tangent space normal maps
+ cTFBC6H = 22, // HDR, RGB only, unsigned
+ cTFASTC_HDR_4x4_RGBA = 23, // HDR, RGBA (currently UASTC HDR is only RGB), unsigned
+
// Uncompressed (raw pixel) formats
+ // Note these uncompressed formats (RGBA32, 565, and 4444) can only be transcoded to from LDR input files (ETC1S or UASTC LDR).
cTFRGBA32 = 13, // 32bpp RGBA image stored in raster (not block) order in memory, R is first byte, A is last byte.
cTFRGB565 = 14, // 16bpp RGB image stored in raster (not block) order in memory, R at bit position 11
cTFBGR565 = 15, // 16bpp RGB image stored in raster (not block) order in memory, R at bit position 0
- cTFRGBA4444 = 16, // 16bpp RGBA image stored in raster (not block) order in memory, R at bit position 12, A at bit position 0
+ cTFRGBA4444 = 16, // 16bpp RGBA image stored in raster (not block) order in memory, R at bit position 12, A at bit position 0
+
+ // Note these uncompressed formats (HALF and 9E5) can only be transcoded to from HDR input files (UASTC HDR).
+ cTFRGB_HALF = 24, // 48bpp RGB half (16-bits/component, 3 components)
+ cTFRGBA_HALF = 25, // 64bpp RGBA half (16-bits/component, 4 components) (A will always currently 1.0, UASTC_HDR doesn't support alpha)
+ cTFRGB_9E5 = 26, // 32bpp RGB 9E5 (shared exponent, positive only, see GL_EXT_texture_shared_exponent)
- cTFTotalTextureFormats = 22,
+ cTFTotalTextureFormats = 27,
// Old enums for compatibility with code compiled against previous versions
cTFETC1 = cTFETC1_RGB,
@@ -124,6 +135,9 @@ namespace basist
// Returns true if the format supports an alpha channel.
bool basis_transcoder_format_has_alpha(transcoder_texture_format fmt);
+ // Returns true if the format is HDR.
+ bool basis_transcoder_format_is_hdr(transcoder_texture_format fmt);
+
// Returns the basisu::texture_format corresponding to the specified transcoder_texture_format.
basisu::texture_format basis_get_basisu_texture_format(transcoder_texture_format fmt);
@@ -142,7 +156,7 @@ namespace basist
// Returns the block height for the specified texture format, which is currently always 4.
uint32_t basis_get_block_height(transcoder_texture_format tex_type);
- // Returns true if the specified format was enabled at compile time.
+ // Returns true if the specified format was enabled at compile time, and is supported for the specific basis/ktx2 texture format (ETC1S, UASTC, or UASTC HDR).
bool basis_is_format_supported(transcoder_texture_format tex_type, basis_tex_format fmt = basis_tex_format::cETC1S);
// Validates that the output buffer is large enough to hold the entire transcoded texture.
@@ -317,6 +331,42 @@ namespace basist
int channel0 = -1, int channel1 = -1);
};
+ class basisu_lowlevel_uastc_hdr_transcoder
+ {
+ friend class basisu_transcoder;
+
+ public:
+ basisu_lowlevel_uastc_hdr_transcoder();
+
+ bool transcode_slice(void* pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t* pImage_data, uint32_t image_data_size, block_format fmt,
+ uint32_t output_block_or_pixel_stride_in_bytes, bool bc1_allow_threecolor_blocks, bool has_alpha, const uint32_t orig_width, const uint32_t orig_height, uint32_t output_row_pitch_in_blocks_or_pixels = 0,
+ basisu_transcoder_state* pState = nullptr, uint32_t output_rows_in_pixels = 0, int channel0 = -1, int channel1 = -1, uint32_t decode_flags = 0);
+
+ bool transcode_slice(void* pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t* pImage_data, uint32_t image_data_size, block_format fmt,
+ uint32_t output_block_or_pixel_stride_in_bytes, bool bc1_allow_threecolor_blocks, const basis_file_header& header, const basis_slice_desc& slice_desc, uint32_t output_row_pitch_in_blocks_or_pixels = 0,
+ basisu_transcoder_state* pState = nullptr, uint32_t output_rows_in_pixels = 0, int channel0 = -1, int channel1 = -1, uint32_t decode_flags = 0)
+ {
+ return transcode_slice(pDst_blocks, num_blocks_x, num_blocks_y, pImage_data, image_data_size, fmt,
+ output_block_or_pixel_stride_in_bytes, bc1_allow_threecolor_blocks, (header.m_flags & cBASISHeaderFlagHasAlphaSlices) != 0, slice_desc.m_orig_width, slice_desc.m_orig_height, output_row_pitch_in_blocks_or_pixels,
+ pState, output_rows_in_pixels, channel0, channel1, decode_flags);
+ }
+
+ // Container independent transcoding
+ bool transcode_image(
+ transcoder_texture_format target_format,
+ void* pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
+ const uint8_t* pCompressed_data, uint32_t compressed_data_length,
+ uint32_t num_blocks_x, uint32_t num_blocks_y, uint32_t orig_width, uint32_t orig_height, uint32_t level_index,
+ uint32_t slice_offset, uint32_t slice_length,
+ uint32_t decode_flags = 0,
+ bool has_alpha = false,
+ bool is_video = false,
+ uint32_t output_row_pitch_in_blocks_or_pixels = 0,
+ basisu_transcoder_state* pState = nullptr,
+ uint32_t output_rows_in_pixels = 0,
+ int channel0 = -1, int channel1 = -1);
+ };
+
struct basisu_slice_info
{
uint32_t m_orig_width;
@@ -530,6 +580,7 @@ namespace basist
private:
mutable basisu_lowlevel_etc1s_transcoder m_lowlevel_etc1s_decoder;
mutable basisu_lowlevel_uastc_transcoder m_lowlevel_uastc_decoder;
+ mutable basisu_lowlevel_uastc_hdr_transcoder m_lowlevel_uastc_hdr_decoder;
bool m_ready_to_transcode;
@@ -612,10 +663,12 @@ namespace basist
#pragma pack(pop)
const uint32_t KTX2_VK_FORMAT_UNDEFINED = 0;
+ const uint32_t KTX2_FORMAT_UASTC_4x4_SFLOAT_BLOCK = 1000066000; // TODO, is this correct?
const uint32_t KTX2_KDF_DF_MODEL_UASTC = 166;
+ const uint32_t KTX2_KDF_DF_MODEL_UASTC_HDR = 167;
const uint32_t KTX2_KDF_DF_MODEL_ETC1S = 163;
const uint32_t KTX2_IMAGE_IS_P_FRAME = 2;
- const uint32_t KTX2_UASTC_BLOCK_SIZE = 16;
+ const uint32_t KTX2_UASTC_BLOCK_SIZE = 16; // also the block size for UASTC_HDR
const uint32_t KTX2_MAX_SUPPORTED_LEVEL_COUNT = 16; // this is an implementation specific constraint and can be increased
// The KTX2 transfer functions supported by KTX2
@@ -800,13 +853,15 @@ namespace basist
// Returns 0 or the number of layers in the texture array or texture video. Valid after init().
uint32_t get_layers() const { return m_header.m_layer_count; }
- // Returns cETC1S or cUASTC4x4. Valid after init().
+ // Returns cETC1S, cUASTC4x4, or cUASTC_HDR_4x4. Valid after init().
basist::basis_tex_format get_format() const { return m_format; }
-
+
bool is_etc1s() const { return get_format() == basist::basis_tex_format::cETC1S; }
bool is_uastc() const { return get_format() == basist::basis_tex_format::cUASTC4x4; }
+ bool is_hdr() const { return get_format() == basist::basis_tex_format::cUASTC_HDR_4x4; }
+
// Returns true if the ETC1S file has two planes (typically RGBA, or RRRG), or true if the UASTC file has alpha data. Valid after init().
uint32_t get_has_alpha() const { return m_has_alpha; }
@@ -913,6 +968,7 @@ namespace basist
basist::basisu_lowlevel_etc1s_transcoder m_etc1s_transcoder;
basist::basisu_lowlevel_uastc_transcoder m_uastc_transcoder;
+ basist::basisu_lowlevel_uastc_hdr_transcoder m_uastc_hdr_transcoder;
ktx2_transcoder_state m_def_transcoder_state;
diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder_internal.h b/thirdparty/basis_universal/transcoder/basisu_transcoder_internal.h
index 0505df6ea6..17c9dc7c8c 100644
--- a/thirdparty/basis_universal/transcoder/basisu_transcoder_internal.h
+++ b/thirdparty/basis_universal/transcoder/basisu_transcoder_internal.h
@@ -1,5 +1,5 @@
// basisu_transcoder_internal.h - Universal texture format transcoder library.
-// Copyright (C) 2019-2021 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2019-2024 Binomial LLC. All Rights Reserved.
//
// Important: If compiling with gcc, be sure strict aliasing is disabled: -fno-strict-aliasing
//
@@ -20,8 +20,9 @@
#pragma warning (disable: 4127) // conditional expression is constant
#endif
-#define BASISD_LIB_VERSION 116
-#define BASISD_VERSION_STRING "01.16"
+// v1.50: Added UASTC HDR support
+#define BASISD_LIB_VERSION 150
+#define BASISD_VERSION_STRING "01.50"
#ifdef _DEBUG
#define BASISD_BUILD_DEBUG
@@ -82,9 +83,15 @@ namespace basist
cRGBA4444_ALPHA,
cRGBA4444_COLOR_OPAQUE,
cRGBA4444,
-
- cUASTC_4x4,
-
+ cRGBA_HALF,
+ cRGB_HALF,
+ cRGB_9E5,
+
+ cUASTC_4x4, // LDR, universal
+ cUASTC_HDR_4x4, // HDR, transcodes only to 4x4 HDR ASTC, BC6H, or uncompressed
+ cBC6H,
+ cASTC_HDR_4x4,
+
cTotalBlockFormats
};
@@ -264,8 +271,8 @@ namespace basist
}
const basisu::uint8_vec &get_code_sizes() const { return m_code_sizes; }
- const basisu::int_vec get_lookup() const { return m_lookup; }
- const basisu::int16_vec get_tree() const { return m_tree; }
+ const basisu::int_vec &get_lookup() const { return m_lookup; }
+ const basisu::int16_vec &get_tree() const { return m_tree; }
bool is_valid() const { return m_code_sizes.size() > 0; }
@@ -789,7 +796,198 @@ namespace basist
};
bool basis_block_format_is_uncompressed(block_format tex_type);
-
+
+ //------------------------------------
+
+ typedef uint16_t half_float;
+
+ const double MIN_DENORM_HALF_FLOAT = 0.000000059604645; // smallest positive subnormal number
+ const double MIN_HALF_FLOAT = 0.00006103515625; // smallest positive normal number
+ const double MAX_HALF_FLOAT = 65504.0; // largest normal number
+
+ inline uint32_t get_bits(uint32_t val, int low, int high)
+ {
+ const int num_bits = (high - low) + 1;
+ assert((num_bits >= 1) && (num_bits <= 32));
+
+ val >>= low;
+ if (num_bits != 32)
+ val &= ((1u << num_bits) - 1);
+
+ return val;
+ }
+
+ inline bool is_half_inf_or_nan(half_float v)
+ {
+ return get_bits(v, 10, 14) == 31;
+ }
+
+ inline bool is_half_denorm(half_float v)
+ {
+ int e = (v >> 10) & 31;
+ return !e;
+ }
+
+ inline int get_half_exp(half_float v)
+ {
+ int e = ((v >> 10) & 31);
+ return e ? (e - 15) : -14;
+ }
+
+ inline int get_half_mantissa(half_float v)
+ {
+ if (is_half_denorm(v))
+ return v & 0x3FF;
+ return (v & 0x3FF) | 0x400;
+ }
+
+ inline float get_half_mantissaf(half_float v)
+ {
+ return ((float)get_half_mantissa(v)) / 1024.0f;
+ }
+
+ inline int get_half_sign(half_float v)
+ {
+ return v ? ((v & 0x8000) ? -1 : 1) : 0;
+ }
+
+ inline bool half_is_signed(half_float v)
+ {
+ return (v & 0x8000) != 0;
+ }
+
+#if 0
+ int hexp = get_half_exp(Cf);
+ float hman = get_half_mantissaf(Cf);
+ int hsign = get_half_sign(Cf);
+ float k = powf(2.0f, hexp) * hman * hsign;
+ if (is_half_inf_or_nan(Cf))
+ k = std::numeric_limits<float>::quiet_NaN();
+#endif
+
+ half_float float_to_half(float val);
+
+ inline float half_to_float(half_float hval)
+ {
+ union { float f; uint32_t u; } x = { 0 };
+
+ uint32_t s = ((uint32_t)hval >> 15) & 1;
+ uint32_t e = ((uint32_t)hval >> 10) & 0x1F;
+ uint32_t m = (uint32_t)hval & 0x3FF;
+
+ if (!e)
+ {
+ if (!m)
+ {
+ // +- 0
+ x.u = s << 31;
+ return x.f;
+ }
+ else
+ {
+ // denormalized
+ while (!(m & 0x00000400))
+ {
+ m <<= 1;
+ --e;
+ }
+
+ ++e;
+ m &= ~0x00000400;
+ }
+ }
+ else if (e == 31)
+ {
+ if (m == 0)
+ {
+ // +/- INF
+ x.u = (s << 31) | 0x7f800000;
+ return x.f;
+ }
+ else
+ {
+ // +/- NaN
+ x.u = (s << 31) | 0x7f800000 | (m << 13);
+ return x.f;
+ }
+ }
+
+ e = e + (127 - 15);
+ m = m << 13;
+
+ assert(s <= 1);
+ assert(m <= 0x7FFFFF);
+ assert(e <= 255);
+
+ x.u = m | (e << 23) | (s << 31);
+ return x.f;
+ }
+
+ // Originally from bc6h_enc.h
+
+ void bc6h_enc_init();
+
+ const uint32_t MAX_BLOG16_VAL = 0xFFFF;
+
+ // BC6H internals
+ const uint32_t NUM_BC6H_MODES = 14;
+ const uint32_t BC6H_LAST_MODE_INDEX = 13;
+ const uint32_t BC6H_FIRST_1SUBSET_MODE_INDEX = 10; // in the MS docs, this is "mode 11" (where the first mode is 1), 60 bits for endpoints (10.10, 10.10, 10.10), 63 bits for weights
+ const uint32_t TOTAL_BC6H_PARTITION_PATTERNS = 32;
+
+ extern const uint8_t g_bc6h_mode_sig_bits[NUM_BC6H_MODES][4]; // base, r, g, b
+
+ struct bc6h_bit_layout
+ {
+ int8_t m_comp; // R=0,G=1,B=2,D=3 (D=partition index)
+ int8_t m_index; // 0-3, 0-1 Low/High subset 1, 2-3 Low/High subset 2, -1=partition index (d)
+ int8_t m_last_bit;
+ int8_t m_first_bit; // may be -1 if a single bit, may be >m_last_bit if reversed
+ };
+
+ const uint32_t MAX_BC6H_LAYOUT_INDEX = 25;
+ extern const bc6h_bit_layout g_bc6h_bit_layouts[NUM_BC6H_MODES][MAX_BC6H_LAYOUT_INDEX];
+
+ extern const uint8_t g_bc6h_2subset_patterns[TOTAL_BC6H_PARTITION_PATTERNS][4][4]; // [y][x]
+
+ extern const uint8_t g_bc6h_weight3[8];
+ extern const uint8_t g_bc6h_weight4[16];
+
+ extern const int8_t g_bc6h_mode_lookup[32];
+
+ // Converts b16 to half float
+ inline half_float bc6h_blog16_to_half(uint32_t comp)
+ {
+ assert(comp <= 0xFFFF);
+
+ // scale the magnitude by 31/64
+ comp = (comp * 31u) >> 6u;
+ return (half_float)comp;
+ }
+
+ const uint32_t MAX_BC6H_HALF_FLOAT_AS_UINT = 0x7BFF;
+
+ // Inverts bc6h_blog16_to_half().
+ // Returns the nearest blog16 given a half value.
+ inline uint32_t bc6h_half_to_blog16(half_float h)
+ {
+ assert(h <= MAX_BC6H_HALF_FLOAT_AS_UINT);
+ return (h * 64 + 30) / 31;
+ }
+
+ struct bc6h_block
+ {
+ uint8_t m_bytes[16];
+ };
+
+ void bc6h_enc_block_mode10(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights);
+ void bc6h_enc_block_1subset_4bit_weights(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights);
+ void bc6h_enc_block_1subset_mode9_3bit_weights(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights);
+ void bc6h_enc_block_1subset_3bit_weights(bc6h_block* pPacked_block, const half_float pEndpoints[3][2], const uint8_t* pWeights);
+ void bc6h_enc_block_2subset_mode9_3bit_weights(bc6h_block* pPacked_block, uint32_t common_part_index, const half_float pEndpoints[2][3][2], const uint8_t* pWeights); // pEndpoints[subset][comp][lh_index]
+ void bc6h_enc_block_2subset_3bit_weights(bc6h_block* pPacked_block, uint32_t common_part_index, const half_float pEndpoints[2][3][2], const uint8_t* pWeights); // pEndpoints[subset][comp][lh_index]
+ bool bc6h_enc_block_solid_color(bc6h_block* pPacked_block, const half_float pColor[3]);
+
} // namespace basist
diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_5.inc b/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_5.inc
index 8244550959..205758b3d7 100644
--- a/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_5.inc
+++ b/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_5.inc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2017-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_6.inc b/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_6.inc
index fad45fe22d..f2d324fcc3 100644
--- a/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_6.inc
+++ b/thirdparty/basis_universal/transcoder/basisu_transcoder_tables_dxt1_6.inc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2019 Binomial LLC. All Rights Reserved.
+// Copyright (C) 2017-2024 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder_uastc.h b/thirdparty/basis_universal/transcoder/basisu_transcoder_uastc.h
index f91314f4ff..457bd51e30 100644
--- a/thirdparty/basis_universal/transcoder/basisu_transcoder_uastc.h
+++ b/thirdparty/basis_universal/transcoder/basisu_transcoder_uastc.h
@@ -13,6 +13,7 @@ namespace basist
const uint32_t UASTC_MODE_INDEX_SOLID_COLOR = 8;
const uint32_t TOTAL_ASTC_BC7_COMMON_PARTITIONS2 = 30;
+ const uint32_t TOTAL_ASTC_BC6H_COMMON_PARTITIONS2 = 27; // BC6H only supports only 5-bit pattern indices, BC7 supports 4-bit or 6-bit
const uint32_t TOTAL_ASTC_BC7_COMMON_PARTITIONS3 = 11;
const uint32_t TOTAL_BC7_3_ASTC2_COMMON_PARTITIONS = 19;
diff --git a/thirdparty/harfbuzz/src/OT/Color/COLR/COLR.hh b/thirdparty/harfbuzz/src/OT/Color/COLR/COLR.hh
index 835d87f8c6..6b5b104f3d 100644
--- a/thirdparty/harfbuzz/src/OT/Color/COLR/COLR.hh
+++ b/thirdparty/harfbuzz/src/OT/Color/COLR/COLR.hh
@@ -72,7 +72,7 @@ public:
hb_map_t current_glyphs;
hb_map_t current_layers;
int depth_left = HB_MAX_NESTING_LEVEL;
- int edge_count = HB_COLRV1_MAX_EDGE_COUNT;
+ int edge_count = HB_MAX_GRAPH_EDGE_COUNT;
hb_paint_context_t (const void *base_,
hb_paint_funcs_t *funcs_,
@@ -2339,7 +2339,11 @@ struct COLR
c->plan->colrv1_varstore_inner_maps.as_array ()))
return_trace (false);
- if (!out->varStore.serialize_serialize (c->serializer,
+ /* do not serialize varStore if there's no variation data after
+ * instancing: region_list or var_data is empty */
+ if (item_vars.get_region_list () &&
+ item_vars.get_vardata_encodings () &&
+ !out->varStore.serialize_serialize (c->serializer,
item_vars.has_long_word (),
c->plan->axis_tags,
item_vars.get_region_list (),
@@ -2347,7 +2351,9 @@ struct COLR
return_trace (false);
/* if varstore is optimized, update colrv1_new_deltaset_idx_varidx_map in
- * subset plan */
+ * subset plan.
+ * If varstore is empty after instancing, varidx_map would be empty and
+ * all var_idxes will be updated to VarIdx::NO_VARIATION */
if (optimize)
{
const hb_map_t &varidx_map = item_vars.get_varidx_map ();
@@ -2579,10 +2585,6 @@ struct COLR
{
// COLRv1 glyph
- ItemVarStoreInstancer instancer (&(this+varStore),
- &(this+varIdxMap),
- hb_array (font->coords, font->num_coords));
-
bool is_bounded = true;
if (clip)
{
diff --git a/thirdparty/harfbuzz/src/OT/Layout/GDEF/GDEF.hh b/thirdparty/harfbuzz/src/OT/Layout/GDEF/GDEF.hh
index 45baeb4ec5..16b232a2ae 100644
--- a/thirdparty/harfbuzz/src/OT/Layout/GDEF/GDEF.hh
+++ b/thirdparty/harfbuzz/src/OT/Layout/GDEF/GDEF.hh
@@ -633,8 +633,8 @@ struct GDEFVersion1_2
ligCaretList.sanitize (c, this) &&
markAttachClassDef.sanitize (c, this) &&
hb_barrier () &&
- (version.to_int () < 0x00010002u || markGlyphSetsDef.sanitize (c, this)) &&
- (version.to_int () < 0x00010003u || varStore.sanitize (c, this)));
+ ((version.to_int () < 0x00010002u && hb_barrier ()) || markGlyphSetsDef.sanitize (c, this)) &&
+ ((version.to_int () < 0x00010003u && hb_barrier ()) || varStore.sanitize (c, this)));
}
static void remap_varidx_after_instantiation (const hb_map_t& varidx_map,
@@ -668,13 +668,13 @@ struct GDEFVersion1_2
// the end of the GDEF table.
// See: https://github.com/harfbuzz/harfbuzz/issues/4636
auto snapshot_version0 = c->serializer->snapshot ();
- if (unlikely (version.to_int () >= 0x00010002u && !c->serializer->embed (markGlyphSetsDef)))
+ if (unlikely (version.to_int () >= 0x00010002u && hb_barrier () && !c->serializer->embed (markGlyphSetsDef)))
return_trace (false);
bool subset_varstore = false;
unsigned varstore_index = (unsigned) -1;
auto snapshot_version2 = c->serializer->snapshot ();
- if (version.to_int () >= 0x00010003u)
+ if (version.to_int () >= 0x00010003u && hb_barrier ())
{
if (unlikely (!c->serializer->embed (varStore))) return_trace (false);
if (c->plan->all_axes_pinned)
@@ -712,7 +712,7 @@ struct GDEFVersion1_2
}
bool subset_markglyphsetsdef = false;
- if (version.to_int () >= 0x00010002u)
+ if (version.to_int () >= 0x00010002u && hb_barrier ())
{
subset_markglyphsetsdef = out->markGlyphSetsDef.serialize_subset (c, markGlyphSetsDef, this);
}
@@ -875,7 +875,7 @@ struct GDEF
bool has_mark_glyph_sets () const
{
switch (u.version.major) {
- case 1: return u.version.to_int () >= 0x00010002u && u.version1.markGlyphSetsDef != 0;
+ case 1: return u.version.to_int () >= 0x00010002u && hb_barrier () && u.version1.markGlyphSetsDef != 0;
#ifndef HB_NO_BEYOND_64K
case 2: return u.version2.markGlyphSetsDef != 0;
#endif
@@ -885,7 +885,7 @@ struct GDEF
const MarkGlyphSets &get_mark_glyph_sets () const
{
switch (u.version.major) {
- case 1: return u.version.to_int () >= 0x00010002u ? this+u.version1.markGlyphSetsDef : Null(MarkGlyphSets);
+ case 1: return u.version.to_int () >= 0x00010002u && hb_barrier () ? this+u.version1.markGlyphSetsDef : Null(MarkGlyphSets);
#ifndef HB_NO_BEYOND_64K
case 2: return this+u.version2.markGlyphSetsDef;
#endif
@@ -895,7 +895,7 @@ struct GDEF
bool has_var_store () const
{
switch (u.version.major) {
- case 1: return u.version.to_int () >= 0x00010003u && u.version1.varStore != 0;
+ case 1: return u.version.to_int () >= 0x00010003u && hb_barrier () && u.version1.varStore != 0;
#ifndef HB_NO_BEYOND_64K
case 2: return u.version2.varStore != 0;
#endif
@@ -905,7 +905,7 @@ struct GDEF
const ItemVariationStore &get_var_store () const
{
switch (u.version.major) {
- case 1: return u.version.to_int () >= 0x00010003u ? this+u.version1.varStore : Null(ItemVariationStore);
+ case 1: return u.version.to_int () >= 0x00010003u && hb_barrier () ? this+u.version1.varStore : Null(ItemVariationStore);
#ifndef HB_NO_BEYOND_64K
case 2: return this+u.version2.varStore;
#endif
diff --git a/thirdparty/harfbuzz/src/OT/Layout/GPOS/PairPosFormat2.hh b/thirdparty/harfbuzz/src/OT/Layout/GPOS/PairPosFormat2.hh
index 9c805b39a1..5ffeb5d0c1 100644
--- a/thirdparty/harfbuzz/src/OT/Layout/GPOS/PairPosFormat2.hh
+++ b/thirdparty/harfbuzz/src/OT/Layout/GPOS/PairPosFormat2.hh
@@ -139,14 +139,8 @@ struct PairPosFormat2_4 : ValueBase
return_trace (false);
}
- unsigned int klass2 = (this+classDef2).get_class (buffer->info[skippy_iter.idx].codepoint);
- if (!klass2)
- {
- buffer->unsafe_to_concat (buffer->idx, skippy_iter.idx + 1);
- return_trace (false);
- }
-
unsigned int klass1 = (this+classDef1).get_class (buffer->cur().codepoint);
+ unsigned int klass2 = (this+classDef2).get_class (buffer->info[skippy_iter.idx].codepoint);
if (unlikely (klass1 >= class1Count || klass2 >= class2Count))
{
buffer->unsafe_to_concat (buffer->idx, skippy_iter.idx + 1);
diff --git a/thirdparty/harfbuzz/src/OT/Var/VARC/VARC.cc b/thirdparty/harfbuzz/src/OT/Var/VARC/VARC.cc
new file mode 100644
index 0000000000..1afb571113
--- /dev/null
+++ b/thirdparty/harfbuzz/src/OT/Var/VARC/VARC.cc
@@ -0,0 +1,346 @@
+#include "VARC.hh"
+
+#ifndef HB_NO_VAR_COMPOSITES
+
+#include "../../../hb-draw.hh"
+#include "../../../hb-geometry.hh"
+#include "../../../hb-ot-layout-common.hh"
+#include "../../../hb-ot-layout-gdef-table.hh"
+
+namespace OT {
+
+//namespace Var {
+
+
+struct hb_transforming_pen_context_t
+{
+ hb_transform_t transform;
+ hb_draw_funcs_t *dfuncs;
+ void *data;
+ hb_draw_state_t *st;
+};
+
+static void
+hb_transforming_pen_move_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
+ void *data,
+ hb_draw_state_t *st,
+ float to_x, float to_y,
+ void *user_data HB_UNUSED)
+{
+ hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
+
+ c->transform.transform_point (to_x, to_y);
+
+ c->dfuncs->move_to (c->data, *c->st, to_x, to_y);
+}
+
+static void
+hb_transforming_pen_line_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
+ void *data,
+ hb_draw_state_t *st,
+ float to_x, float to_y,
+ void *user_data HB_UNUSED)
+{
+ hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
+
+ c->transform.transform_point (to_x, to_y);
+
+ c->dfuncs->line_to (c->data, *c->st, to_x, to_y);
+}
+
+static void
+hb_transforming_pen_quadratic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
+ void *data,
+ hb_draw_state_t *st,
+ float control_x, float control_y,
+ float to_x, float to_y,
+ void *user_data HB_UNUSED)
+{
+ hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
+
+ c->transform.transform_point (control_x, control_y);
+ c->transform.transform_point (to_x, to_y);
+
+ c->dfuncs->quadratic_to (c->data, *c->st, control_x, control_y, to_x, to_y);
+}
+
+static void
+hb_transforming_pen_cubic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
+ void *data,
+ hb_draw_state_t *st,
+ float control1_x, float control1_y,
+ float control2_x, float control2_y,
+ float to_x, float to_y,
+ void *user_data HB_UNUSED)
+{
+ hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
+
+ c->transform.transform_point (control1_x, control1_y);
+ c->transform.transform_point (control2_x, control2_y);
+ c->transform.transform_point (to_x, to_y);
+
+ c->dfuncs->cubic_to (c->data, *c->st, control1_x, control1_y, control2_x, control2_y, to_x, to_y);
+}
+
+static void
+hb_transforming_pen_close_path (hb_draw_funcs_t *dfuncs HB_UNUSED,
+ void *data,
+ hb_draw_state_t *st,
+ void *user_data HB_UNUSED)
+{
+ hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
+
+ c->dfuncs->close_path (c->data, *c->st);
+}
+
+static inline void free_static_transforming_pen_funcs ();
+
+static struct hb_transforming_pen_funcs_lazy_loader_t : hb_draw_funcs_lazy_loader_t<hb_transforming_pen_funcs_lazy_loader_t>
+{
+ static hb_draw_funcs_t *create ()
+ {
+ hb_draw_funcs_t *funcs = hb_draw_funcs_create ();
+
+ hb_draw_funcs_set_move_to_func (funcs, hb_transforming_pen_move_to, nullptr, nullptr);
+ hb_draw_funcs_set_line_to_func (funcs, hb_transforming_pen_line_to, nullptr, nullptr);
+ hb_draw_funcs_set_quadratic_to_func (funcs, hb_transforming_pen_quadratic_to, nullptr, nullptr);
+ hb_draw_funcs_set_cubic_to_func (funcs, hb_transforming_pen_cubic_to, nullptr, nullptr);
+ hb_draw_funcs_set_close_path_func (funcs, hb_transforming_pen_close_path, nullptr, nullptr);
+
+ hb_draw_funcs_make_immutable (funcs);
+
+ hb_atexit (free_static_transforming_pen_funcs);
+
+ return funcs;
+ }
+} static_transforming_pen_funcs;
+
+static inline
+void free_static_transforming_pen_funcs ()
+{
+ static_transforming_pen_funcs.free_instance ();
+}
+
+static hb_draw_funcs_t *
+hb_transforming_pen_get_funcs ()
+{
+ return static_transforming_pen_funcs.get_unconst ();
+}
+
+
+hb_ubytes_t
+VarComponent::get_path_at (hb_font_t *font,
+ hb_codepoint_t parent_gid,
+ hb_draw_session_t &draw_session,
+ hb_array_t<const int> coords,
+ hb_ubytes_t total_record,
+ hb_set_t *visited,
+ signed *edges_left,
+ signed depth_left,
+ VarRegionList::cache_t *cache) const
+{
+ const unsigned char *end = total_record.arrayZ + total_record.length;
+ const unsigned char *record = total_record.arrayZ;
+
+ auto &VARC = *font->face->table.VARC;
+ auto &varStore = &VARC+VARC.varStore;
+ auto instancer = MultiItemVarStoreInstancer(&varStore, nullptr, coords, cache);
+
+#define READ_UINT32VAR(name) \
+ HB_STMT_START { \
+ if (unlikely (unsigned (end - record) < HBUINT32VAR::min_size)) return hb_ubytes_t (); \
+ hb_barrier (); \
+ auto &varint = * (const HBUINT32VAR *) record; \
+ unsigned size = varint.get_size (); \
+ if (unlikely (unsigned (end - record) < size)) return hb_ubytes_t (); \
+ name = (uint32_t) varint; \
+ record += size; \
+ } HB_STMT_END
+
+ uint32_t flags;
+ READ_UINT32VAR (flags);
+
+ // gid
+
+ hb_codepoint_t gid = 0;
+ if (flags & (unsigned) flags_t::GID_IS_24BIT)
+ {
+ if (unlikely (unsigned (end - record) < HBGlyphID24::static_size))
+ return hb_ubytes_t ();
+ hb_barrier ();
+ gid = * (const HBGlyphID24 *) record;
+ record += HBGlyphID24::static_size;
+ }
+ else
+ {
+ if (unlikely (unsigned (end - record) < HBGlyphID16::static_size))
+ return hb_ubytes_t ();
+ hb_barrier ();
+ gid = * (const HBGlyphID16 *) record;
+ record += HBGlyphID16::static_size;
+ }
+
+ // Condition
+ bool show = true;
+ if (flags & (unsigned) flags_t::HAVE_CONDITION)
+ {
+ unsigned conditionIndex;
+ READ_UINT32VAR (conditionIndex);
+ const auto &condition = (&VARC+VARC.conditionList)[conditionIndex];
+ show = condition.evaluate (coords.arrayZ, coords.length, &instancer);
+ }
+
+ // Axis values
+
+ hb_vector_t<unsigned> axisIndices;
+ hb_vector_t<float> axisValues;
+ if (flags & (unsigned) flags_t::HAVE_AXES)
+ {
+ unsigned axisIndicesIndex;
+ READ_UINT32VAR (axisIndicesIndex);
+ axisIndices = (&VARC+VARC.axisIndicesList)[axisIndicesIndex];
+ axisValues.resize (axisIndices.length);
+ const HBUINT8 *p = (const HBUINT8 *) record;
+ TupleValues::decompile (p, axisValues, (const HBUINT8 *) end);
+ record += (const unsigned char *) p - record;
+ }
+
+ // Apply variations if any
+ if (flags & (unsigned) flags_t::AXIS_VALUES_HAVE_VARIATION)
+ {
+ uint32_t axisValuesVarIdx;
+ READ_UINT32VAR (axisValuesVarIdx);
+ if (show && coords && !axisValues.in_error ())
+ varStore.get_delta (axisValuesVarIdx, coords, axisValues.as_array (), cache);
+ }
+
+ auto component_coords = coords;
+ /* Copying coords is expensive; so we have put an arbitrary
+ * limit on the max number of coords for now. */
+ if ((flags & (unsigned) flags_t::RESET_UNSPECIFIED_AXES) ||
+ coords.length > HB_VAR_COMPOSITE_MAX_AXES)
+ component_coords = hb_array<int> (font->coords, font->num_coords);
+
+ // Transform
+
+ uint32_t transformVarIdx = VarIdx::NO_VARIATION;
+ if (flags & (unsigned) flags_t::TRANSFORM_HAS_VARIATION)
+ READ_UINT32VAR (transformVarIdx);
+
+#define PROCESS_TRANSFORM_COMPONENTS \
+ HB_STMT_START { \
+ PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TRANSLATE_X, translateX); \
+ PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TRANSLATE_Y, translateY); \
+ PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_ROTATION, rotation); \
+ PROCESS_TRANSFORM_COMPONENT (F6DOT10, HAVE_SCALE_X, scaleX); \
+ PROCESS_TRANSFORM_COMPONENT (F6DOT10, HAVE_SCALE_Y, scaleY); \
+ PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_SKEW_X, skewX); \
+ PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_SKEW_Y, skewY); \
+ PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TCENTER_X, tCenterX); \
+ PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TCENTER_Y, tCenterY); \
+ } HB_STMT_END
+
+ hb_transform_decomposed_t transform;
+
+ // Read transform components
+#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
+ if (flags & (unsigned) flags_t::flag) \
+ { \
+ static_assert (type::static_size == HBINT16::static_size, ""); \
+ if (unlikely (unsigned (end - record) < HBINT16::static_size)) \
+ return hb_ubytes_t (); \
+ hb_barrier (); \
+ transform.name = * (const HBINT16 *) record; \
+ record += HBINT16::static_size; \
+ }
+ PROCESS_TRANSFORM_COMPONENTS;
+#undef PROCESS_TRANSFORM_COMPONENT
+
+ // Read reserved records
+ unsigned i = flags & (unsigned) flags_t::RESERVED_MASK;
+ while (i)
+ {
+ HB_UNUSED uint32_t discard;
+ READ_UINT32VAR (discard);
+ i &= i - 1;
+ }
+
+ /* Parsing is over now. */
+
+ if (show)
+ {
+ // Only use coord_setter if there's actually any axis overrides.
+ coord_setter_t coord_setter (axisIndices ? component_coords : hb_array<int> ());
+ // Go backwards, to reduce coord_setter vector reallocations.
+ for (unsigned i = axisIndices.length; i; i--)
+ coord_setter[axisIndices[i - 1]] = axisValues[i - 1];
+ if (axisIndices)
+ component_coords = coord_setter.get_coords ();
+
+ // Apply transform variations if any
+ if (transformVarIdx != VarIdx::NO_VARIATION && coords)
+ {
+ float transformValues[9];
+ unsigned numTransformValues = 0;
+#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
+ if (flags & (unsigned) flags_t::flag) \
+ transformValues[numTransformValues++] = transform.name;
+ PROCESS_TRANSFORM_COMPONENTS;
+#undef PROCESS_TRANSFORM_COMPONENT
+ varStore.get_delta (transformVarIdx, coords, hb_array (transformValues, numTransformValues), cache);
+ numTransformValues = 0;
+#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
+ if (flags & (unsigned) flags_t::flag) \
+ transform.name = transformValues[numTransformValues++];
+ PROCESS_TRANSFORM_COMPONENTS;
+#undef PROCESS_TRANSFORM_COMPONENT
+ }
+
+ // Divide them by their divisors
+#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
+ if (flags & (unsigned) flags_t::flag) \
+ { \
+ HBINT16 int_v; \
+ int_v = roundf (transform.name); \
+ type typed_v = * (const type *) &int_v; \
+ float float_v = (float) typed_v; \
+ transform.name = float_v; \
+ }
+ PROCESS_TRANSFORM_COMPONENTS;
+#undef PROCESS_TRANSFORM_COMPONENT
+
+ if (!(flags & (unsigned) flags_t::HAVE_SCALE_Y))
+ transform.scaleY = transform.scaleX;
+
+ // Scale the transform by the font's scale
+ float x_scale = font->x_multf;
+ float y_scale = font->y_multf;
+ transform.translateX *= x_scale;
+ transform.translateY *= y_scale;
+ transform.tCenterX *= x_scale;
+ transform.tCenterY *= y_scale;
+
+ // Build a transforming pen to apply the transform.
+ hb_draw_funcs_t *transformer_funcs = hb_transforming_pen_get_funcs ();
+ hb_transforming_pen_context_t context {transform.to_transform (),
+ draw_session.funcs,
+ draw_session.draw_data,
+ &draw_session.st};
+ hb_draw_session_t transformer_session {transformer_funcs, &context};
+
+ VARC.get_path_at (font, gid,
+ transformer_session, component_coords,
+ parent_gid,
+ visited, edges_left, depth_left - 1);
+ }
+
+#undef PROCESS_TRANSFORM_COMPONENTS
+#undef READ_UINT32VAR
+
+ return hb_ubytes_t (record, end - record);
+}
+
+//} // namespace Var
+} // namespace OT
+
+#endif
diff --git a/thirdparty/harfbuzz/src/OT/Var/VARC/VARC.hh b/thirdparty/harfbuzz/src/OT/Var/VARC/VARC.hh
new file mode 100644
index 0000000000..d60f7b0c28
--- /dev/null
+++ b/thirdparty/harfbuzz/src/OT/Var/VARC/VARC.hh
@@ -0,0 +1,193 @@
+#ifndef OT_VAR_VARC_VARC_HH
+#define OT_VAR_VARC_VARC_HH
+
+#include "../../../hb-ot-layout-common.hh"
+#include "../../../hb-ot-glyf-table.hh"
+#include "../../../hb-ot-cff2-table.hh"
+#include "../../../hb-ot-cff1-table.hh"
+
+#include "coord-setter.hh"
+
+namespace OT {
+
+//namespace Var {
+
+/*
+ * VARC -- Variable Composites
+ * https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md
+ */
+
+#ifndef HB_NO_VAR_COMPOSITES
+
+struct VarComponent
+{
+ enum class flags_t : uint32_t
+ {
+ RESET_UNSPECIFIED_AXES = 1u << 0,
+ HAVE_AXES = 1u << 1,
+ AXIS_VALUES_HAVE_VARIATION = 1u << 2,
+ TRANSFORM_HAS_VARIATION = 1u << 3,
+ HAVE_TRANSLATE_X = 1u << 4,
+ HAVE_TRANSLATE_Y = 1u << 5,
+ HAVE_ROTATION = 1u << 6,
+ HAVE_CONDITION = 1u << 7,
+ HAVE_SCALE_X = 1u << 8,
+ HAVE_SCALE_Y = 1u << 9,
+ HAVE_TCENTER_X = 1u << 10,
+ HAVE_TCENTER_Y = 1u << 11,
+ GID_IS_24BIT = 1u << 12,
+ HAVE_SKEW_X = 1u << 13,
+ HAVE_SKEW_Y = 1u << 14,
+ RESERVED_MASK = ~((1u << 15) - 1),
+ };
+
+ HB_INTERNAL hb_ubytes_t
+ get_path_at (hb_font_t *font,
+ hb_codepoint_t parent_gid,
+ hb_draw_session_t &draw_session,
+ hb_array_t<const int> coords,
+ hb_ubytes_t record,
+ hb_set_t *visited,
+ signed *edges_left,
+ signed depth_left,
+ VarRegionList::cache_t *cache = nullptr) const;
+};
+
+struct VarCompositeGlyph
+{
+ static void
+ get_path_at (hb_font_t *font,
+ hb_codepoint_t glyph,
+ hb_draw_session_t &draw_session,
+ hb_array_t<const int> coords,
+ hb_ubytes_t record,
+ hb_set_t *visited,
+ signed *edges_left,
+ signed depth_left,
+ VarRegionList::cache_t *cache = nullptr)
+ {
+ while (record)
+ {
+ const VarComponent &comp = * (const VarComponent *) (record.arrayZ);
+ record = comp.get_path_at (font, glyph,
+ draw_session, coords,
+ record,
+ visited, edges_left, depth_left, cache);
+ }
+ }
+};
+
+HB_MARK_AS_FLAG_T (VarComponent::flags_t);
+
+struct VARC
+{
+ friend struct VarComponent;
+
+ static constexpr hb_tag_t tableTag = HB_TAG ('V', 'A', 'R', 'C');
+
+ bool
+ get_path_at (hb_font_t *font,
+ hb_codepoint_t glyph,
+ hb_draw_session_t &draw_session,
+ hb_array_t<const int> coords,
+ hb_codepoint_t parent_glyph = HB_CODEPOINT_INVALID,
+ hb_set_t *visited = nullptr,
+ signed *edges_left = nullptr,
+ signed depth_left = HB_MAX_NESTING_LEVEL) const
+ {
+ hb_set_t stack_set;
+ if (visited == nullptr)
+ visited = &stack_set;
+ signed stack_edges = HB_MAX_GRAPH_EDGE_COUNT;
+ if (edges_left == nullptr)
+ edges_left = &stack_edges;
+
+ // Don't recurse on the same glyph.
+ unsigned idx = glyph == parent_glyph ?
+ NOT_COVERED :
+ (this+coverage).get_coverage (glyph);
+ if (idx == NOT_COVERED)
+ {
+ if (!font->face->table.glyf->get_path_at (font, glyph, draw_session, coords))
+#ifndef HB_NO_CFF
+ if (!font->face->table.cff2->get_path_at (font, glyph, draw_session, coords))
+ if (!font->face->table.cff1->get_path (font, glyph, draw_session)) // Doesn't have variations
+#endif
+ return false;
+ return true;
+ }
+
+ if (depth_left <= 0)
+ return true;
+
+ if (*edges_left <= 0)
+ return true;
+ (*edges_left)--;
+
+ if (visited->has (glyph) || visited->in_error ())
+ return true;
+ visited->add (glyph);
+
+ hb_ubytes_t record = (this+glyphRecords)[idx];
+
+ VarRegionList::cache_t *cache = record.length >= 64 ? // Heuristic
+ (this+varStore).create_cache ()
+ : nullptr;
+
+ VarCompositeGlyph::get_path_at (font, glyph,
+ draw_session, coords,
+ record,
+ visited, edges_left, depth_left,
+ cache);
+
+ (this+varStore).destroy_cache (cache);
+
+ visited->del (glyph);
+
+ return true;
+ }
+
+ bool
+ get_path (hb_font_t *font, hb_codepoint_t gid, hb_draw_session_t &draw_session) const
+ { return get_path_at (font, gid, draw_session, hb_array (font->coords, font->num_coords)); }
+
+ bool paint_glyph (hb_font_t *font, hb_codepoint_t gid, hb_paint_funcs_t *funcs, void *data, hb_color_t foreground) const
+ {
+ funcs->push_clip_glyph (data, gid, font);
+ funcs->color (data, true, foreground);
+ funcs->pop_clip (data);
+
+ return true;
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (version.sanitize (c) &&
+ hb_barrier () &&
+ version.major == 1 &&
+ coverage.sanitize (c, this) &&
+ varStore.sanitize (c, this) &&
+ conditionList.sanitize (c, this) &&
+ axisIndicesList.sanitize (c, this) &&
+ glyphRecords.sanitize (c, this));
+ }
+
+ protected:
+ FixedVersion<> version; /* Version identifier */
+ Offset32To<Coverage> coverage;
+ Offset32To<MultiItemVariationStore> varStore;
+ Offset32To<ConditionList> conditionList;
+ Offset32To<TupleList> axisIndicesList;
+ Offset32To<CFF2Index/*Of<VarCompositeGlyph>*/> glyphRecords;
+ public:
+ DEFINE_SIZE_STATIC (24);
+};
+
+#endif
+
+//}
+
+}
+
+#endif /* OT_VAR_VARC_VARC_HH */
diff --git a/thirdparty/harfbuzz/src/OT/Var/VARC/coord-setter.hh b/thirdparty/harfbuzz/src/OT/Var/VARC/coord-setter.hh
new file mode 100644
index 0000000000..a2b483ce25
--- /dev/null
+++ b/thirdparty/harfbuzz/src/OT/Var/VARC/coord-setter.hh
@@ -0,0 +1,37 @@
+#ifndef OT_VAR_VARC_COORD_SETTER_HH
+#define OT_VAR_VARC_COORD_SETTER_HH
+
+
+#include "../../../hb.hh"
+
+
+namespace OT {
+//namespace Var {
+
+
+struct coord_setter_t
+{
+ coord_setter_t (hb_array_t<const int> coords) :
+ coords (coords) {}
+
+ int& operator [] (unsigned idx)
+ {
+ if (unlikely (idx >= HB_VAR_COMPOSITE_MAX_AXES))
+ return Crap(int);
+ if (coords.length < idx + 1)
+ coords.resize (idx + 1);
+ return coords[idx];
+ }
+
+ hb_array_t<int> get_coords ()
+ { return coords.as_array (); }
+
+ hb_vector_t<int> coords;
+};
+
+
+//} // namespace Var
+
+} // namespace OT
+
+#endif /* OT_VAR_VARC_COORD_SETTER_HH */
diff --git a/thirdparty/harfbuzz/src/OT/glyf/Glyph.hh b/thirdparty/harfbuzz/src/OT/glyf/Glyph.hh
index 69a0b625c7..7772597e59 100644
--- a/thirdparty/harfbuzz/src/OT/glyf/Glyph.hh
+++ b/thirdparty/harfbuzz/src/OT/glyf/Glyph.hh
@@ -7,8 +7,6 @@
#include "GlyphHeader.hh"
#include "SimpleGlyph.hh"
#include "CompositeGlyph.hh"
-#include "VarCompositeGlyph.hh"
-#include "coord-setter.hh"
namespace OT {
@@ -33,9 +31,6 @@ struct Glyph
EMPTY,
SIMPLE,
COMPOSITE,
-#ifndef HB_NO_VAR_COMPOSITES
- VAR_COMPOSITE,
-#endif
};
public:
@@ -44,22 +39,10 @@ struct Glyph
if (type != COMPOSITE) return composite_iter_t ();
return CompositeGlyph (*header, bytes).iter ();
}
- var_composite_iter_t get_var_composite_iterator () const
- {
-#ifndef HB_NO_VAR_COMPOSITES
- if (type != VAR_COMPOSITE) return var_composite_iter_t ();
- return VarCompositeGlyph (*header, bytes).iter ();
-#else
- return var_composite_iter_t ();
-#endif
- }
const hb_bytes_t trim_padding () const
{
switch (type) {
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE: return VarCompositeGlyph (*header, bytes).trim_padding ();
-#endif
case COMPOSITE: return CompositeGlyph (*header, bytes).trim_padding ();
case SIMPLE: return SimpleGlyph (*header, bytes).trim_padding ();
case EMPTY: return bytes;
@@ -70,9 +53,6 @@ struct Glyph
void drop_hints ()
{
switch (type) {
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE: return; // No hinting
-#endif
case COMPOSITE: CompositeGlyph (*header, bytes).drop_hints (); return;
case SIMPLE: SimpleGlyph (*header, bytes).drop_hints (); return;
case EMPTY: return;
@@ -82,9 +62,6 @@ struct Glyph
void set_overlaps_flag ()
{
switch (type) {
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE: return; // No overlaps flag
-#endif
case COMPOSITE: CompositeGlyph (*header, bytes).set_overlaps_flag (); return;
case SIMPLE: SimpleGlyph (*header, bytes).set_overlaps_flag (); return;
case EMPTY: return;
@@ -94,9 +71,6 @@ struct Glyph
void drop_hints_bytes (hb_bytes_t &dest_start, hb_bytes_t &dest_end) const
{
switch (type) {
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE: return; // No hinting
-#endif
case COMPOSITE: CompositeGlyph (*header, bytes).drop_hints_bytes (dest_start); return;
case SIMPLE: SimpleGlyph (*header, bytes).drop_hints_bytes (dest_start, dest_end); return;
case EMPTY: return;
@@ -120,14 +94,6 @@ struct Glyph
if (unlikely (!item.get_points (points))) return false;
break;
}
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE:
- {
- for (auto &item : get_var_composite_iterator ())
- if (unlikely (!item.get_points (points))) return false;
- break;
- }
-#endif
case EMPTY:
break;
}
@@ -303,13 +269,6 @@ struct Glyph
{
switch (type)
{
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE:
- // TODO
- dest_end = hb_bytes_t ();
- break;
-#endif
-
case COMPOSITE:
if (!CompositeGlyph (*header, bytes).compile_bytes_with_deltas (dest_start,
points_with_deltas,
@@ -352,7 +311,7 @@ struct Glyph
bool shift_points_hori = true,
bool use_my_metrics = true,
bool phantom_only = false,
- hb_array_t<int> coords = hb_array_t<int> (),
+ hb_array_t<const int> coords = hb_array_t<const int> (),
hb_map_t *current_glyphs = nullptr,
unsigned int depth = 0,
unsigned *edge_count = nullptr) const
@@ -360,7 +319,7 @@ struct Glyph
if (unlikely (depth > HB_MAX_NESTING_LEVEL)) return false;
unsigned stack_edge_count = 0;
if (!edge_count) edge_count = &stack_edge_count;
- if (unlikely (*edge_count > HB_GLYF_MAX_EDGE_COUNT)) return false;
+ if (unlikely (*edge_count > HB_MAX_GRAPH_EDGE_COUNT)) return false;
(*edge_count)++;
hb_map_t current_glyphs_stack;
@@ -394,14 +353,6 @@ struct Glyph
if (unlikely (!item.get_points (points))) return false;
break;
}
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE:
- {
- for (auto &item : get_var_composite_iterator ())
- if (unlikely (!item.get_points (points))) return false;
- break;
- }
-#endif
case EMPTY:
break;
}
@@ -542,81 +493,6 @@ struct Glyph
}
all_points.extend (phantoms);
} break;
-#ifndef HB_NO_VAR_COMPOSITES
- case VAR_COMPOSITE:
- {
- hb_array_t<contour_point_t> points_left = points.as_array ();
- for (auto &item : get_var_composite_iterator ())
- {
- hb_codepoint_t item_gid = item.get_gid ();
-
- if (unlikely (current_glyphs->has (item_gid)))
- continue;
-
- current_glyphs->add (item_gid);
-
- unsigned item_num_points = item.get_num_points ();
- hb_array_t<contour_point_t> record_points = points_left.sub_array (0, item_num_points);
- assert (record_points.length == item_num_points);
-
- auto component_coords = coords;
- /* Copying coords is expensive; so we have put an arbitrary
- * limit on the max number of coords for now. */
- if (item.is_reset_unspecified_axes () ||
- coords.length > HB_GLYF_VAR_COMPOSITE_MAX_AXES)
- component_coords = hb_array<int> ();
-
- coord_setter_t coord_setter (component_coords);
- item.set_variations (coord_setter, record_points);
-
- unsigned old_count = all_points.length;
-
- if (unlikely ((!phantom_only || (use_my_metrics && item.is_use_my_metrics ())) &&
- !glyf_accelerator.glyph_for_gid (item_gid)
- .get_points (font,
- glyf_accelerator,
- all_points,
- points_with_deltas,
- head_maxp_info,
- nullptr,
- shift_points_hori,
- use_my_metrics,
- phantom_only,
- coord_setter.get_coords (),
- current_glyphs,
- depth + 1,
- edge_count)))
- {
- current_glyphs->del (item_gid);
- return false;
- }
-
- auto comp_points = all_points.as_array ().sub_array (old_count);
-
- /* Apply component transformation */
- if (comp_points) // Empty in case of phantom_only
- item.transform_points (record_points, comp_points);
-
- /* Copy phantom points from component if USE_MY_METRICS flag set */
- if (use_my_metrics && item.is_use_my_metrics ())
- for (unsigned int i = 0; i < PHANTOM_COUNT; i++)
- phantoms[i] = comp_points[comp_points.length - PHANTOM_COUNT + i];
-
- all_points.resize (all_points.length - PHANTOM_COUNT);
-
- if (all_points.length > HB_GLYF_MAX_POINTS)
- {
- current_glyphs->del (item_gid);
- return false;
- }
-
- points_left += item_num_points;
-
- current_glyphs->del (item_gid);
- }
- all_points.extend (phantoms);
- } break;
-#endif
case EMPTY:
all_points.extend (phantoms);
break;
@@ -627,7 +503,7 @@ struct Glyph
/* Undocumented rasterizer behavior:
* Shift points horizontally by the updated left side bearing
*/
- int v = -phantoms[PHANTOM_LEFT].x;
+ float v = -phantoms[PHANTOM_LEFT].x;
if (v)
for (auto &point : all_points)
point.x += v;
@@ -661,10 +537,7 @@ struct Glyph
int num_contours = header->numberOfContours;
if (unlikely (num_contours == 0)) type = EMPTY;
else if (num_contours > 0) type = SIMPLE;
- else if (num_contours == -1) type = COMPOSITE;
-#ifndef HB_NO_VAR_COMPOSITES
- else if (num_contours == -2) type = VAR_COMPOSITE;
-#endif
+ else if (num_contours <= -1) type = COMPOSITE;
else type = EMPTY; // Spec deviation; Spec says COMPOSITE, but not seen in the wild.
}
diff --git a/thirdparty/harfbuzz/src/OT/glyf/SubsetGlyph.hh b/thirdparty/harfbuzz/src/OT/glyf/SubsetGlyph.hh
index 8099d3c126..fe63066e41 100644
--- a/thirdparty/harfbuzz/src/OT/glyf/SubsetGlyph.hh
+++ b/thirdparty/harfbuzz/src/OT/glyf/SubsetGlyph.hh
@@ -53,23 +53,12 @@ struct SubsetGlyph
if (plan->new_gid_for_old_gid (_.get_gid(), &new_gid))
const_cast<CompositeGlyphRecord &> (_).set_gid (new_gid);
}
-#ifndef HB_NO_VAR_COMPOSITES
- for (auto &_ : Glyph (dest_glyph).get_var_composite_iterator ())
- {
- hb_codepoint_t new_gid;
- if (plan->new_gid_for_old_gid (_.get_gid(), &new_gid))
- const_cast<VarCompositeGlyphRecord &> (_).set_gid (new_gid);
- }
-#endif
#ifndef HB_NO_BEYOND_64K
auto it = Glyph (dest_glyph).get_composite_iterator ();
if (it)
{
- /* lower GID24 to GID16 in components if possible.
- *
- * TODO: VarComposite. Not as critical, since VarComposite supports
- * gid24 from the first version. */
+ /* lower GID24 to GID16 in components if possible. */
char *p = it ? (char *) &*it : nullptr;
char *q = p;
const char *end = dest_glyph.arrayZ + dest_glyph.length;
diff --git a/thirdparty/harfbuzz/src/OT/glyf/glyf.hh b/thirdparty/harfbuzz/src/OT/glyf/glyf.hh
index 6300cf4be0..f346ae05dc 100644
--- a/thirdparty/harfbuzz/src/OT/glyf/glyf.hh
+++ b/thirdparty/harfbuzz/src/OT/glyf/glyf.hh
@@ -205,8 +205,12 @@ struct glyf_accelerator_t
protected:
template<typename T>
- bool get_points (hb_font_t *font, hb_codepoint_t gid, T consumer) const
+ bool get_points (hb_font_t *font, hb_codepoint_t gid, T consumer,
+ hb_array_t<const int> coords = hb_array_t<const int> ()) const
{
+ if (!coords)
+ coords = hb_array (font->coords, font->num_coords);
+
if (gid >= num_glyphs) return false;
/* Making this allocfree is not that easy
@@ -216,7 +220,7 @@ struct glyf_accelerator_t
contour_point_vector_t all_points;
bool phantom_only = !consumer.is_consuming_contour_points ();
- if (unlikely (!glyph_for_gid (gid).get_points (font, *this, all_points, nullptr, nullptr, nullptr, true, true, phantom_only)))
+ if (unlikely (!glyph_for_gid (gid).get_points (font, *this, all_points, nullptr, nullptr, nullptr, true, true, phantom_only, coords)))
return false;
unsigned count = all_points.length;
@@ -408,6 +412,11 @@ struct glyf_accelerator_t
get_path (hb_font_t *font, hb_codepoint_t gid, hb_draw_session_t &draw_session) const
{ return get_points (font, gid, glyf_impl::path_builder_t (font, draw_session)); }
+ bool
+ get_path_at (hb_font_t *font, hb_codepoint_t gid, hb_draw_session_t &draw_session,
+ hb_array_t<const int> coords) const
+ { return get_points (font, gid, glyf_impl::path_builder_t (font, draw_session), coords); }
+
#ifndef HB_NO_VAR
const gvar_accelerator_t *gvar;
#endif
diff --git a/thirdparty/harfbuzz/src/graph/graph.hh b/thirdparty/harfbuzz/src/graph/graph.hh
index 2a9d8346c0..b24507ece1 100644
--- a/thirdparty/harfbuzz/src/graph/graph.hh
+++ b/thirdparty/harfbuzz/src/graph/graph.hh
@@ -368,7 +368,7 @@ struct graph_t
// it's parent where possible.
int64_t modified_distance =
- hb_min (hb_max(distance + distance_modifier (), 0), 0x7FFFFFFFFFF);
+ hb_clamp (distance + distance_modifier (), (int64_t) 0, 0x7FFFFFFFFFF);
if (has_max_priority ()) {
modified_distance = 0;
}
@@ -1141,7 +1141,7 @@ struct graph_t
unsigned clone_idx = duplicate (child_idx);
if (clone_idx == (unsigned) -1) return false;
-
+
for (unsigned parent_idx : *parents) {
// duplicate shifts the root node idx, so if parent_idx was root update it.
if (parent_idx == clone_idx) parent_idx++;
diff --git a/thirdparty/harfbuzz/src/hb-aat-layout-common.hh b/thirdparty/harfbuzz/src/hb-aat-layout-common.hh
index c26f376aa6..2ea86a2a19 100644
--- a/thirdparty/harfbuzz/src/hb-aat-layout-common.hh
+++ b/thirdparty/harfbuzz/src/hb-aat-layout-common.hh
@@ -39,6 +39,7 @@ namespace AAT {
using namespace OT;
+#define HB_AAT_BUFFER_DIGEST_THRESHOLD 32
struct ankr;
@@ -60,6 +61,7 @@ struct hb_aat_apply_context_t :
const ankr *ankr_table;
const OT::GDEF *gdef_table;
const hb_sorted_vector_t<hb_aat_map_t::range_flags_t> *range_flags = nullptr;
+ hb_set_digest_t buffer_digest = hb_set_digest_t::full ();
hb_set_digest_t machine_glyph_set = hb_set_digest_t::full ();
hb_set_digest_t left_set = hb_set_digest_t::full ();
hb_set_digest_t right_set = hb_set_digest_t::full ();
@@ -466,11 +468,11 @@ struct Lookup
const T* get_value (hb_codepoint_t glyph_id, unsigned int num_glyphs) const
{
switch (u.format) {
- case 0: return u.format0.get_value (glyph_id, num_glyphs);
- case 2: return u.format2.get_value (glyph_id);
- case 4: return u.format4.get_value (glyph_id);
- case 6: return u.format6.get_value (glyph_id);
- case 8: return u.format8.get_value (glyph_id);
+ case 0: hb_barrier (); return u.format0.get_value (glyph_id, num_glyphs);
+ case 2: hb_barrier (); return u.format2.get_value (glyph_id);
+ case 4: hb_barrier (); return u.format4.get_value (glyph_id);
+ case 6: hb_barrier (); return u.format6.get_value (glyph_id);
+ case 8: hb_barrier (); return u.format8.get_value (glyph_id);
default:return nullptr;
}
}
@@ -479,7 +481,7 @@ struct Lookup
{
switch (u.format) {
/* Format 10 cannot return a pointer. */
- case 10: return u.format10.get_value_or_null (glyph_id);
+ case 10: hb_barrier (); return u.format10.get_value_or_null (glyph_id);
default:
const T *v = get_value (glyph_id, num_glyphs);
return v ? *v : Null (T);
@@ -490,12 +492,12 @@ struct Lookup
void collect_glyphs (set_t &glyphs, unsigned int num_glyphs) const
{
switch (u.format) {
- case 0: u.format0.collect_glyphs (glyphs, num_glyphs); return;
- case 2: u.format2.collect_glyphs (glyphs); return;
- case 4: u.format4.collect_glyphs (glyphs); return;
- case 6: u.format6.collect_glyphs (glyphs); return;
- case 8: u.format8.collect_glyphs (glyphs); return;
- case 10: u.format10.collect_glyphs (glyphs); return;
+ case 0: hb_barrier (); u.format0.collect_glyphs (glyphs, num_glyphs); return;
+ case 2: hb_barrier (); u.format2.collect_glyphs (glyphs); return;
+ case 4: hb_barrier (); u.format4.collect_glyphs (glyphs); return;
+ case 6: hb_barrier (); u.format6.collect_glyphs (glyphs); return;
+ case 8: hb_barrier (); u.format8.collect_glyphs (glyphs); return;
+ case 10: hb_barrier (); u.format10.collect_glyphs (glyphs); return;
default:return;
}
}
@@ -514,12 +516,12 @@ struct Lookup
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format) {
- case 0: return_trace (u.format0.sanitize (c));
- case 2: return_trace (u.format2.sanitize (c));
- case 4: return_trace (u.format4.sanitize (c));
- case 6: return_trace (u.format6.sanitize (c));
- case 8: return_trace (u.format8.sanitize (c));
- case 10: return_trace (u.format10.sanitize (c));
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c));
+ case 4: hb_barrier (); return_trace (u.format4.sanitize (c));
+ case 6: hb_barrier (); return_trace (u.format6.sanitize (c));
+ case 8: hb_barrier (); return_trace (u.format8.sanitize (c));
+ case 10: hb_barrier (); return_trace (u.format10.sanitize (c));
default:return_trace (true);
}
}
@@ -529,11 +531,11 @@ struct Lookup
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format) {
- case 0: return_trace (u.format0.sanitize (c, base));
- case 2: return_trace (u.format2.sanitize (c, base));
- case 4: return_trace (u.format4.sanitize (c, base));
- case 6: return_trace (u.format6.sanitize (c, base));
- case 8: return_trace (u.format8.sanitize (c, base));
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c, base));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c, base));
+ case 4: hb_barrier (); return_trace (u.format4.sanitize (c, base));
+ case 6: hb_barrier (); return_trace (u.format6.sanitize (c, base));
+ case 8: hb_barrier (); return_trace (u.format8.sanitize (c, base));
case 10: return_trace (false); /* We don't support format10 here currently. */
default:return_trace (true);
}
@@ -927,7 +929,15 @@ struct StateTableDriver
machine (machine_),
num_glyphs (face_->get_num_glyphs ()) {}
- template <typename context_t, typename set_t = hb_set_digest_t>
+ template <typename context_t>
+ bool is_idempotent_on_all_out_of_bounds (context_t *c, hb_aat_apply_context_t *ac)
+ {
+ const auto entry = machine.get_entry (StateTableT::STATE_START_OF_TEXT, CLASS_OUT_OF_BOUNDS);
+ return !c->is_actionable (ac->buffer, this, entry) &&
+ machine.new_state (entry.newState) == StateTableT::STATE_START_OF_TEXT;
+ }
+
+ template <typename context_t>
void drive (context_t *c, hb_aat_apply_context_t *ac)
{
hb_buffer_t *buffer = ac->buffer;
@@ -1005,7 +1015,7 @@ struct StateTableDriver
const auto is_safe_to_break_extra = [&]()
{
/* 2c. */
- const auto wouldbe_entry = machine.get_entry(StateTableT::STATE_START_OF_TEXT, klass);
+ const auto &wouldbe_entry = machine.get_entry(StateTableT::STATE_START_OF_TEXT, klass);
/* 2c'. */
if (c->is_actionable (buffer, this, wouldbe_entry))
diff --git a/thirdparty/harfbuzz/src/hb-aat-layout-just-table.hh b/thirdparty/harfbuzz/src/hb-aat-layout-just-table.hh
index ee08da172e..9531b5e4b3 100644
--- a/thirdparty/harfbuzz/src/hb-aat-layout-just-table.hh
+++ b/thirdparty/harfbuzz/src/hb-aat-layout-just-table.hh
@@ -189,12 +189,12 @@ struct ActionSubrecord
switch (u.header.actionType)
{
- case 0: return_trace (u.decompositionAction.sanitize (c));
- case 1: return_trace (u.unconditionalAddGlyphAction.sanitize (c));
- case 2: return_trace (u.conditionalAddGlyphAction.sanitize (c));
- // case 3: return_trace (u.stretchGlyphAction.sanitize (c));
- case 4: return_trace (u.decompositionAction.sanitize (c));
- case 5: return_trace (u.decompositionAction.sanitize (c));
+ case 0: hb_barrier (); return_trace (u.decompositionAction.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.unconditionalAddGlyphAction.sanitize (c));
+ case 2: hb_barrier (); return_trace (u.conditionalAddGlyphAction.sanitize (c));
+ // case 3: hb_barrier (); return_trace (u.stretchGlyphAction.sanitize (c));
+ case 4: hb_barrier (); return_trace (u.decompositionAction.sanitize (c));
+ case 5: hb_barrier (); return_trace (u.decompositionAction.sanitize (c));
default: return_trace (true);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-aat-layout-kerx-table.hh b/thirdparty/harfbuzz/src/hb-aat-layout-kerx-table.hh
index 8d0d87af02..c01c31d735 100644
--- a/thirdparty/harfbuzz/src/hb-aat-layout-kerx-table.hh
+++ b/thirdparty/harfbuzz/src/hb-aat-layout-kerx-table.hh
@@ -107,10 +107,14 @@ struct KerxSubTableFormat0
TRACE_APPLY (this);
if (!c->plan->requested_kerning)
- return false;
+ return_trace (false);
if (header.coverage & header.Backwards)
- return false;
+ return_trace (false);
+
+ if (!(c->buffer_digest.may_have (c->left_set) &&
+ c->buffer_digest.may_have (c->right_set)))
+ return_trace (false);
accelerator_t accel (*this, c);
hb_kern_machine_t<accelerator_t> machine (accel, header.coverage & header.CrossStream);
@@ -367,6 +371,12 @@ struct KerxSubTableFormat1
driver_context_t dc (this, c);
StateTableDriver<Types, EntryData> driver (machine, c->font->face);
+
+ if (driver.is_idempotent_on_all_out_of_bounds (&dc, c) &&
+ !(c->buffer_digest.may_have (c->left_set) &&
+ c->buffer_digest.may_have (c->right_set)))
+ return_trace (false);
+
driver.drive (&dc, c);
return_trace (true);
@@ -425,10 +435,14 @@ struct KerxSubTableFormat2
TRACE_APPLY (this);
if (!c->plan->requested_kerning)
- return false;
+ return_trace (false);
if (header.coverage & header.Backwards)
- return false;
+ return_trace (false);
+
+ if (!(c->buffer_digest.may_have (c->left_set) &&
+ c->buffer_digest.may_have (c->right_set)))
+ return_trace (false);
accelerator_t accel (*this, c);
hb_kern_machine_t<accelerator_t> machine (accel, header.coverage & header.CrossStream);
@@ -635,6 +649,12 @@ struct KerxSubTableFormat4
driver_context_t dc (this, c);
StateTableDriver<Types, EntryData> driver (machine, c->font->face);
+
+ if (driver.is_idempotent_on_all_out_of_bounds (&dc, c) &&
+ !(c->buffer_digest.may_have (c->left_set) &&
+ c->buffer_digest.may_have (c->right_set)))
+ return_trace (false);
+
driver.drive (&dc, c);
return_trace (true);
@@ -710,10 +730,14 @@ struct KerxSubTableFormat6
TRACE_APPLY (this);
if (!c->plan->requested_kerning)
- return false;
+ return_trace (false);
if (header.coverage & header.Backwards)
- return false;
+ return_trace (false);
+
+ if (!(c->buffer_digest.may_have (c->left_set) &&
+ c->buffer_digest.may_have (c->right_set)))
+ return_trace (false);
accelerator_t accel (*this, c);
hb_kern_machine_t<accelerator_t> machine (accel, header.coverage & header.CrossStream);
@@ -919,6 +943,9 @@ struct KerxTable
{
if (st->get_type () == 1)
return true;
+
+ // TODO: What about format 4? What's this API used for anyway?
+
st = &StructAfter<SubTable> (*st);
}
return false;
@@ -962,6 +989,11 @@ struct KerxTable
{
c->buffer->unsafe_to_concat ();
+ if (c->buffer->len < HB_AAT_BUFFER_DIGEST_THRESHOLD)
+ c->buffer_digest = c->buffer->digest ();
+ else
+ c->buffer_digest = hb_set_digest_t::full ();
+
typedef typename T::SubTable SubTable;
bool ret = false;
diff --git a/thirdparty/harfbuzz/src/hb-aat-layout-morx-table.hh b/thirdparty/harfbuzz/src/hb-aat-layout-morx-table.hh
index 4a94e6a8ff..d31834402a 100644
--- a/thirdparty/harfbuzz/src/hb-aat-layout-morx-table.hh
+++ b/thirdparty/harfbuzz/src/hb-aat-layout-morx-table.hh
@@ -170,6 +170,11 @@ struct RearrangementSubtable
driver_context_t dc (this);
StateTableDriver<Types, EntryData> driver (machine, c->face);
+
+ if (driver.is_idempotent_on_all_out_of_bounds (&dc, c) &&
+ !c->buffer_digest.may_have (c->machine_glyph_set))
+ return_trace (false);
+
driver.drive (&dc, c);
return_trace (dc.ret);
@@ -267,6 +272,7 @@ struct ContextualSubtable
{
buffer->unsafe_to_break (mark, hb_min (buffer->idx + 1, buffer->len));
buffer->info[mark].codepoint = *replacement;
+ c->buffer_digest.add (*replacement);
if (has_glyph_classes)
_hb_glyph_info_set_glyph_props (&buffer->info[mark],
gdef.get_glyph_props (*replacement));
@@ -296,6 +302,7 @@ struct ContextualSubtable
if (replacement)
{
buffer->info[idx].codepoint = *replacement;
+ c->buffer_digest.add (*replacement);
if (has_glyph_classes)
_hb_glyph_info_set_glyph_props (&buffer->info[idx],
gdef.get_glyph_props (*replacement));
@@ -328,6 +335,11 @@ struct ContextualSubtable
driver_context_t dc (this, c);
StateTableDriver<Types, EntryData> driver (machine, c->face);
+
+ if (driver.is_idempotent_on_all_out_of_bounds (&dc, c) &&
+ !c->buffer_digest.may_have (c->machine_glyph_set))
+ return_trace (false);
+
driver.drive (&dc, c);
return_trace (dc.ret);
@@ -586,6 +598,11 @@ struct LigatureSubtable
driver_context_t dc (this, c);
StateTableDriver<Types, EntryData> driver (machine, c->face);
+
+ if (driver.is_idempotent_on_all_out_of_bounds (&dc, c) &&
+ !c->buffer_digest.may_have (c->machine_glyph_set))
+ return_trace (false);
+
driver.drive (&dc, c);
return_trace (dc.ret);
@@ -654,6 +671,7 @@ struct NoncontextualSubtable
if (replacement)
{
info[i].codepoint = *replacement;
+ c->buffer_digest.add (*replacement);
if (has_glyph_classes)
_hb_glyph_info_set_glyph_props (&info[i],
gdef.get_glyph_props (*replacement));
@@ -788,6 +806,9 @@ struct InsertionSubtable
if (unlikely (!buffer->copy_glyph ())) return;
/* TODO We ignore KashidaLike setting. */
if (unlikely (!buffer->replace_glyphs (0, count, glyphs))) return;
+ for (unsigned int i = 0; i < count; i++)
+ c->buffer_digest.add (glyphs[i]);
+ ret = true;
if (buffer->idx < buffer->len && !before)
buffer->skip_glyph ();
@@ -853,6 +874,11 @@ struct InsertionSubtable
driver_context_t dc (this, c);
StateTableDriver<Types, EntryData> driver (machine, c->face);
+
+ if (driver.is_idempotent_on_all_out_of_bounds (&dc, c) &&
+ !c->buffer_digest.may_have (c->machine_glyph_set))
+ return_trace (false);
+
driver.drive (&dc, c);
return_trace (dc.ret);
@@ -1036,7 +1062,8 @@ struct ChainSubtable
bool apply (hb_aat_apply_context_t *c) const
{
TRACE_APPLY (this);
- hb_sanitize_with_object_t with (&c->sanitizer, this);
+ // Disabled for https://github.com/harfbuzz/harfbuzz/issues/4873
+ //hb_sanitize_with_object_t with (&c->sanitizer, this);
return_trace (dispatch (c));
}
@@ -1049,7 +1076,8 @@ struct ChainSubtable
c->check_range (this, length)))
return_trace (false);
- hb_sanitize_with_object_t with (c, this);
+ // Disabled for https://github.com/harfbuzz/harfbuzz/issues/4873
+ //hb_sanitize_with_object_t with (c, this);
return_trace (dispatch (c));
}
@@ -1348,6 +1376,11 @@ struct mortmorx
c->buffer->unsafe_to_concat ();
+ if (c->buffer->len < HB_AAT_BUFFER_DIGEST_THRESHOLD)
+ c->buffer_digest = c->buffer->digest ();
+ else
+ c->buffer_digest = hb_set_digest_t::full ();
+
c->set_lookup_index (0);
const Chain<Types> *chain = &firstChain;
unsigned int count = chainCount;
diff --git a/thirdparty/harfbuzz/src/hb-aat-layout-opbd-table.hh b/thirdparty/harfbuzz/src/hb-aat-layout-opbd-table.hh
index 9840d3a554..dc75f5db57 100644
--- a/thirdparty/harfbuzz/src/hb-aat-layout-opbd-table.hh
+++ b/thirdparty/harfbuzz/src/hb-aat-layout-opbd-table.hh
@@ -133,8 +133,8 @@ struct opbd
{
switch (format)
{
- case 0: return u.format0.get_bounds (font, glyph_id, extents, this);
- case 1: return u.format1.get_bounds (font, glyph_id, extents, this);
+ case 0: hb_barrier (); return u.format0.get_bounds (font, glyph_id, extents, this);
+ case 1: hb_barrier (); return u.format1.get_bounds (font, glyph_id, extents, this);
default:return false;
}
}
@@ -148,8 +148,8 @@ struct opbd
switch (format)
{
- case 0: return_trace (u.format0.sanitize (c, this));
- case 1: return_trace (u.format1.sanitize (c, this));
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c, this));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c, this));
default:return_trace (true);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-buffer-verify.cc b/thirdparty/harfbuzz/src/hb-buffer-verify.cc
index 671d6eda8c..345f08d260 100644
--- a/thirdparty/harfbuzz/src/hb-buffer-verify.cc
+++ b/thirdparty/harfbuzz/src/hb-buffer-verify.cc
@@ -412,7 +412,7 @@ hb_buffer_t::verify (hb_buffer_t *text_buffer,
&len,
HB_BUFFER_SERIALIZE_FORMAT_TEXT,
HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS);
- buffer_verify_error (this, font, BUFFER_VERIFY_ERROR "text was: %s.", bytes.arrayZ);
+ buffer_verify_error (this, font, BUFFER_VERIFY_ERROR "text was: %s.", bytes.arrayZ ? bytes.arrayZ : "");
}
#endif
}
diff --git a/thirdparty/harfbuzz/src/hb-buffer.cc b/thirdparty/harfbuzz/src/hb-buffer.cc
index d621a7cc55..3fc869887e 100644
--- a/thirdparty/harfbuzz/src/hb-buffer.cc
+++ b/thirdparty/harfbuzz/src/hb-buffer.cc
@@ -271,6 +271,7 @@ hb_buffer_t::similar (const hb_buffer_t &src)
replacement = src.replacement;
invisible = src.invisible;
not_found = src.not_found;
+ not_found_variation_selector = src.not_found_variation_selector;
}
void
@@ -283,6 +284,7 @@ hb_buffer_t::reset ()
replacement = HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT;
invisible = 0;
not_found = 0;
+ not_found_variation_selector = HB_CODEPOINT_INVALID;
clear ();
}
@@ -705,6 +707,7 @@ DEFINE_NULL_INSTANCE (hb_buffer_t) =
HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT,
0, /* invisible */
0, /* not_found */
+ HB_CODEPOINT_INVALID, /* not_found_variation_selector */
HB_BUFFER_CONTENT_TYPE_INVALID,
@@ -1361,6 +1364,46 @@ hb_buffer_get_not_found_glyph (const hb_buffer_t *buffer)
}
/**
+ * hb_buffer_set_not_found_variation_selector_glyph:
+ * @buffer: An #hb_buffer_t
+ * @not_found_variation_selector: the not-found-variation-selector #hb_codepoint_t
+ *
+ * Sets the #hb_codepoint_t that replaces variation-selector characters not resolved
+ * in the font during shaping.
+ *
+ * The not-found-variation-selector glyph defaults to #HB_CODEPOINT_INVALID,
+ * in which case an unresolved variation-selector will be removed from the glyph
+ * string during shaping. This API allows for changing that and retaining a glyph,
+ * such that the situation can be detected by the client and handled accordingly
+ * (e.g. by using a different font).
+ *
+ * Since: 10.0.0
+ **/
+void
+hb_buffer_set_not_found_variation_selector_glyph (hb_buffer_t *buffer,
+ hb_codepoint_t not_found_variation_selector)
+{
+ buffer->not_found_variation_selector = not_found_variation_selector;
+}
+
+/**
+ * hb_buffer_get_not_found_variation_selector_glyph:
+ * @buffer: An #hb_buffer_t
+ *
+ * See hb_buffer_set_not_found_variation_selector_glyph().
+ *
+ * Return value:
+ * The @buffer not-found-variation-selector #hb_codepoint_t
+ *
+ * Since: 10.0.0
+ **/
+hb_codepoint_t
+hb_buffer_get_not_found_variation_selector_glyph (const hb_buffer_t *buffer)
+{
+ return buffer->not_found_variation_selector;
+}
+
+/**
* hb_buffer_set_random_state:
* @buffer: An #hb_buffer_t
* @state: the new random state
diff --git a/thirdparty/harfbuzz/src/hb-buffer.h b/thirdparty/harfbuzz/src/hb-buffer.h
index f75fe96b21..dd0edb9b7d 100644
--- a/thirdparty/harfbuzz/src/hb-buffer.h
+++ b/thirdparty/harfbuzz/src/hb-buffer.h
@@ -488,6 +488,13 @@ HB_EXTERN hb_codepoint_t
hb_buffer_get_not_found_glyph (const hb_buffer_t *buffer);
HB_EXTERN void
+hb_buffer_set_not_found_variation_selector_glyph (hb_buffer_t *buffer,
+ hb_codepoint_t not_found_variation_selector);
+
+HB_EXTERN hb_codepoint_t
+hb_buffer_get_not_found_variation_selector_glyph (const hb_buffer_t *buffer);
+
+HB_EXTERN void
hb_buffer_set_random_state (hb_buffer_t *buffer,
unsigned state);
diff --git a/thirdparty/harfbuzz/src/hb-buffer.hh b/thirdparty/harfbuzz/src/hb-buffer.hh
index 0a198722d6..2a6ad6128c 100644
--- a/thirdparty/harfbuzz/src/hb-buffer.hh
+++ b/thirdparty/harfbuzz/src/hb-buffer.hh
@@ -52,6 +52,7 @@ enum hb_buffer_scratch_flags_t {
HB_BUFFER_SCRATCH_FLAG_HAS_CGJ = 0x00000010u,
HB_BUFFER_SCRATCH_FLAG_HAS_GLYPH_FLAGS = 0x00000020u,
HB_BUFFER_SCRATCH_FLAG_HAS_BROKEN_SYLLABLE = 0x00000040u,
+ HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK= 0x00000080u,
/* Reserved for shapers' internal use. */
HB_BUFFER_SCRATCH_FLAG_SHAPER0 = 0x01000000u,
@@ -80,6 +81,7 @@ struct hb_buffer_t
hb_codepoint_t replacement; /* U+FFFD or something else. */
hb_codepoint_t invisible; /* 0 or something else. */
hb_codepoint_t not_found; /* 0 or something else. */
+ hb_codepoint_t not_found_variation_selector; /* HB_CODEPOINT_INVALID or something else. */
/*
* Buffer contents
diff --git a/thirdparty/harfbuzz/src/hb-cff-interp-common.hh b/thirdparty/harfbuzz/src/hb-cff-interp-common.hh
index 1d1f10f2bf..6ca7500af0 100644
--- a/thirdparty/harfbuzz/src/hb-cff-interp-common.hh
+++ b/thirdparty/harfbuzz/src/hb-cff-interp-common.hh
@@ -624,7 +624,6 @@ struct opset_t
} else {
/* invalid unknown operator */
env.clear_args ();
- env.set_error ();
}
break;
}
diff --git a/thirdparty/harfbuzz/src/hb-cff-interp-dict-common.hh b/thirdparty/harfbuzz/src/hb-cff-interp-dict-common.hh
index a08b10b5ff..b513a1e8c2 100644
--- a/thirdparty/harfbuzz/src/hb-cff-interp-dict-common.hh
+++ b/thirdparty/harfbuzz/src/hb-cff-interp-dict-common.hh
@@ -84,7 +84,7 @@ struct dict_opset_t : opset_t<number_t>
enum Nibble { DECIMAL=10, EXP_POS, EXP_NEG, RESERVED, NEG, END };
- char buf[32];
+ char buf[32] = {0};
unsigned char byte = 0;
for (unsigned i = 0, count = 0; count < ARRAY_LENGTH (buf); ++i, ++count)
{
diff --git a/thirdparty/harfbuzz/src/hb-common.h b/thirdparty/harfbuzz/src/hb-common.h
index 533de91562..1108545481 100644
--- a/thirdparty/harfbuzz/src/hb-common.h
+++ b/thirdparty/harfbuzz/src/hb-common.h
@@ -504,6 +504,13 @@ hb_language_matches (hb_language_t language,
* @HB_SCRIPT_MATH: `Zmth`, Since: 3.4.0
* @HB_SCRIPT_KAWI: `Kawi`, Since: 5.2.0
* @HB_SCRIPT_NAG_MUNDARI: `Nagm`, Since: 5.2.0
+ * @HB_SCRIPT_GARAY: `Gara`, Since: 10.0.0
+ * @HB_SCRIPT_GURUNG_KHEMA: `Gukh`, Since: 10.0.0
+ * @HB_SCRIPT_KIRAT_RAI: `Krai`, Since: 10.0.0
+ * @HB_SCRIPT_OL_ONAL: `Onao`, Since: 10.0.0
+ * @HB_SCRIPT_SUNUWAR: `Sunu`, Since: 10.0.0
+ * @HB_SCRIPT_TODHRI: `Todr`, Since: 10.0.0
+ * @HB_SCRIPT_TULU_TIGALARI: `Tutg`, Since: 10.0.0
* @HB_SCRIPT_INVALID: No script set
*
* Data type for scripts. Each #hb_script_t's value is an #hb_tag_t corresponding
@@ -731,6 +738,17 @@ typedef enum
HB_SCRIPT_KAWI = HB_TAG ('K','a','w','i'), /*15.0*/
HB_SCRIPT_NAG_MUNDARI = HB_TAG ('N','a','g','m'), /*15.0*/
+ /*
+ * Since 10.0.0
+ */
+ HB_SCRIPT_GARAY = HB_TAG ('G','a','r','a'), /*16.0*/
+ HB_SCRIPT_GURUNG_KHEMA = HB_TAG ('G','u','k','h'), /*16.0*/
+ HB_SCRIPT_KIRAT_RAI = HB_TAG ('K','r','a','i'), /*16.0*/
+ HB_SCRIPT_OL_ONAL = HB_TAG ('O','n','a','o'), /*16.0*/
+ HB_SCRIPT_SUNUWAR = HB_TAG ('S','u','n','u'), /*16.0*/
+ HB_SCRIPT_TODHRI = HB_TAG ('T','o','d','r'), /*16.0*/
+ HB_SCRIPT_TULU_TIGALARI = HB_TAG ('T','u','t','g'), /*16.0*/
+
/* No script set. */
HB_SCRIPT_INVALID = HB_TAG_NONE,
diff --git a/thirdparty/harfbuzz/src/hb-config.hh b/thirdparty/harfbuzz/src/hb-config.hh
index 816c55c7d3..79fee17517 100644
--- a/thirdparty/harfbuzz/src/hb-config.hh
+++ b/thirdparty/harfbuzz/src/hb-config.hh
@@ -118,6 +118,10 @@
#define HB_NO_VAR_COMPOSITES
#endif
+#ifdef HB_NO_VAR
+#define HB_NO_VAR_COMPOSITES
+#endif
+
#ifdef HB_DISABLE_DEPRECATED
#define HB_IF_NOT_DEPRECATED(x)
#else
diff --git a/thirdparty/harfbuzz/src/hb-coretext.cc b/thirdparty/harfbuzz/src/hb-coretext.cc
index a87cb5cd02..070b8be02f 100644
--- a/thirdparty/harfbuzz/src/hb-coretext.cc
+++ b/thirdparty/harfbuzz/src/hb-coretext.cc
@@ -48,6 +48,8 @@
/* https://developer.apple.com/documentation/coretext/1508745-ctfontcreatewithgraphicsfont */
#define HB_CORETEXT_DEFAULT_FONT_SIZE 12.f
+static CTFontRef create_ct_font (CGFontRef cg_font, CGFloat font_size);
+
static void
release_table_data (void *user_data)
{
@@ -76,6 +78,52 @@ _hb_cg_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data
release_table_data);
}
+static unsigned
+_hb_cg_get_table_tags (const hb_face_t *face HB_UNUSED,
+ unsigned int start_offset,
+ unsigned int *table_count,
+ hb_tag_t *table_tags,
+ void *user_data)
+{
+ CGFontRef cg_font = reinterpret_cast<CGFontRef> (user_data);
+
+ CTFontRef ct_font = create_ct_font (cg_font, (CGFloat) HB_CORETEXT_DEFAULT_FONT_SIZE);
+
+ auto arr = CTFontCopyAvailableTables (ct_font, kCTFontTableOptionNoOptions);
+
+ unsigned population = (unsigned) CFArrayGetCount (arr);
+ unsigned end_offset;
+
+ if (!table_count)
+ goto done;
+
+ if (unlikely (start_offset >= population))
+ {
+ *table_count = 0;
+ goto done;
+ }
+
+ end_offset = start_offset + *table_count;
+ if (unlikely (end_offset < start_offset))
+ {
+ *table_count = 0;
+ goto done;
+ }
+ end_offset= hb_min (end_offset, (unsigned) population);
+
+ *table_count = end_offset - start_offset;
+ for (unsigned i = start_offset; i < end_offset; i++)
+ {
+ CTFontTableTag tag = (CTFontTableTag)(uintptr_t) CFArrayGetValueAtIndex (arr, i);
+ table_tags[i - start_offset] = tag;
+ }
+
+done:
+ CFRelease (arr);
+ CFRelease (ct_font);
+ return population;
+}
+
static void
_hb_cg_font_release (void *data)
{
@@ -294,7 +342,9 @@ _hb_coretext_shaper_face_data_destroy (hb_coretext_face_data_t *data)
hb_face_t *
hb_coretext_face_create (CGFontRef cg_font)
{
- return hb_face_create_for_tables (_hb_cg_reference_table, CGFontRetain (cg_font), _hb_cg_font_release);
+ hb_face_t *face = hb_face_create_for_tables (_hb_cg_reference_table, CGFontRetain (cg_font), _hb_cg_font_release);
+ hb_face_set_get_table_tags_func (face, _hb_cg_get_table_tags, cg_font, nullptr);
+ return face;
}
/**
diff --git a/thirdparty/harfbuzz/src/hb-draw.hh b/thirdparty/harfbuzz/src/hb-draw.hh
index 25dee1261e..87d03a4882 100644
--- a/thirdparty/harfbuzz/src/hb-draw.hh
+++ b/thirdparty/harfbuzz/src/hb-draw.hh
@@ -232,7 +232,7 @@ struct hb_draw_session_t
funcs->close_path (draw_data, st);
}
- protected:
+ public:
float slant;
bool not_slanted;
hb_draw_funcs_t *funcs;
diff --git a/thirdparty/harfbuzz/src/hb-face-builder.cc b/thirdparty/harfbuzz/src/hb-face-builder.cc
index 84b14d28d6..beea89ed22 100644
--- a/thirdparty/harfbuzz/src/hb-face-builder.cc
+++ b/thirdparty/harfbuzz/src/hb-face-builder.cc
@@ -42,7 +42,7 @@
struct face_table_info_t
{
hb_blob_t* data;
- signed order;
+ unsigned order;
};
struct hb_face_builder_data_t
@@ -153,6 +153,50 @@ _hb_face_builder_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void
return hb_blob_reference (data->tables[tag].data);
}
+static unsigned
+_hb_face_builder_get_table_tags (const hb_face_t *face HB_UNUSED,
+ unsigned int start_offset,
+ unsigned int *table_count,
+ hb_tag_t *table_tags,
+ void *user_data)
+{
+ hb_face_builder_data_t *data = (hb_face_builder_data_t *) user_data;
+
+ unsigned population = data->tables.get_population ();
+
+ if (!table_count)
+ return population;
+
+ if (unlikely (start_offset >= population))
+ {
+ if (table_count)
+ *table_count = 0;
+ return population;
+ }
+
+ // Sort the tags.
+ hb_vector_t<hb_tag_t> sorted_tags;
+ data->tables.keys () | hb_sink (sorted_tags);
+ if (unlikely (sorted_tags.in_error ()))
+ {
+ // Not much to do...
+ }
+ sorted_tags.qsort ([] (const void* a, const void* b) {
+ return * (hb_tag_t *) a < * (hb_tag_t *) b ? -1 :
+ * (hb_tag_t *) a == * (hb_tag_t *) b ? 0 :
+ +1;
+ });
+
+ auto array = sorted_tags.as_array ().sub_array (start_offset, table_count);
+ auto out = hb_array (table_tags, *table_count);
+
+ + array.iter ()
+ | hb_sink (out)
+ ;
+
+ return population;
+}
+
/**
* hb_face_builder_create:
@@ -171,9 +215,16 @@ hb_face_builder_create ()
hb_face_builder_data_t *data = _hb_face_builder_data_create ();
if (unlikely (!data)) return hb_face_get_empty ();
- return hb_face_create_for_tables (_hb_face_builder_reference_table,
- data,
- _hb_face_builder_data_destroy);
+ hb_face_t *face = hb_face_create_for_tables (_hb_face_builder_reference_table,
+ data,
+ _hb_face_builder_data_destroy);
+
+ hb_face_set_get_table_tags_func (face,
+ _hb_face_builder_get_table_tags,
+ data,
+ nullptr);
+
+ return face;
}
/**
@@ -199,7 +250,7 @@ hb_face_builder_add_table (hb_face_t *face, hb_tag_t tag, hb_blob_t *blob)
hb_face_builder_data_t *data = (hb_face_builder_data_t *) face->user_data;
hb_blob_t* previous = data->tables.get (tag).data;
- if (!data->tables.set (tag, face_table_info_t {hb_blob_reference (blob), -1}))
+ if (!data->tables.set (tag, face_table_info_t {hb_blob_reference (blob), (unsigned) -1}))
{
hb_blob_destroy (blob);
return false;
diff --git a/thirdparty/harfbuzz/src/hb-face.cc b/thirdparty/harfbuzz/src/hb-face.cc
index e340710586..9b4af16f4d 100644
--- a/thirdparty/harfbuzz/src/hb-face.cc
+++ b/thirdparty/harfbuzz/src/hb-face.cc
@@ -90,10 +90,6 @@ DEFINE_NULL_INSTANCE (hb_face_t) =
{
HB_OBJECT_HEADER_STATIC,
- nullptr, /* reference_table_func */
- nullptr, /* user_data */
- nullptr, /* destroy */
-
0, /* index */
1000, /* upem */
0, /* num_glyphs */
@@ -110,8 +106,9 @@ DEFINE_NULL_INSTANCE (hb_face_t) =
*
* Variant of hb_face_create(), built for those cases where it is more
* convenient to provide data for individual tables instead of the whole font
- * data. With the caveat that hb_face_get_table_tags() does not currently work
- * with faces created this way.
+ * data. With the caveat that hb_face_get_table_tags() would not work
+ * with faces created this way. You can address that by calling the
+ * hb_face_set_get_table_tags_func() function and setting the appropriate callback.
*
* Creates a new face object from the specified @user_data and @reference_table_func,
* with the @destroy callback.
@@ -194,6 +191,22 @@ _hb_face_for_data_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void
return blob;
}
+static unsigned
+_hb_face_for_data_get_table_tags (const hb_face_t *face HB_UNUSED,
+ unsigned int start_offset,
+ unsigned int *table_count,
+ hb_tag_t *table_tags,
+ void *user_data)
+{
+ hb_face_for_data_closure_t *data = (hb_face_for_data_closure_t *) user_data;
+
+ const OT::OpenTypeFontFile &ot_file = *data->blob->as<OT::OpenTypeFontFile> ();
+ const OT::OpenTypeFontFace &ot_face = ot_file.get_face (data->index);
+
+ return ot_face.get_table_tags (start_offset, table_count, table_tags);
+}
+
+
/**
* hb_face_create:
* @blob: #hb_blob_t to work upon
@@ -240,6 +253,10 @@ hb_face_create (hb_blob_t *blob,
face = hb_face_create_for_tables (_hb_face_for_data_reference_table,
closure,
_hb_face_for_data_closure_destroy);
+ hb_face_set_get_table_tags_func (face,
+ _hb_face_for_data_get_table_tags,
+ closure,
+ nullptr);
face->index = index;
@@ -306,6 +323,9 @@ hb_face_destroy (hb_face_t *face)
face->data.fini ();
face->table.fini ();
+ if (face->get_table_tags_destroy)
+ face->get_table_tags_destroy (face->get_table_tags_user_data);
+
if (face->destroy)
face->destroy (face->user_data);
@@ -548,6 +568,37 @@ hb_face_get_glyph_count (const hb_face_t *face)
}
/**
+ * hb_face_set_get_table_tags_func:
+ * @face: A face object
+ * @func: (closure user_data) (destroy destroy) (scope notified): The table-tag-fetching function
+ * @user_data: A pointer to the user data, to be destroyed by @destroy when not needed anymore
+ * @destroy: (nullable): A callback to call when @func is not needed anymore
+ *
+ * Sets the table-tag-fetching function for the specified face object.
+ *
+ * Since: 10.0.0
+ */
+HB_EXTERN void
+hb_face_set_get_table_tags_func (hb_face_t *face,
+ hb_get_table_tags_func_t func,
+ void *user_data,
+ hb_destroy_func_t destroy)
+{
+ if (hb_object_is_immutable (face))
+ {
+ if (destroy)
+ destroy (user_data);
+ }
+
+ if (face->get_table_tags_destroy)
+ face->get_table_tags_destroy (face->get_table_tags_user_data);
+
+ face->get_table_tags_func = func;
+ face->get_table_tags_user_data = user_data;
+ face->get_table_tags_destroy = destroy;
+}
+
+/**
* hb_face_get_table_tags:
* @face: A face object
* @start_offset: The index of first table tag to retrieve
@@ -568,19 +619,14 @@ hb_face_get_table_tags (const hb_face_t *face,
unsigned int *table_count, /* IN/OUT */
hb_tag_t *table_tags /* OUT */)
{
- if (face->destroy != (hb_destroy_func_t) _hb_face_for_data_closure_destroy)
+ if (!face->get_table_tags_func)
{
if (table_count)
*table_count = 0;
return 0;
}
- hb_face_for_data_closure_t *data = (hb_face_for_data_closure_t *) face->user_data;
-
- const OT::OpenTypeFontFile &ot_file = *data->blob->as<OT::OpenTypeFontFile> ();
- const OT::OpenTypeFontFace &ot_face = ot_file.get_face (data->index);
-
- return ot_face.get_table_tags (start_offset, table_count, table_tags);
+ return face->get_table_tags_func (face, start_offset, table_count, table_tags, face->get_table_tags_user_data);
}
diff --git a/thirdparty/harfbuzz/src/hb-face.h b/thirdparty/harfbuzz/src/hb-face.h
index 2e54ccf13b..98d0afec26 100644
--- a/thirdparty/harfbuzz/src/hb-face.h
+++ b/thirdparty/harfbuzz/src/hb-face.h
@@ -135,6 +135,34 @@ hb_face_set_glyph_count (hb_face_t *face,
HB_EXTERN unsigned int
hb_face_get_glyph_count (const hb_face_t *face);
+
+/**
+ * hb_get_table_tags_func_t:
+ * @face: A face object
+ * @start_offset: The index of first table tag to retrieve
+ * @table_count: (inout): Input = the maximum number of table tags to return;
+ * Output = the actual number of table tags returned (may be zero)
+ * @table_tags: (out) (array length=table_count): The array of table tags found
+ * @user_data: User data pointer passed by the caller
+ *
+ * Callback function for hb_face_get_table_tags().
+ *
+ * Return value: Total number of tables, or zero if it is not possible to list
+ *
+ * Since: 10.0.0
+ */
+typedef unsigned int (*hb_get_table_tags_func_t) (const hb_face_t *face,
+ unsigned int start_offset,
+ unsigned int *table_count, /* IN/OUT */
+ hb_tag_t *table_tags /* OUT */,
+ void *user_data);
+
+HB_EXTERN void
+hb_face_set_get_table_tags_func (hb_face_t *face,
+ hb_get_table_tags_func_t func,
+ void *user_data,
+ hb_destroy_func_t destroy);
+
HB_EXTERN unsigned int
hb_face_get_table_tags (const hb_face_t *face,
unsigned int start_offset,
diff --git a/thirdparty/harfbuzz/src/hb-face.hh b/thirdparty/harfbuzz/src/hb-face.hh
index aff3ff0d07..6401568326 100644
--- a/thirdparty/harfbuzz/src/hb-face.hh
+++ b/thirdparty/harfbuzz/src/hb-face.hh
@@ -48,13 +48,17 @@ struct hb_face_t
{
hb_object_header_t header;
+ unsigned int index; /* Face index in a collection, zero-based. */
+ mutable hb_atomic_int_t upem; /* Units-per-EM. */
+ mutable hb_atomic_int_t num_glyphs; /* Number of glyphs. */
+
hb_reference_table_func_t reference_table_func;
void *user_data;
hb_destroy_func_t destroy;
- unsigned int index; /* Face index in a collection, zero-based. */
- mutable hb_atomic_int_t upem; /* Units-per-EM. */
- mutable hb_atomic_int_t num_glyphs; /* Number of glyphs. */
+ hb_get_table_tags_func_t get_table_tags_func;
+ void *get_table_tags_user_data;
+ hb_destroy_func_t get_table_tags_destroy;
hb_shaper_object_dataset_t<hb_face_t> data;/* Various shaper data. */
hb_ot_face_t table; /* All the face's tables. */
diff --git a/thirdparty/harfbuzz/src/hb-features.h b/thirdparty/harfbuzz/src/hb-features.h
deleted file mode 100644
index 9199864195..0000000000
--- a/thirdparty/harfbuzz/src/hb-features.h
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright © 2022 Red Hat, Inc.
- *
- * This is part of HarfBuzz, a text shaping library.
- *
- * Permission is hereby granted, without written agreement and without
- * license or royalty fees, to use, copy, modify, and distribute this
- * software and its documentation for any purpose, provided that the
- * above copyright notice and the following two paragraphs appear in
- * all copies of this software.
- *
- * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
- * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
- * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
- * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
- * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
- * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
- * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
- */
-
-#ifndef HB_FEATURES_H
-#define HB_FEATURES_H
-
-HB_BEGIN_DECLS
-
-/**
- * SECTION: hb-features
- * @title: hb-features
- * @short_description: Feature detection
- * @include: hb-features.h
- *
- * Macros for detecting optional HarfBuzz features at build time.
- **/
-
-/**
- * HB_HAS_CAIRO:
- *
- * Defined if Harfbuzz has been built with cairo support.
- */
-#
-
-/**
- * HB_HAS_CORETEXT:
- *
- * Defined if Harfbuzz has been built with CoreText support.
- */
-#undef HB_HAS_CORETEXT
-
-/**
- * HB_HAS_DIRECTWRITE:
- *
- * Defined if Harfbuzz has been built with DirectWrite support.
- */
-#undef HB_HAS_DIRECTWRITE
-
-/**
- * HB_HAS_FREETYPE:
- *
- * Defined if Harfbuzz has been built with Freetype support.
- */
-#define HB_HAS_FREETYPE 1
-
-/**
- * HB_HAS_GDI:
- *
- * Defined if Harfbuzz has been built with GDI support.
- */
-#undef HB_HAS_GDI
-
-/**
- * HB_HAS_GLIB:
- *
- * Defined if Harfbuzz has been built with GLib support.
- */
-#define HB_HAS_GLIB 1
-
-/**
- * HB_HAS_GOBJECT:
- *
- * Defined if Harfbuzz has been built with GObject support.
- */
-#undef HB_HAS_GOBJECT
-
-/**
- * HB_HAS_GRAPHITE:
- *
- * Defined if Harfbuzz has been built with Graphite support.
- */
-#undef HB_HAS_GRAPHITE
-
-/**
- * HB_HAS_ICU:
- *
- * Defined if Harfbuzz has been built with ICU support.
- */
-#undef HB_HAS_ICU
-
-/**
- * HB_HAS_UNISCRIBE:
- *
- * Defined if Harfbuzz has been built with Uniscribe support.
- */
-#undef HB_HAS_UNISCRIBE
-
-/**
- * HB_HAS_WASM:
- *
- * Defined if Harfbuzz has been built with WebAssembly support.
- */
-#undef HB_HAS_WASM
-
-
-HB_END_DECLS
-
-#endif /* HB_FEATURES_H */
diff --git a/thirdparty/harfbuzz/src/hb-ft-colr.hh b/thirdparty/harfbuzz/src/hb-ft-colr.hh
index 1afbbbb183..8766a2a2ce 100644
--- a/thirdparty/harfbuzz/src/hb-ft-colr.hh
+++ b/thirdparty/harfbuzz/src/hb-ft-colr.hh
@@ -108,7 +108,7 @@ struct hb_ft_paint_context_t
hb_map_t current_glyphs;
hb_map_t current_layers;
int depth_left = HB_MAX_NESTING_LEVEL;
- int edge_count = HB_COLRV1_MAX_EDGE_COUNT;
+ int edge_count = HB_MAX_GRAPH_EDGE_COUNT;
};
static unsigned
diff --git a/thirdparty/harfbuzz/src/hb-ft.cc b/thirdparty/harfbuzz/src/hb-ft.cc
index 3de4a6d5d4..f6d8bf4136 100644
--- a/thirdparty/harfbuzz/src/hb-ft.cc
+++ b/thirdparty/harfbuzz/src/hb-ft.cc
@@ -1104,6 +1104,45 @@ _hb_ft_reference_table (hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data
buffer, hb_free);
}
+static unsigned
+_hb_ft_get_table_tags (const hb_face_t *face HB_UNUSED,
+ unsigned int start_offset,
+ unsigned int *table_count,
+ hb_tag_t *table_tags,
+ void *user_data)
+{
+ FT_Face ft_face = (FT_Face) user_data;
+
+ FT_ULong population = 0;
+ FT_Sfnt_Table_Info (ft_face,
+ 0, // table_index; ignored
+ nullptr,
+ &population);
+
+ if (!table_count)
+ return population;
+ else
+ *table_count = 0;
+
+ if (unlikely (start_offset >= population))
+ return population;
+
+ unsigned end_offset = hb_min (start_offset + *table_count, (unsigned) population);
+ if (unlikely (end_offset < start_offset))
+ return population;
+
+ *table_count = end_offset - start_offset;
+ for (unsigned i = start_offset; i < end_offset; i++)
+ {
+ FT_ULong tag = 0, length;
+ FT_Sfnt_Table_Info (ft_face, i, &tag, &length);
+ table_tags[i - start_offset] = tag;
+ }
+
+ return population;
+}
+
+
/**
* hb_ft_face_create:
* @ft_face: (destroy destroy) (scope notified): FT_Face to work upon
@@ -1145,6 +1184,7 @@ hb_ft_face_create (FT_Face ft_face,
hb_blob_destroy (blob);
} else {
face = hb_face_create_for_tables (_hb_ft_reference_table, ft_face, destroy);
+ hb_face_set_get_table_tags_func (face, _hb_ft_get_table_tags, ft_face, nullptr);
}
hb_face_set_index (face, ft_face->face_index);
diff --git a/thirdparty/harfbuzz/src/hb-geometry.hh b/thirdparty/harfbuzz/src/hb-geometry.hh
new file mode 100644
index 0000000000..7777ff9ac3
--- /dev/null
+++ b/thirdparty/harfbuzz/src/hb-geometry.hh
@@ -0,0 +1,284 @@
+/*
+ * Copyright © 2022 Behdad Esfahbod
+ *
+ * This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+#ifndef HB_GEOMETRY_HH
+#define HB_GEOMETRY_HH
+
+#include "hb.hh"
+
+
+struct hb_extents_t
+{
+ hb_extents_t () {}
+ hb_extents_t (float xmin, float ymin, float xmax, float ymax) :
+ xmin (xmin), ymin (ymin), xmax (xmax), ymax (ymax) {}
+
+ bool is_empty () const { return xmin >= xmax || ymin >= ymax; }
+ bool is_void () const { return xmin > xmax; }
+
+ void union_ (const hb_extents_t &o)
+ {
+ xmin = hb_min (xmin, o.xmin);
+ ymin = hb_min (ymin, o.ymin);
+ xmax = hb_max (xmax, o.xmax);
+ ymax = hb_max (ymax, o.ymax);
+ }
+
+ void intersect (const hb_extents_t &o)
+ {
+ xmin = hb_max (xmin, o.xmin);
+ ymin = hb_max (ymin, o.ymin);
+ xmax = hb_min (xmax, o.xmax);
+ ymax = hb_min (ymax, o.ymax);
+ }
+
+ void
+ add_point (float x, float y)
+ {
+ if (unlikely (is_void ()))
+ {
+ xmin = xmax = x;
+ ymin = ymax = y;
+ }
+ else
+ {
+ xmin = hb_min (xmin, x);
+ ymin = hb_min (ymin, y);
+ xmax = hb_max (xmax, x);
+ ymax = hb_max (ymax, y);
+ }
+ }
+
+ float xmin = 0.f;
+ float ymin = 0.f;
+ float xmax = -1.f;
+ float ymax = -1.f;
+};
+
+struct hb_transform_t
+{
+ hb_transform_t () {}
+ hb_transform_t (float xx, float yx,
+ float xy, float yy,
+ float x0, float y0) :
+ xx (xx), yx (yx), xy (xy), yy (yy), x0 (x0), y0 (y0) {}
+
+ void multiply (const hb_transform_t &o)
+ {
+ /* Copied from cairo, with "o" being "a" there and "this" being "b" there. */
+ hb_transform_t r;
+
+ r.xx = o.xx * xx + o.yx * xy;
+ r.yx = o.xx * yx + o.yx * yy;
+
+ r.xy = o.xy * xx + o.yy * xy;
+ r.yy = o.xy * yx + o.yy * yy;
+
+ r.x0 = o.x0 * xx + o.y0 * xy + x0;
+ r.y0 = o.x0 * yx + o.y0 * yy + y0;
+
+ *this = r;
+ }
+
+ void transform_distance (float &dx, float &dy) const
+ {
+ float new_x = xx * dx + xy * dy;
+ float new_y = yx * dx + yy * dy;
+ dx = new_x;
+ dy = new_y;
+ }
+
+ void transform_point (float &x, float &y) const
+ {
+ transform_distance (x, y);
+ x += x0;
+ y += y0;
+ }
+
+ void transform_extents (hb_extents_t &extents) const
+ {
+ float quad_x[4], quad_y[4];
+
+ quad_x[0] = extents.xmin;
+ quad_y[0] = extents.ymin;
+ quad_x[1] = extents.xmin;
+ quad_y[1] = extents.ymax;
+ quad_x[2] = extents.xmax;
+ quad_y[2] = extents.ymin;
+ quad_x[3] = extents.xmax;
+ quad_y[3] = extents.ymax;
+
+ extents = hb_extents_t {};
+ for (unsigned i = 0; i < 4; i++)
+ {
+ transform_point (quad_x[i], quad_y[i]);
+ extents.add_point (quad_x[i], quad_y[i]);
+ }
+ }
+
+ void transform (const hb_transform_t &o) { multiply (o); }
+
+ void translate (float x, float y)
+ {
+ if (x == 0.f && y == 0.f)
+ return;
+
+ x0 += xx * x + xy * y;
+ y0 += yx * x + yy * y;
+ }
+
+ void scale (float scaleX, float scaleY)
+ {
+ if (scaleX == 1.f && scaleY == 1.f)
+ return;
+
+ xx *= scaleX;
+ yx *= scaleX;
+ xy *= scaleY;
+ yy *= scaleY;
+ }
+
+ void rotate (float rotation)
+ {
+ if (rotation == 0.f)
+ return;
+
+ // https://github.com/fonttools/fonttools/blob/f66ee05f71c8b57b5f519ee975e95edcd1466e14/Lib/fontTools/misc/transform.py#L240
+ rotation = rotation * HB_PI;
+ float c;
+ float s;
+#ifdef HAVE_SINCOSF
+ sincosf (rotation, &s, &c);
+#else
+ c = cosf (rotation);
+ s = sinf (rotation);
+#endif
+ auto other = hb_transform_t{c, s, -s, c, 0.f, 0.f};
+ transform (other);
+ }
+
+ void skew (float skewX, float skewY)
+ {
+ if (skewX == 0.f && skewY == 0.f)
+ return;
+
+ // https://github.com/fonttools/fonttools/blob/f66ee05f71c8b57b5f519ee975e95edcd1466e14/Lib/fontTools/misc/transform.py#L255
+ skewX = skewX * HB_PI;
+ skewY = skewY * HB_PI;
+ auto other = hb_transform_t{1.f,
+ skewY ? tanf (skewY) : 0.f,
+ skewX ? tanf (skewX) : 0.f,
+ 1.f,
+ 0.f, 0.f};
+ transform (other);
+ }
+
+ float xx = 1.f;
+ float yx = 0.f;
+ float xy = 0.f;
+ float yy = 1.f;
+ float x0 = 0.f;
+ float y0 = 0.f;
+};
+
+struct hb_bounds_t
+{
+ enum status_t {
+ UNBOUNDED,
+ BOUNDED,
+ EMPTY,
+ };
+
+ hb_bounds_t (status_t status) : status (status) {}
+ hb_bounds_t (const hb_extents_t &extents) :
+ status (extents.is_empty () ? EMPTY : BOUNDED), extents (extents) {}
+
+ void union_ (const hb_bounds_t &o)
+ {
+ if (o.status == UNBOUNDED)
+ status = UNBOUNDED;
+ else if (o.status == BOUNDED)
+ {
+ if (status == EMPTY)
+ *this = o;
+ else if (status == BOUNDED)
+ extents.union_ (o.extents);
+ }
+ }
+
+ void intersect (const hb_bounds_t &o)
+ {
+ if (o.status == EMPTY)
+ status = EMPTY;
+ else if (o.status == BOUNDED)
+ {
+ if (status == UNBOUNDED)
+ *this = o;
+ else if (status == BOUNDED)
+ {
+ extents.intersect (o.extents);
+ if (extents.is_empty ())
+ status = EMPTY;
+ }
+ }
+ }
+
+ status_t status;
+ hb_extents_t extents;
+};
+
+struct hb_transform_decomposed_t
+{
+ float translateX = 0;
+ float translateY = 0;
+ float rotation = 0; // in degrees, counter-clockwise
+ float scaleX = 1;
+ float scaleY = 1;
+ float skewX = 0; // in degrees, counter-clockwise
+ float skewY = 0; // in degrees, counter-clockwise
+ float tCenterX = 0;
+ float tCenterY = 0;
+
+ operator bool () const
+ {
+ return translateX || translateY ||
+ rotation ||
+ scaleX != 1 || scaleY != 1 ||
+ skewX || skewY ||
+ tCenterX || tCenterY;
+ }
+
+ hb_transform_t to_transform () const
+ {
+ hb_transform_t t;
+ t.translate (translateX + tCenterX, translateY + tCenterY);
+ t.rotate (rotation);
+ t.scale (scaleX, scaleY);
+ t.skew (-skewX, skewY);
+ t.translate (-tCenterX, -tCenterY);
+ return t;
+ }
+};
+
+
+#endif /* HB_GEOMETRY_HH */
diff --git a/thirdparty/harfbuzz/src/hb-iter.hh b/thirdparty/harfbuzz/src/hb-iter.hh
index 61e05180be..04d09940ae 100644
--- a/thirdparty/harfbuzz/src/hb-iter.hh
+++ b/thirdparty/harfbuzz/src/hb-iter.hh
@@ -324,6 +324,16 @@ struct hb_is_sink_of
(hb_is_source_of(Iter, Item) && Iter::is_sorted_iterator)
+struct
+{
+ template <typename Iterable,
+ hb_requires (hb_is_iterable (Iterable))>
+ unsigned operator () (const Iterable &_) const { return hb_len (hb_iter (_)); }
+
+ unsigned operator () (unsigned _) const { return _; }
+}
+HB_FUNCOBJ (hb_len_of);
+
/* Range-based 'for' for iterables. */
template <typename Iterable,
diff --git a/thirdparty/harfbuzz/src/hb-limits.hh b/thirdparty/harfbuzz/src/hb-limits.hh
index 7efc893eae..2cb14a4cc8 100644
--- a/thirdparty/harfbuzz/src/hb-limits.hh
+++ b/thirdparty/harfbuzz/src/hb-limits.hh
@@ -88,25 +88,24 @@
#define HB_MAX_LOOKUP_VISIT_COUNT 35000
#endif
-
-#ifndef HB_GLYF_VAR_COMPOSITE_MAX_AXES
-#define HB_GLYF_VAR_COMPOSITE_MAX_AXES 4096
+#ifndef HB_MAX_GRAPH_EDGE_COUNT
+#define HB_MAX_GRAPH_EDGE_COUNT 2048
#endif
-#ifndef HB_GLYF_MAX_POINTS
-#define HB_GLYF_MAX_POINTS 20000
+#ifndef HB_VAR_COMPOSITE_MAX_AXES
+#define HB_VAR_COMPOSITE_MAX_AXES 4096
#endif
-#ifndef HB_GLYF_MAX_EDGE_COUNT
-#define HB_GLYF_MAX_EDGE_COUNT 1024
+#ifndef HB_GLYF_MAX_POINTS
+#define HB_GLYF_MAX_POINTS 200000
#endif
#ifndef HB_CFF_MAX_OPS
#define HB_CFF_MAX_OPS 10000
#endif
-#ifndef HB_COLRV1_MAX_EDGE_COUNT
-#define HB_COLRV1_MAX_EDGE_COUNT 2048
+#ifndef HB_MAX_COMPOSITE_OPERATIONS_PER_GLYPH
+#define HB_MAX_COMPOSITE_OPERATIONS_PER_GLYPH 64
#endif
diff --git a/thirdparty/harfbuzz/src/hb-open-file.hh b/thirdparty/harfbuzz/src/hb-open-file.hh
index 1157ea46d0..6c98226f21 100644
--- a/thirdparty/harfbuzz/src/hb-open-file.hh
+++ b/thirdparty/harfbuzz/src/hb-open-file.hh
@@ -250,7 +250,7 @@ struct TTCHeader
{
switch (u.header.version.major) {
case 2: /* version 2 is compatible with version 1 */
- case 1: return u.version1.get_face_count ();
+ case 1: hb_barrier (); return u.version1.get_face_count ();
default:return 0;
}
}
@@ -258,7 +258,7 @@ struct TTCHeader
{
switch (u.header.version.major) {
case 2: /* version 2 is compatible with version 1 */
- case 1: return u.version1.get_face (i);
+ case 1: hb_barrier (); return u.version1.get_face (i);
default:return Null (OpenTypeFontFace);
}
}
@@ -270,7 +270,7 @@ struct TTCHeader
hb_barrier ();
switch (u.header.version.major) {
case 2: /* version 2 is compatible with version 1 */
- case 1: return_trace (u.version1.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.version1.sanitize (c));
default:return_trace (true);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-open-type.hh b/thirdparty/harfbuzz/src/hb-open-type.hh
index 9c11f14344..6655259b7a 100644
--- a/thirdparty/harfbuzz/src/hb-open-type.hh
+++ b/thirdparty/harfbuzz/src/hb-open-type.hh
@@ -132,6 +132,89 @@ struct HBUINT15 : HBUINT16
DEFINE_SIZE_STATIC (2);
};
+/* 32-bit unsigned integer with variable encoding. */
+struct HBUINT32VAR
+{
+ unsigned get_size () const
+ {
+ unsigned b0 = v[0];
+ if (b0 < 0x80)
+ return 1;
+ else if (b0 < 0xC0)
+ return 2;
+ else if (b0 < 0xE0)
+ return 3;
+ else if (b0 < 0xF0)
+ return 4;
+ else
+ return 5;
+ }
+
+ static unsigned get_size (uint32_t v)
+ {
+ if (v < 0x80)
+ return 1;
+ else if (v < 0x4000)
+ return 2;
+ else if (v < 0x200000)
+ return 3;
+ else if (v < 0x10000000)
+ return 4;
+ else
+ return 5;
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (c->check_range (v, 1) &&
+ hb_barrier () &&
+ c->check_range (v, get_size ()));
+ }
+
+ operator uint32_t () const
+ {
+ unsigned b0 = v[0];
+ if (b0 < 0x80)
+ return b0;
+ else if (b0 < 0xC0)
+ return ((b0 & 0x3F) << 8) | v[1];
+ else if (b0 < 0xE0)
+ return ((b0 & 0x1F) << 16) | (v[1] << 8) | v[2];
+ else if (b0 < 0xF0)
+ return ((b0 & 0x0F) << 24) | (v[1] << 16) | (v[2] << 8) | v[3];
+ else
+ return (v[1] << 24) | (v[2] << 16) | (v[3] << 8) | v[4];
+ }
+
+ static bool serialize (hb_serialize_context_t *c, uint32_t v)
+ {
+ unsigned len = get_size (v);
+
+ unsigned char *buf = c->allocate_size<unsigned char> (len, false);
+ if (unlikely (!buf))
+ return false;
+
+ unsigned char *p = buf + len;
+ for (unsigned i = 0; i < len; i++)
+ {
+ *--p = v & 0xFF;
+ v >>= 8;
+ }
+
+ if (len > 1)
+ buf[0] |= ((1 << (len - 1)) - 1) << (9 - len);
+
+ return true;
+ }
+
+ protected:
+ unsigned char v[5];
+
+ public:
+ DEFINE_SIZE_MIN (1);
+};
+
/* 16-bit signed integer (HBINT16) that describes a quantity in FUnits. */
typedef HBINT16 FWORD;
@@ -149,6 +232,7 @@ struct HBFixed : Type
operator signed () const = delete;
operator unsigned () const = delete;
+ explicit operator float () const { return to_float (); }
typename Type::type to_int () const { return Type::v; }
void set_int (typename Type::type i ) { Type::v = i; }
float to_float (float offset = 0) const { return ((int32_t) Type::v + offset) / shift; }
@@ -570,7 +654,7 @@ struct UnsizedListOfOffset16To : UnsizedArray16OfOffsetTo<Type, OffsetType, Base
unsigned int i = (unsigned int) i_;
const OffsetTo<Type, OffsetType, BaseType, has_null> *p = &this->arrayZ[i];
if (unlikely ((const void *) p < (const void *) this->arrayZ)) return Null (Type); /* Overflowed. */
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return this+*p;
}
Type& operator [] (int i_)
@@ -578,7 +662,7 @@ struct UnsizedListOfOffset16To : UnsizedArray16OfOffsetTo<Type, OffsetType, Base
unsigned int i = (unsigned int) i_;
const OffsetTo<Type, OffsetType, BaseType, has_null> *p = &this->arrayZ[i];
if (unlikely ((const void *) p < (const void *) this->arrayZ)) return Crap (Type); /* Overflowed. */
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return this+*p;
}
@@ -629,14 +713,14 @@ struct ArrayOf
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= len)) return Null (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return arrayZ[i];
}
Type& operator [] (int i_)
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= len)) return Crap (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return arrayZ[i];
}
@@ -756,6 +840,7 @@ template <typename Type> using Array32Of = ArrayOf<Type, HBUINT32>;
using PString = ArrayOf<HBUINT8, HBUINT8>;
/* Array of Offset's */
+template <typename Type> using Array8OfOffset24To = ArrayOf<OffsetTo<Type, HBUINT24>, HBUINT8>;
template <typename Type> using Array16OfOffset16To = ArrayOf<OffsetTo<Type, HBUINT16>, HBUINT16>;
template <typename Type> using Array16OfOffset32To = ArrayOf<OffsetTo<Type, HBUINT32>, HBUINT16>;
template <typename Type> using Array32OfOffset32To = ArrayOf<OffsetTo<Type, HBUINT32>, HBUINT32>;
@@ -768,14 +853,14 @@ struct List16OfOffsetTo : ArrayOf<OffsetTo<Type, OffsetType>, HBUINT16>
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= this->len)) return Null (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return this+this->arrayZ[i];
}
const Type& operator [] (int i_)
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= this->len)) return Crap (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return this+this->arrayZ[i];
}
@@ -813,14 +898,14 @@ struct HeadlessArrayOf
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= lenP1 || !i)) return Null (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return arrayZ[i-1];
}
Type& operator [] (int i_)
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= lenP1 || !i)) return Crap (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return arrayZ[i-1];
}
unsigned int get_size () const
@@ -907,14 +992,14 @@ struct ArrayOfM1
{
unsigned int i = (unsigned int) i_;
if (unlikely (i > lenM1)) return Null (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return arrayZ[i];
}
Type& operator [] (int i_)
{
unsigned int i = (unsigned int) i_;
if (unlikely (i > lenM1)) return Crap (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return arrayZ[i];
}
unsigned int get_size () const
@@ -1099,14 +1184,14 @@ struct VarSizedBinSearchArrayOf
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= get_length ())) return Null (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return StructAtOffset<Type> (&bytesZ, i * header.unitSize);
}
Type& operator [] (int i_)
{
unsigned int i = (unsigned int) i_;
if (unlikely (i >= get_length ())) return Crap (Type);
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return StructAtOffset<Type> (&bytesZ, i * header.unitSize);
}
unsigned int get_length () const
@@ -1163,6 +1248,638 @@ struct VarSizedBinSearchArrayOf
};
+/* CFF INDEX */
+
+template <typename COUNT>
+struct CFFIndex
+{
+ unsigned int offset_array_size () const
+ { return offSize * (count + 1); }
+
+ template <typename Iterable,
+ hb_requires (hb_is_iterable (Iterable))>
+ bool serialize (hb_serialize_context_t *c,
+ const Iterable &iterable,
+ const unsigned *p_data_size = nullptr,
+ unsigned min_off_size = 0)
+ {
+ TRACE_SERIALIZE (this);
+ unsigned data_size;
+ if (p_data_size)
+ data_size = *p_data_size;
+ else
+ total_size (iterable, &data_size);
+
+ auto it = hb_iter (iterable);
+ if (unlikely (!serialize_header (c, +it, data_size, min_off_size))) return_trace (false);
+ unsigned char *ret = c->allocate_size<unsigned char> (data_size, false);
+ if (unlikely (!ret)) return_trace (false);
+ for (const auto &_ : +it)
+ {
+ unsigned len = _.length;
+ if (!len)
+ continue;
+ if (len <= 1)
+ {
+ *ret++ = *_.arrayZ;
+ continue;
+ }
+ hb_memcpy (ret, _.arrayZ, len);
+ ret += len;
+ }
+ return_trace (true);
+ }
+
+ template <typename Iterator,
+ hb_requires (hb_is_iterator (Iterator))>
+ bool serialize_header (hb_serialize_context_t *c,
+ Iterator it,
+ unsigned data_size,
+ unsigned min_off_size = 0)
+ {
+ TRACE_SERIALIZE (this);
+
+ unsigned off_size = (hb_bit_storage (data_size + 1) + 7) / 8;
+ off_size = hb_max(min_off_size, off_size);
+
+ /* serialize CFFIndex header */
+ if (unlikely (!c->extend_min (this))) return_trace (false);
+ this->count = hb_len (it);
+ if (!this->count) return_trace (true);
+ if (unlikely (!c->extend (this->offSize))) return_trace (false);
+ this->offSize = off_size;
+ if (unlikely (!c->allocate_size<HBUINT8> (off_size * (this->count + 1), false)))
+ return_trace (false);
+
+ /* serialize indices */
+ unsigned int offset = 1;
+ if (HB_OPTIMIZE_SIZE_VAL)
+ {
+ unsigned int i = 0;
+ for (const auto &_ : +it)
+ {
+ set_offset_at (i++, offset);
+ offset += hb_len_of (_);
+ }
+ set_offset_at (i, offset);
+ }
+ else
+ switch (off_size)
+ {
+ case 1:
+ {
+ HBUINT8 *p = (HBUINT8 *) offsets;
+ for (const auto &_ : +it)
+ {
+ *p++ = offset;
+ offset += hb_len_of (_);
+ }
+ *p = offset;
+ }
+ break;
+ case 2:
+ {
+ HBUINT16 *p = (HBUINT16 *) offsets;
+ for (const auto &_ : +it)
+ {
+ *p++ = offset;
+ offset += hb_len_of (_);
+ }
+ *p = offset;
+ }
+ break;
+ case 3:
+ {
+ HBUINT24 *p = (HBUINT24 *) offsets;
+ for (const auto &_ : +it)
+ {
+ *p++ = offset;
+ offset += hb_len_of (_);
+ }
+ *p = offset;
+ }
+ break;
+ case 4:
+ {
+ HBUINT32 *p = (HBUINT32 *) offsets;
+ for (const auto &_ : +it)
+ {
+ *p++ = offset;
+ offset += hb_len_of (_);
+ }
+ *p = offset;
+ }
+ break;
+ default:
+ break;
+ }
+
+ assert (offset == data_size + 1);
+ return_trace (true);
+ }
+
+ template <typename Iterable,
+ hb_requires (hb_is_iterable (Iterable))>
+ static unsigned total_size (const Iterable &iterable, unsigned *data_size = nullptr, unsigned min_off_size = 0)
+ {
+ auto it = + hb_iter (iterable);
+ if (!it)
+ {
+ if (data_size) *data_size = 0;
+ return min_size;
+ }
+
+ unsigned total = 0;
+ for (const auto &_ : +it)
+ total += hb_len_of (_);
+
+ if (data_size) *data_size = total;
+
+ unsigned off_size = (hb_bit_storage (total + 1) + 7) / 8;
+ off_size = hb_max(min_off_size, off_size);
+
+ return min_size + HBUINT8::static_size + (hb_len (it) + 1) * off_size + total;
+ }
+
+ void set_offset_at (unsigned int index, unsigned int offset)
+ {
+ assert (index <= count);
+
+ unsigned int size = offSize;
+ const HBUINT8 *p = offsets;
+ switch (size)
+ {
+ case 1: ((HBUINT8 *) p)[index] = offset; break;
+ case 2: ((HBUINT16 *) p)[index] = offset; break;
+ case 3: ((HBUINT24 *) p)[index] = offset; break;
+ case 4: ((HBUINT32 *) p)[index] = offset; break;
+ default: return;
+ }
+ }
+
+ private:
+ unsigned int offset_at (unsigned int index) const
+ {
+ assert (index <= count);
+
+ unsigned int size = offSize;
+ const HBUINT8 *p = offsets;
+ switch (size)
+ {
+ case 1: return ((HBUINT8 *) p)[index];
+ case 2: return ((HBUINT16 *) p)[index];
+ case 3: return ((HBUINT24 *) p)[index];
+ case 4: return ((HBUINT32 *) p)[index];
+ default: return 0;
+ }
+ }
+
+ const unsigned char *data_base () const
+ { return (const unsigned char *) this + min_size + offSize.static_size - 1 + offset_array_size (); }
+ public:
+
+ hb_ubytes_t operator [] (unsigned int index) const
+ {
+ if (unlikely (index >= count)) return hb_ubytes_t ();
+ hb_barrier ();
+ unsigned offset0 = offset_at (index);
+ unsigned offset1 = offset_at (index + 1);
+ if (unlikely (offset1 < offset0 || offset1 > offset_at (count)))
+ return hb_ubytes_t ();
+ return hb_ubytes_t (data_base () + offset0, offset1 - offset0);
+ }
+
+ unsigned int get_size () const
+ {
+ if (count)
+ return min_size + offSize.static_size + offset_array_size () + (offset_at (count) - 1);
+ return min_size; /* empty CFFIndex contains count only */
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (likely (c->check_struct (this) &&
+ hb_barrier () &&
+ (count == 0 || /* empty INDEX */
+ (count < count + 1u &&
+ c->check_struct (&offSize) && offSize >= 1 && offSize <= 4 &&
+ c->check_array (offsets, offSize, count + 1u) &&
+ c->check_range (data_base (), offset_at (count))))));
+ }
+
+ public:
+ COUNT count; /* Number of object data. Note there are (count+1) offsets */
+ private:
+ HBUINT8 offSize; /* The byte size of each offset in the offsets array. */
+ HBUINT8 offsets[HB_VAR_ARRAY];
+ /* The array of (count + 1) offsets into objects array (1-base). */
+ /* HBUINT8 data[HB_VAR_ARRAY]; Object data */
+ public:
+ DEFINE_SIZE_MIN (COUNT::static_size);
+};
+typedef CFFIndex<HBUINT16> CFF1Index;
+typedef CFFIndex<HBUINT32> CFF2Index;
+
+
+/* TupleValues */
+struct TupleValues
+{
+ enum packed_value_flag_t
+ {
+ VALUES_ARE_ZEROS = 0x80,
+ VALUES_ARE_BYTES = 0x00,
+ VALUES_ARE_WORDS = 0x40,
+ VALUES_ARE_LONGS = 0xC0,
+ VALUES_SIZE_MASK = 0xC0,
+ VALUE_RUN_COUNT_MASK = 0x3F
+ };
+
+ static unsigned compile (hb_array_t<const int> values, /* IN */
+ hb_array_t<unsigned char> encoded_bytes /* OUT */)
+ {
+ unsigned num_values = values.length;
+ unsigned encoded_len = 0;
+ unsigned i = 0;
+ while (i < num_values)
+ {
+ int val = values.arrayZ[i];
+ if (val == 0)
+ encoded_len += encode_value_run_as_zeroes (i, encoded_bytes.sub_array (encoded_len), values);
+ else if (val >= -128 && val <= 127)
+ encoded_len += encode_value_run_as_bytes (i, encoded_bytes.sub_array (encoded_len), values);
+ else if (val >= -32768 && val <= 32767)
+ encoded_len += encode_value_run_as_words (i, encoded_bytes.sub_array (encoded_len), values);
+ else
+ encoded_len += encode_value_run_as_longs (i, encoded_bytes.sub_array (encoded_len), values);
+ }
+ return encoded_len;
+ }
+
+ static unsigned encode_value_run_as_zeroes (unsigned& i,
+ hb_array_t<unsigned char> encoded_bytes,
+ hb_array_t<const int> values)
+ {
+ unsigned num_values = values.length;
+ unsigned run_length = 0;
+ auto it = encoded_bytes.iter ();
+ unsigned encoded_len = 0;
+ while (i < num_values && values.arrayZ[i] == 0)
+ {
+ i++;
+ run_length++;
+ }
+
+ while (run_length >= 64)
+ {
+ *it++ = char (VALUES_ARE_ZEROS | 63);
+ run_length -= 64;
+ encoded_len++;
+ }
+
+ if (run_length)
+ {
+ *it++ = char (VALUES_ARE_ZEROS | (run_length - 1));
+ encoded_len++;
+ }
+ return encoded_len;
+ }
+
+ static unsigned encode_value_run_as_bytes (unsigned &i,
+ hb_array_t<unsigned char> encoded_bytes,
+ hb_array_t<const int> values)
+ {
+ unsigned start = i;
+ unsigned num_values = values.length;
+ while (i < num_values)
+ {
+ int val = values.arrayZ[i];
+ if (val > 127 || val < -128)
+ break;
+
+ /* from fonttools: if there're 2 or more zeros in a sequence,
+ * it is better to start a new run to save bytes. */
+ if (val == 0 && i + 1 < num_values && values.arrayZ[i+1] == 0)
+ break;
+
+ i++;
+ }
+ unsigned run_length = i - start;
+
+ unsigned encoded_len = 0;
+ auto it = encoded_bytes.iter ();
+
+ while (run_length >= 64)
+ {
+ *it++ = (VALUES_ARE_BYTES | 63);
+ encoded_len++;
+
+ for (unsigned j = 0; j < 64; j++)
+ {
+ *it++ = static_cast<char> (values.arrayZ[start + j]);
+ encoded_len++;
+ }
+
+ start += 64;
+ run_length -= 64;
+ }
+
+ if (run_length)
+ {
+ *it++ = (VALUES_ARE_BYTES | (run_length - 1));
+ encoded_len++;
+
+ while (start < i)
+ {
+ *it++ = static_cast<char> (values.arrayZ[start++]);
+ encoded_len++;
+ }
+ }
+
+ return encoded_len;
+ }
+
+ static unsigned encode_value_run_as_words (unsigned &i,
+ hb_array_t<unsigned char> encoded_bytes,
+ hb_array_t<const int> values)
+ {
+ unsigned start = i;
+ unsigned num_values = values.length;
+ while (i < num_values)
+ {
+ int val = values.arrayZ[i];
+
+ /* start a new run for a single zero value*/
+ if (val == 0) break;
+
+ /* from fonttools: continue word-encoded run if there's only one
+ * single value in the range [-128, 127] because it is more compact.
+ * Only start a new run when there're 2 continuous such values. */
+ if (val >= -128 && val <= 127 &&
+ i + 1 < num_values &&
+ values.arrayZ[i+1] >= -128 && values.arrayZ[i+1] <= 127)
+ break;
+
+ i++;
+ }
+
+ unsigned run_length = i - start;
+ auto it = encoded_bytes.iter ();
+ unsigned encoded_len = 0;
+ while (run_length >= 64)
+ {
+ *it++ = (VALUES_ARE_WORDS | 63);
+ encoded_len++;
+
+ for (unsigned j = 0; j < 64; j++)
+ {
+ int16_t value_val = values.arrayZ[start + j];
+ *it++ = static_cast<char> (value_val >> 8);
+ *it++ = static_cast<char> (value_val & 0xFF);
+
+ encoded_len += 2;
+ }
+
+ start += 64;
+ run_length -= 64;
+ }
+
+ if (run_length)
+ {
+ *it++ = (VALUES_ARE_WORDS | (run_length - 1));
+ encoded_len++;
+ while (start < i)
+ {
+ int16_t value_val = values.arrayZ[start++];
+ *it++ = static_cast<char> (value_val >> 8);
+ *it++ = static_cast<char> (value_val & 0xFF);
+
+ encoded_len += 2;
+ }
+ }
+ return encoded_len;
+ }
+
+ static unsigned encode_value_run_as_longs (unsigned &i,
+ hb_array_t<unsigned char> encoded_bytes,
+ hb_array_t<const int> values)
+ {
+ unsigned start = i;
+ unsigned num_values = values.length;
+ while (i < num_values)
+ {
+ int val = values.arrayZ[i];
+
+ if (val >= -32768 && val <= 32767)
+ break;
+
+ i++;
+ }
+
+ unsigned run_length = i - start;
+ auto it = encoded_bytes.iter ();
+ unsigned encoded_len = 0;
+ while (run_length >= 64)
+ {
+ *it++ = (VALUES_ARE_LONGS | 63);
+ encoded_len++;
+
+ for (unsigned j = 0; j < 64; j++)
+ {
+ int32_t value_val = values.arrayZ[start + j];
+ *it++ = static_cast<char> (value_val >> 24);
+ *it++ = static_cast<char> (value_val >> 16);
+ *it++ = static_cast<char> (value_val >> 8);
+ *it++ = static_cast<char> (value_val & 0xFF);
+
+ encoded_len += 4;
+ }
+
+ start += 64;
+ run_length -= 64;
+ }
+
+ if (run_length)
+ {
+ *it++ = (VALUES_ARE_LONGS | (run_length - 1));
+ encoded_len++;
+ while (start < i)
+ {
+ int32_t value_val = values.arrayZ[start++];
+ *it++ = static_cast<char> (value_val >> 24);
+ *it++ = static_cast<char> (value_val >> 16);
+ *it++ = static_cast<char> (value_val >> 8);
+ *it++ = static_cast<char> (value_val & 0xFF);
+
+ encoded_len += 4;
+ }
+ }
+ return encoded_len;
+ }
+
+ template <typename T>
+ static bool decompile (const HBUINT8 *&p /* IN/OUT */,
+ hb_vector_t<T> &values /* IN/OUT */,
+ const HBUINT8 *end,
+ bool consume_all = false)
+ {
+ unsigned i = 0;
+ unsigned count = consume_all ? UINT_MAX : values.length;
+ if (consume_all)
+ values.alloc ((end - p) / 2);
+ while (i < count)
+ {
+ if (unlikely (p + 1 > end)) return consume_all;
+ unsigned control = *p++;
+ unsigned run_count = (control & VALUE_RUN_COUNT_MASK) + 1;
+ if (consume_all)
+ {
+ if (unlikely (!values.resize (values.length + run_count, false)))
+ return false;
+ }
+ unsigned stop = i + run_count;
+ if (unlikely (stop > count)) return false;
+ if ((control & VALUES_SIZE_MASK) == VALUES_ARE_ZEROS)
+ {
+ for (; i < stop; i++)
+ values.arrayZ[i] = 0;
+ }
+ else if ((control & VALUES_SIZE_MASK) == VALUES_ARE_WORDS)
+ {
+ if (unlikely (p + run_count * HBINT16::static_size > end)) return false;
+ for (; i < stop; i++)
+ {
+ values.arrayZ[i] = * (const HBINT16 *) p;
+ p += HBINT16::static_size;
+ }
+ }
+ else if ((control & VALUES_SIZE_MASK) == VALUES_ARE_LONGS)
+ {
+ if (unlikely (p + run_count * HBINT32::static_size > end)) return false;
+ for (; i < stop; i++)
+ {
+ values.arrayZ[i] = * (const HBINT32 *) p;
+ p += HBINT32::static_size;
+ }
+ }
+ else if ((control & VALUES_SIZE_MASK) == VALUES_ARE_BYTES)
+ {
+ if (unlikely (p + run_count > end)) return false;
+ for (; i < stop; i++)
+ {
+ values.arrayZ[i] = * (const HBINT8 *) p++;
+ }
+ }
+ }
+ return true;
+ }
+
+ struct iter_t : hb_iter_with_fallback_t<iter_t, int>
+ {
+ iter_t (const unsigned char *p_, unsigned len_)
+ : p (p_), end (p_ + len_)
+ { if (ensure_run ()) read_value (); }
+
+ private:
+ const unsigned char *p;
+ const unsigned char * const end;
+ int current_value = 0;
+ signed run_count = 0;
+ unsigned width = 0;
+
+ bool ensure_run ()
+ {
+ if (likely (run_count > 0)) return true;
+
+ if (unlikely (p >= end))
+ {
+ run_count = 0;
+ current_value = 0;
+ return false;
+ }
+
+ unsigned control = *p++;
+ run_count = (control & VALUE_RUN_COUNT_MASK) + 1;
+ width = control & VALUES_SIZE_MASK;
+ switch (width)
+ {
+ case VALUES_ARE_ZEROS: width = 0; break;
+ case VALUES_ARE_BYTES: width = HBINT8::static_size; break;
+ case VALUES_ARE_WORDS: width = HBINT16::static_size; break;
+ case VALUES_ARE_LONGS: width = HBINT32::static_size; break;
+ default: assert (false);
+ }
+
+ if (unlikely (p + run_count * width > end))
+ {
+ run_count = 0;
+ current_value = 0;
+ return false;
+ }
+
+ return true;
+ }
+ void read_value ()
+ {
+ switch (width)
+ {
+ case 0: current_value = 0; break;
+ case 1: current_value = * (const HBINT8 *) p; break;
+ case 2: current_value = * (const HBINT16 *) p; break;
+ case 4: current_value = * (const HBINT32 *) p; break;
+ }
+ p += width;
+ }
+
+ public:
+
+ typedef int __item_t__;
+ __item_t__ __item__ () const
+ { return current_value; }
+
+ bool __more__ () const { return run_count || p < end; }
+ void __next__ ()
+ {
+ run_count--;
+ if (unlikely (!ensure_run ()))
+ return;
+ read_value ();
+ }
+ void __forward__ (unsigned n)
+ {
+ if (unlikely (!ensure_run ()))
+ return;
+ while (n)
+ {
+ unsigned i = hb_min (n, (unsigned) run_count);
+ run_count -= i;
+ n -= i;
+ p += (i - 1) * width;
+ if (unlikely (!ensure_run ()))
+ return;
+ read_value ();
+ }
+ }
+ bool operator != (const iter_t& o) const
+ { return p != o.p || run_count != o.run_count; }
+ iter_t __end__ () const
+ {
+ iter_t it (end, 0);
+ return it;
+ }
+ };
+};
+
+struct TupleList : CFF2Index
+{
+ TupleValues::iter_t operator [] (unsigned i) const
+ {
+ auto bytes = CFF2Index::operator [] (i);
+ return TupleValues::iter_t (bytes.arrayZ, bytes.length);
+ }
+};
+
+
} /* namespace OT */
diff --git a/thirdparty/harfbuzz/src/hb-ot-cff-common.hh b/thirdparty/harfbuzz/src/hb-ot-cff-common.hh
index c7c3264c08..b49c0be517 100644
--- a/thirdparty/harfbuzz/src/hb-ot-cff-common.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-cff-common.hh
@@ -68,247 +68,6 @@ using str_buff_t = hb_vector_t<unsigned char>;
using str_buff_vec_t = hb_vector_t<str_buff_t>;
using glyph_to_sid_map_t = hb_vector_t<code_pair_t>;
-struct length_f_t
-{
- template <typename Iterable,
- hb_requires (hb_is_iterable (Iterable))>
- unsigned operator () (const Iterable &_) const { return hb_len (hb_iter (_)); }
-
- unsigned operator () (unsigned _) const { return _; }
-}
-HB_FUNCOBJ (length_f);
-
-/* CFF INDEX */
-template <typename COUNT>
-struct CFFIndex
-{
- unsigned int offset_array_size () const
- { return offSize * (count + 1); }
-
- template <typename Iterable,
- hb_requires (hb_is_iterable (Iterable))>
- bool serialize (hb_serialize_context_t *c,
- const Iterable &iterable,
- const unsigned *p_data_size = nullptr,
- unsigned min_off_size = 0)
- {
- TRACE_SERIALIZE (this);
- unsigned data_size;
- if (p_data_size)
- data_size = *p_data_size;
- else
- total_size (iterable, &data_size);
-
- auto it = hb_iter (iterable);
- if (unlikely (!serialize_header (c, +it, data_size, min_off_size))) return_trace (false);
- unsigned char *ret = c->allocate_size<unsigned char> (data_size, false);
- if (unlikely (!ret)) return_trace (false);
- for (const auto &_ : +it)
- {
- unsigned len = _.length;
- if (!len)
- continue;
- if (len <= 1)
- {
- *ret++ = *_.arrayZ;
- continue;
- }
- hb_memcpy (ret, _.arrayZ, len);
- ret += len;
- }
- return_trace (true);
- }
-
- template <typename Iterator,
- hb_requires (hb_is_iterator (Iterator))>
- bool serialize_header (hb_serialize_context_t *c,
- Iterator it,
- unsigned data_size,
- unsigned min_off_size = 0)
- {
- TRACE_SERIALIZE (this);
-
- unsigned off_size = (hb_bit_storage (data_size + 1) + 7) / 8;
- off_size = hb_max(min_off_size, off_size);
-
- /* serialize CFFIndex header */
- if (unlikely (!c->extend_min (this))) return_trace (false);
- this->count = hb_len (it);
- if (!this->count) return_trace (true);
- if (unlikely (!c->extend (this->offSize))) return_trace (false);
- this->offSize = off_size;
- if (unlikely (!c->allocate_size<HBUINT8> (off_size * (this->count + 1), false)))
- return_trace (false);
-
- /* serialize indices */
- unsigned int offset = 1;
- if (HB_OPTIMIZE_SIZE_VAL)
- {
- unsigned int i = 0;
- for (const auto &_ : +it)
- {
- set_offset_at (i++, offset);
- offset += length_f (_);
- }
- set_offset_at (i, offset);
- }
- else
- switch (off_size)
- {
- case 1:
- {
- HBUINT8 *p = (HBUINT8 *) offsets;
- for (const auto &_ : +it)
- {
- *p++ = offset;
- offset += length_f (_);
- }
- *p = offset;
- }
- break;
- case 2:
- {
- HBUINT16 *p = (HBUINT16 *) offsets;
- for (const auto &_ : +it)
- {
- *p++ = offset;
- offset += length_f (_);
- }
- *p = offset;
- }
- break;
- case 3:
- {
- HBUINT24 *p = (HBUINT24 *) offsets;
- for (const auto &_ : +it)
- {
- *p++ = offset;
- offset += length_f (_);
- }
- *p = offset;
- }
- break;
- case 4:
- {
- HBUINT32 *p = (HBUINT32 *) offsets;
- for (const auto &_ : +it)
- {
- *p++ = offset;
- offset += length_f (_);
- }
- *p = offset;
- }
- break;
- default:
- break;
- }
-
- assert (offset == data_size + 1);
- return_trace (true);
- }
-
- template <typename Iterable,
- hb_requires (hb_is_iterable (Iterable))>
- static unsigned total_size (const Iterable &iterable, unsigned *data_size = nullptr, unsigned min_off_size = 0)
- {
- auto it = + hb_iter (iterable);
- if (!it)
- {
- if (data_size) *data_size = 0;
- return min_size;
- }
-
- unsigned total = 0;
- for (const auto &_ : +it)
- total += length_f (_);
-
- if (data_size) *data_size = total;
-
- unsigned off_size = (hb_bit_storage (total + 1) + 7) / 8;
- off_size = hb_max(min_off_size, off_size);
-
- return min_size + HBUINT8::static_size + (hb_len (it) + 1) * off_size + total;
- }
-
- void set_offset_at (unsigned int index, unsigned int offset)
- {
- assert (index <= count);
-
- unsigned int size = offSize;
- const HBUINT8 *p = offsets;
- switch (size)
- {
- case 1: ((HBUINT8 *) p)[index] = offset; break;
- case 2: ((HBUINT16 *) p)[index] = offset; break;
- case 3: ((HBUINT24 *) p)[index] = offset; break;
- case 4: ((HBUINT32 *) p)[index] = offset; break;
- default: return;
- }
- }
-
- private:
- unsigned int offset_at (unsigned int index) const
- {
- assert (index <= count);
-
- unsigned int size = offSize;
- const HBUINT8 *p = offsets;
- switch (size)
- {
- case 1: return ((HBUINT8 *) p)[index];
- case 2: return ((HBUINT16 *) p)[index];
- case 3: return ((HBUINT24 *) p)[index];
- case 4: return ((HBUINT32 *) p)[index];
- default: return 0;
- }
- }
-
- const unsigned char *data_base () const
- { return (const unsigned char *) this + min_size + offSize.static_size - 1 + offset_array_size (); }
- public:
-
- hb_ubytes_t operator [] (unsigned int index) const
- {
- if (unlikely (index >= count)) return hb_ubytes_t ();
- _hb_compiler_memory_r_barrier ();
- unsigned offset0 = offset_at (index);
- unsigned offset1 = offset_at (index + 1);
- if (unlikely (offset1 < offset0 || offset1 > offset_at (count)))
- return hb_ubytes_t ();
- return hb_ubytes_t (data_base () + offset0, offset1 - offset0);
- }
-
- unsigned int get_size () const
- {
- if (count)
- return min_size + offSize.static_size + offset_array_size () + (offset_at (count) - 1);
- return min_size; /* empty CFFIndex contains count only */
- }
-
- bool sanitize (hb_sanitize_context_t *c) const
- {
- TRACE_SANITIZE (this);
- return_trace (likely (c->check_struct (this) &&
- hb_barrier () &&
- (count == 0 || /* empty INDEX */
- (count < count + 1u &&
- hb_barrier () &&
- c->check_struct (&offSize) && offSize >= 1 && offSize <= 4 &&
- c->check_array (offsets, offSize, count + 1u) &&
- c->check_array ((const HBUINT8*) data_base (), 1, offset_at (count))))));
- }
-
- public:
- COUNT count; /* Number of object data. Note there are (count+1) offsets */
- private:
- HBUINT8 offSize; /* The byte size of each offset in the offsets array. */
- HBUINT8 offsets[HB_VAR_ARRAY];
- /* The array of (count + 1) offsets into objects array (1-base). */
- /* HBUINT8 data[HB_VAR_ARRAY]; Object data */
- public:
- DEFINE_SIZE_MIN (COUNT::static_size);
-};
-
/* Top Dict, Font Dict, Private Dict */
struct Dict : UnsizedByteStr
{
@@ -549,8 +308,8 @@ struct FDSelect
{
switch (format)
{
- case 0: return format.static_size + u.format0.get_size (num_glyphs);
- case 3: return format.static_size + u.format3.get_size ();
+ case 0: hb_barrier (); return format.static_size + u.format0.get_size (num_glyphs);
+ case 3: hb_barrier (); return format.static_size + u.format3.get_size ();
default:return 0;
}
}
@@ -561,8 +320,8 @@ struct FDSelect
switch (format)
{
- case 0: return u.format0.get_fd (glyph);
- case 3: return u.format3.get_fd (glyph);
+ case 0: hb_barrier (); return u.format0.get_fd (glyph);
+ case 3: hb_barrier (); return u.format3.get_fd (glyph);
default:return 0;
}
}
@@ -573,8 +332,8 @@ struct FDSelect
switch (format)
{
- case 0: return u.format0.get_fd_range (glyph);
- case 3: return u.format3.get_fd_range (glyph);
+ case 0: hb_barrier (); return u.format0.get_fd_range (glyph);
+ case 3: hb_barrier (); return u.format3.get_fd_range (glyph);
default:return {0, 1};
}
}
@@ -588,8 +347,8 @@ struct FDSelect
switch (format)
{
- case 0: return_trace (u.format0.sanitize (c, fdcount));
- case 3: return_trace (u.format3.sanitize (c, fdcount));
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c, fdcount));
+ case 3: hb_barrier (); return_trace (u.format3.sanitize (c, fdcount));
default:return_trace (false);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-cff1-table.hh b/thirdparty/harfbuzz/src/hb-ot-cff1-table.hh
index 1bbd463841..b84d896e3e 100644
--- a/thirdparty/harfbuzz/src/hb-ot-cff1-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-cff1-table.hh
@@ -51,9 +51,6 @@ namespace CFF {
enum EncodingID { StandardEncoding = 0, ExpertEncoding = 1 };
enum CharsetID { ISOAdobeCharset = 0, ExpertCharset = 1, ExpertSubsetCharset = 2 };
-typedef CFFIndex<HBUINT16> CFF1Index;
-
-typedef CFFIndex<HBUINT16> CFF1Index;
typedef CFF1Index CFF1CharStrings;
typedef Subrs<HBUINT16> CFF1Subrs;
@@ -242,8 +239,8 @@ struct Encoding
unsigned int size = min_size;
switch (table_format ())
{
- case 0: size += u.format0.get_size (); break;
- case 1: size += u.format1.get_size (); break;
+ case 0: hb_barrier (); size += u.format0.get_size (); break;
+ case 1: hb_barrier (); size += u.format1.get_size (); break;
}
if (has_supplement ())
size += suppEncData ().get_size ();
@@ -254,8 +251,8 @@ struct Encoding
{
switch (table_format ())
{
- case 0: return u.format0.get_code (glyph);
- case 1: return u.format1.get_code (glyph);
+ case 0: hb_barrier (); return u.format0.get_code (glyph);
+ case 1: hb_barrier (); return u.format1.get_code (glyph);
default:return 0;
}
}
@@ -279,8 +276,8 @@ struct Encoding
switch (table_format ())
{
- case 0: if (unlikely (!u.format0.sanitize (c))) { return_trace (false); } break;
- case 1: if (unlikely (!u.format1.sanitize (c))) { return_trace (false); } break;
+ case 0: hb_barrier (); if (unlikely (!u.format0.sanitize (c))) { return_trace (false); } break;
+ case 1: hb_barrier (); if (unlikely (!u.format1.sanitize (c))) { return_trace (false); } break;
default:return_trace (false);
}
return_trace (likely (!has_supplement () || suppEncData ().sanitize (c)));
@@ -291,8 +288,8 @@ struct Encoding
{
switch (table_format ())
{
- case 0: return StructAfter<CFF1SuppEncData> (u.format0.codes[u.format0.nCodes ()-1]);
- case 1: return StructAfter<CFF1SuppEncData> (u.format1.ranges[u.format1.nRanges ()-1]);
+ case 0: hb_barrier (); return StructAfter<CFF1SuppEncData> (u.format0.codes[u.format0.nCodes ()-1]);
+ case 1: hb_barrier (); return StructAfter<CFF1SuppEncData> (u.format1.ranges[u.format1.nRanges ()-1]);
default:return Null (CFF1SuppEncData);
}
}
@@ -570,9 +567,9 @@ struct Charset
{
switch (format)
{
- case 0: return min_size + u.format0.get_size (num_glyphs);
- case 1: return min_size + u.format1.get_size (num_glyphs);
- case 2: return min_size + u.format2.get_size (num_glyphs);
+ case 0: hb_barrier (); return min_size + u.format0.get_size (num_glyphs);
+ case 1: hb_barrier (); return min_size + u.format1.get_size (num_glyphs);
+ case 2: hb_barrier (); return min_size + u.format2.get_size (num_glyphs);
default:return 0;
}
}
@@ -582,9 +579,9 @@ struct Charset
{
switch (format)
{
- case 0: return u.format0.get_sid (glyph, num_glyphs);
- case 1: return u.format1.get_sid (glyph, num_glyphs, cache);
- case 2: return u.format2.get_sid (glyph, num_glyphs, cache);
+ case 0: hb_barrier (); return u.format0.get_sid (glyph, num_glyphs);
+ case 1: hb_barrier (); return u.format1.get_sid (glyph, num_glyphs, cache);
+ case 2: hb_barrier (); return u.format2.get_sid (glyph, num_glyphs, cache);
default:return 0;
}
}
@@ -593,9 +590,9 @@ struct Charset
{
switch (format)
{
- case 0: u.format0.collect_glyph_to_sid_map (mapping, num_glyphs); return;
- case 1: u.format1.collect_glyph_to_sid_map (mapping, num_glyphs); return;
- case 2: u.format2.collect_glyph_to_sid_map (mapping, num_glyphs); return;
+ case 0: hb_barrier (); u.format0.collect_glyph_to_sid_map (mapping, num_glyphs); return;
+ case 1: hb_barrier (); u.format1.collect_glyph_to_sid_map (mapping, num_glyphs); return;
+ case 2: hb_barrier (); u.format2.collect_glyph_to_sid_map (mapping, num_glyphs); return;
default:return;
}
}
@@ -604,9 +601,9 @@ struct Charset
{
switch (format)
{
- case 0: return u.format0.get_glyph (sid, num_glyphs);
- case 1: return u.format1.get_glyph (sid, num_glyphs);
- case 2: return u.format2.get_glyph (sid, num_glyphs);
+ case 0: hb_barrier (); return u.format0.get_glyph (sid, num_glyphs);
+ case 1: hb_barrier (); return u.format1.get_glyph (sid, num_glyphs);
+ case 2: hb_barrier (); return u.format2.get_glyph (sid, num_glyphs);
default:return 0;
}
}
@@ -620,9 +617,9 @@ struct Charset
switch (format)
{
- case 0: return_trace (u.format0.sanitize (c, c->get_num_glyphs (), num_charset_entries));
- case 1: return_trace (u.format1.sanitize (c, c->get_num_glyphs (), num_charset_entries));
- case 2: return_trace (u.format2.sanitize (c, c->get_num_glyphs (), num_charset_entries));
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c, c->get_num_glyphs (), num_charset_entries));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c, c->get_num_glyphs (), num_charset_entries));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c, c->get_num_glyphs (), num_charset_entries));
default:return_trace (false);
}
}
@@ -1179,6 +1176,7 @@ struct cff1
if (unlikely (!font_interp.interpret (*font))) goto fail;
PRIVDICTVAL *priv = &privateDicts[i];
const hb_ubytes_t privDictStr = StructAtOffsetOrNull<UnsizedByteStr> (cff, font->privateDictInfo.offset, sc, font->privateDictInfo.size).as_ubytes (font->privateDictInfo.size);
+ if (unlikely (privDictStr == (const unsigned char *) &Null (UnsizedByteStr))) goto fail;
num_interp_env_t env2 (privDictStr);
dict_interpreter_t<PRIVOPSET, PRIVDICTVAL> priv_interp (env2);
priv->init ();
@@ -1193,6 +1191,7 @@ struct cff1
PRIVDICTVAL *priv = &privateDicts[0];
const hb_ubytes_t privDictStr = StructAtOffsetOrNull<UnsizedByteStr> (cff, font->privateDictInfo.offset, sc, font->privateDictInfo.size).as_ubytes (font->privateDictInfo.size);
+ if (unlikely (privDictStr == (const unsigned char *) &Null (UnsizedByteStr))) goto fail;
num_interp_env_t env (privDictStr);
dict_interpreter_t<PRIVOPSET, PRIVDICTVAL> priv_interp (env);
priv->init ();
diff --git a/thirdparty/harfbuzz/src/hb-ot-cff2-table.cc b/thirdparty/harfbuzz/src/hb-ot-cff2-table.cc
index 7955565551..e42217b4e8 100644
--- a/thirdparty/harfbuzz/src/hb-ot-cff2-table.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-cff2-table.cc
@@ -203,6 +203,11 @@ struct cff2_cs_opset_path_t : cff2_cs_opset_t<cff2_cs_opset_path_t, cff2_path_pa
bool OT::cff2::accelerator_t::get_path (hb_font_t *font, hb_codepoint_t glyph, hb_draw_session_t &draw_session) const
{
+ return get_path_at (font, glyph, draw_session, hb_array (font->coords, font->num_coords));
+}
+
+bool OT::cff2::accelerator_t::get_path_at (hb_font_t *font, hb_codepoint_t glyph, hb_draw_session_t &draw_session, hb_array_t<const int> coords) const
+{
#ifdef HB_NO_OT_FONT_CFF
/* XXX Remove check when this code moves to .hh file. */
return true;
@@ -212,7 +217,7 @@ bool OT::cff2::accelerator_t::get_path (hb_font_t *font, hb_codepoint_t glyph, h
unsigned int fd = fdSelect->get_fd (glyph);
const hb_ubytes_t str = (*charStrings)[glyph];
- cff2_cs_interp_env_t<number_t> env (str, *this, fd, font->coords, font->num_coords);
+ cff2_cs_interp_env_t<number_t> env (str, *this, fd, coords.arrayZ, coords.length);
cff2_cs_interpreter_t<cff2_cs_opset_path_t, cff2_path_param_t, number_t> interp (env);
cff2_path_param_t param (font, draw_session);
if (unlikely (!interp.interpret (param))) return false;
diff --git a/thirdparty/harfbuzz/src/hb-ot-cff2-table.hh b/thirdparty/harfbuzz/src/hb-ot-cff2-table.hh
index 4b3bdc9315..c52c0511c6 100644
--- a/thirdparty/harfbuzz/src/hb-ot-cff2-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-cff2-table.hh
@@ -40,8 +40,6 @@ namespace CFF {
*/
#define HB_OT_TAG_CFF2 HB_TAG('C','F','F','2')
-typedef CFFIndex<HBUINT32> CFF2Index;
-
typedef CFF2Index CFF2CharStrings;
typedef Subrs<HBUINT32> CFF2Subrs;
@@ -64,9 +62,9 @@ struct CFF2FDSelect
{
switch (format)
{
- case 0: return format.static_size + u.format0.get_size (num_glyphs);
- case 3: return format.static_size + u.format3.get_size ();
- case 4: return format.static_size + u.format4.get_size ();
+ case 0: hb_barrier (); return format.static_size + u.format0.get_size (num_glyphs);
+ case 3: hb_barrier (); return format.static_size + u.format3.get_size ();
+ case 4: hb_barrier (); return format.static_size + u.format4.get_size ();
default:return 0;
}
}
@@ -78,9 +76,9 @@ struct CFF2FDSelect
switch (format)
{
- case 0: return u.format0.get_fd (glyph);
- case 3: return u.format3.get_fd (glyph);
- case 4: return u.format4.get_fd (glyph);
+ case 0: hb_barrier (); return u.format0.get_fd (glyph);
+ case 3: hb_barrier (); return u.format3.get_fd (glyph);
+ case 4: hb_barrier (); return u.format4.get_fd (glyph);
default:return 0;
}
}
@@ -94,9 +92,9 @@ struct CFF2FDSelect
switch (format)
{
- case 0: return_trace (u.format0.sanitize (c, fdcount));
- case 3: return_trace (u.format3.sanitize (c, fdcount));
- case 4: return_trace (u.format4.sanitize (c, fdcount));
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c, fdcount));
+ case 3: hb_barrier (); return_trace (u.format3.sanitize (c, fdcount));
+ case 4: hb_barrier (); return_trace (u.format4.sanitize (c, fdcount));
default:return_trace (false);
}
}
@@ -460,6 +458,7 @@ struct cff2
if (unlikely (!font_interp.interpret (*font))) goto fail;
const hb_ubytes_t privDictStr = StructAtOffsetOrNull<UnsizedByteStr> (cff2, font->privateDictInfo.offset, sc, font->privateDictInfo.size).as_ubytes (font->privateDictInfo.size);
+ if (unlikely (privDictStr == (const unsigned char *) &Null (UnsizedByteStr))) goto fail;
cff2_priv_dict_interp_env_t env2 (privDictStr);
dict_interpreter_t<PRIVOPSET, PRIVDICTVAL, cff2_priv_dict_interp_env_t> priv_interp (env2);
privateDicts[i].init ();
@@ -521,6 +520,7 @@ struct cff2
hb_glyph_extents_t *extents) const;
HB_INTERNAL bool paint_glyph (hb_font_t *font, hb_codepoint_t glyph, hb_paint_funcs_t *funcs, void *data, hb_color_t foreground) const;
HB_INTERNAL bool get_path (hb_font_t *font, hb_codepoint_t glyph, hb_draw_session_t &draw_session) const;
+ HB_INTERNAL bool get_path_at (hb_font_t *font, hb_codepoint_t glyph, hb_draw_session_t &draw_session, hb_array_t<const int> coords) const;
};
struct accelerator_subset_t : accelerator_templ_t<cff2_private_dict_opset_subset_t, cff2_private_dict_values_subset_t>
diff --git a/thirdparty/harfbuzz/src/hb-ot-cmap-table.hh b/thirdparty/harfbuzz/src/hb-ot-cmap-table.hh
index 64d2b13880..0f1edce0b0 100644
--- a/thirdparty/harfbuzz/src/hb-ot-cmap-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-cmap-table.hh
@@ -43,27 +43,145 @@ namespace OT {
static inline uint8_t unicode_to_macroman (hb_codepoint_t u)
{
- uint16_t mapping[] = {
- 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
- 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
- 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
- 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
- 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
- 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
- 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,
- 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
- 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
- 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
- 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,
- 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
- 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
- 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
- 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
- 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7
+ static const struct unicode_to_macroman_t
+ {
+ uint16_t unicode;
+ uint8_t macroman;
+ }
+ mapping[] =
+ {
+ { 0x00A0, 0xCA },
+ { 0x00A1, 0xC1 },
+ { 0x00A2, 0xA2 },
+ { 0x00A3, 0xA3 },
+ { 0x00A5, 0xB4 },
+ { 0x00A7, 0xA4 },
+ { 0x00A8, 0xAC },
+ { 0x00A9, 0xA9 },
+ { 0x00AA, 0xBB },
+ { 0x00AB, 0xC7 },
+ { 0x00AC, 0xC2 },
+ { 0x00AE, 0xA8 },
+ { 0x00AF, 0xF8 },
+ { 0x00B0, 0xA1 },
+ { 0x00B1, 0xB1 },
+ { 0x00B4, 0xAB },
+ { 0x00B5, 0xB5 },
+ { 0x00B6, 0xA6 },
+ { 0x00B7, 0xE1 },
+ { 0x00B8, 0xFC },
+ { 0x00BA, 0xBC },
+ { 0x00BB, 0xC8 },
+ { 0x00BF, 0xC0 },
+ { 0x00C0, 0xCB },
+ { 0x00C1, 0xE7 },
+ { 0x00C2, 0xE5 },
+ { 0x00C3, 0xCC },
+ { 0x00C4, 0x80 },
+ { 0x00C5, 0x81 },
+ { 0x00C6, 0xAE },
+ { 0x00C7, 0x82 },
+ { 0x00C8, 0xE9 },
+ { 0x00C9, 0x83 },
+ { 0x00CA, 0xE6 },
+ { 0x00CB, 0xE8 },
+ { 0x00CC, 0xED },
+ { 0x00CD, 0xEA },
+ { 0x00CE, 0xEB },
+ { 0x00CF, 0xEC },
+ { 0x00D1, 0x84 },
+ { 0x00D2, 0xF1 },
+ { 0x00D3, 0xEE },
+ { 0x00D4, 0xEF },
+ { 0x00D5, 0xCD },
+ { 0x00D6, 0x85 },
+ { 0x00D8, 0xAF },
+ { 0x00D9, 0xF4 },
+ { 0x00DA, 0xF2 },
+ { 0x00DB, 0xF3 },
+ { 0x00DC, 0x86 },
+ { 0x00DF, 0xA7 },
+ { 0x00E0, 0x88 },
+ { 0x00E1, 0x87 },
+ { 0x00E2, 0x89 },
+ { 0x00E3, 0x8B },
+ { 0x00E4, 0x8A },
+ { 0x00E5, 0x8C },
+ { 0x00E6, 0xBE },
+ { 0x00E7, 0x8D },
+ { 0x00E8, 0x8F },
+ { 0x00E9, 0x8E },
+ { 0x00EA, 0x90 },
+ { 0x00EB, 0x91 },
+ { 0x00EC, 0x93 },
+ { 0x00ED, 0x92 },
+ { 0x00EE, 0x94 },
+ { 0x00EF, 0x95 },
+ { 0x00F1, 0x96 },
+ { 0x00F2, 0x98 },
+ { 0x00F3, 0x97 },
+ { 0x00F4, 0x99 },
+ { 0x00F5, 0x9B },
+ { 0x00F6, 0x9A },
+ { 0x00F7, 0xD6 },
+ { 0x00F8, 0xBF },
+ { 0x00F9, 0x9D },
+ { 0x00FA, 0x9C },
+ { 0x00FB, 0x9E },
+ { 0x00FC, 0x9F },
+ { 0x00FF, 0xD8 },
+ { 0x0131, 0xF5 },
+ { 0x0152, 0xCE },
+ { 0x0153, 0xCF },
+ { 0x0178, 0xD9 },
+ { 0x0192, 0xC4 },
+ { 0x02C6, 0xF6 },
+ { 0x02C7, 0xFF },
+ { 0x02D8, 0xF9 },
+ { 0x02D9, 0xFA },
+ { 0x02DA, 0xFB },
+ { 0x02DB, 0xFE },
+ { 0x02DC, 0xF7 },
+ { 0x02DD, 0xFD },
+ { 0x03A9, 0xBD },
+ { 0x03C0, 0xB9 },
+ { 0x2013, 0xD0 },
+ { 0x2014, 0xD1 },
+ { 0x2018, 0xD4 },
+ { 0x2019, 0xD5 },
+ { 0x201A, 0xE2 },
+ { 0x201C, 0xD2 },
+ { 0x201D, 0xD3 },
+ { 0x201E, 0xE3 },
+ { 0x2020, 0xA0 },
+ { 0x2021, 0xE0 },
+ { 0x2022, 0xA5 },
+ { 0x2026, 0xC9 },
+ { 0x2030, 0xE4 },
+ { 0x2039, 0xDC },
+ { 0x203A, 0xDD },
+ { 0x2044, 0xDA },
+ { 0x20AC, 0xDB },
+ { 0x2122, 0xAA },
+ { 0x2202, 0xB6 },
+ { 0x2206, 0xC6 },
+ { 0x220F, 0xB8 },
+ { 0x2211, 0xB7 },
+ { 0x221A, 0xC3 },
+ { 0x221E, 0xB0 },
+ { 0x222B, 0xBA },
+ { 0x2248, 0xC5 },
+ { 0x2260, 0xAD },
+ { 0x2264, 0xB2 },
+ { 0x2265, 0xB3 },
+ { 0x25CA, 0xD7 },
+ { 0xF8FF, 0xF0 },
+ { 0xFB01, 0xDE },
+ { 0xFB02, 0xDF },
};
- uint16_t *c = hb_bsearch (u, mapping, ARRAY_LENGTH (mapping), sizeof (mapping[0]),
- _hb_cmp_operator<uint16_t, uint16_t>);
- return c ? (c - mapping) + 0x7F : 0;
+ auto *c = hb_bsearch (u, mapping, ARRAY_LENGTH (mapping), sizeof (mapping[0]),
+ _hb_cmp_operator<uint16_t, uint16_t>);
+ return c ? c->macroman : 0;
}
struct CmapSubtableFormat0
@@ -1379,12 +1497,12 @@ struct CmapSubtable
hb_codepoint_t *glyph) const
{
switch (u.format) {
- case 0: return u.format0 .get_glyph (codepoint, glyph);
- case 4: return u.format4 .get_glyph (codepoint, glyph);
- case 6: return u.format6 .get_glyph (codepoint, glyph);
- case 10: return u.format10.get_glyph (codepoint, glyph);
- case 12: return u.format12.get_glyph (codepoint, glyph);
- case 13: return u.format13.get_glyph (codepoint, glyph);
+ case 0: hb_barrier (); return u.format0 .get_glyph (codepoint, glyph);
+ case 4: hb_barrier (); return u.format4 .get_glyph (codepoint, glyph);
+ case 6: hb_barrier (); return u.format6 .get_glyph (codepoint, glyph);
+ case 10: hb_barrier (); return u.format10.get_glyph (codepoint, glyph);
+ case 12: hb_barrier (); return u.format12.get_glyph (codepoint, glyph);
+ case 13: hb_barrier (); return u.format13.get_glyph (codepoint, glyph);
case 14:
default: return false;
}
@@ -1392,12 +1510,12 @@ struct CmapSubtable
void collect_unicodes (hb_set_t *out, unsigned int num_glyphs = UINT_MAX) const
{
switch (u.format) {
- case 0: u.format0 .collect_unicodes (out); return;
- case 4: u.format4 .collect_unicodes (out); return;
- case 6: u.format6 .collect_unicodes (out); return;
- case 10: u.format10.collect_unicodes (out); return;
- case 12: u.format12.collect_unicodes (out, num_glyphs); return;
- case 13: u.format13.collect_unicodes (out, num_glyphs); return;
+ case 0: hb_barrier (); u.format0 .collect_unicodes (out); return;
+ case 4: hb_barrier (); u.format4 .collect_unicodes (out); return;
+ case 6: hb_barrier (); u.format6 .collect_unicodes (out); return;
+ case 10: hb_barrier (); u.format10.collect_unicodes (out); return;
+ case 12: hb_barrier (); u.format12.collect_unicodes (out, num_glyphs); return;
+ case 13: hb_barrier (); u.format13.collect_unicodes (out, num_glyphs); return;
case 14:
default: return;
}
@@ -1408,12 +1526,12 @@ struct CmapSubtable
unsigned num_glyphs = UINT_MAX) const
{
switch (u.format) {
- case 0: u.format0 .collect_mapping (unicodes, mapping); return;
- case 4: u.format4 .collect_mapping (unicodes, mapping); return;
- case 6: u.format6 .collect_mapping (unicodes, mapping); return;
- case 10: u.format10.collect_mapping (unicodes, mapping); return;
- case 12: u.format12.collect_mapping (unicodes, mapping, num_glyphs); return;
- case 13: u.format13.collect_mapping (unicodes, mapping, num_glyphs); return;
+ case 0: hb_barrier (); u.format0 .collect_mapping (unicodes, mapping); return;
+ case 4: hb_barrier (); u.format4 .collect_mapping (unicodes, mapping); return;
+ case 6: hb_barrier (); u.format6 .collect_mapping (unicodes, mapping); return;
+ case 10: hb_barrier (); u.format10.collect_mapping (unicodes, mapping); return;
+ case 12: hb_barrier (); u.format12.collect_mapping (unicodes, mapping, num_glyphs); return;
+ case 13: hb_barrier (); u.format13.collect_mapping (unicodes, mapping, num_glyphs); return;
case 14:
default: return;
}
@@ -1422,12 +1540,12 @@ struct CmapSubtable
unsigned get_language () const
{
switch (u.format) {
- case 0: return u.format0 .get_language ();
- case 4: return u.format4 .get_language ();
- case 6: return u.format6 .get_language ();
- case 10: return u.format10.get_language ();
- case 12: return u.format12.get_language ();
- case 13: return u.format13.get_language ();
+ case 0: hb_barrier (); return u.format0 .get_language ();
+ case 4: hb_barrier (); return u.format4 .get_language ();
+ case 6: hb_barrier (); return u.format6 .get_language ();
+ case 10: hb_barrier (); return u.format10.get_language ();
+ case 12: hb_barrier (); return u.format12.get_language ();
+ case 13: hb_barrier (); return u.format13.get_language ();
case 14:
default: return 0;
}
@@ -1442,9 +1560,9 @@ struct CmapSubtable
const void *base)
{
switch (format) {
- case 4: return u.format4.serialize (c, it);
- case 12: return u.format12.serialize (c, it);
- case 14: return u.format14.serialize (c, &plan->unicodes, &plan->glyphs_requested, plan->glyph_map, base);
+ case 4: hb_barrier (); return u.format4.serialize (c, it);
+ case 12: hb_barrier (); return u.format12.serialize (c, it);
+ case 14: hb_barrier (); return u.format14.serialize (c, &plan->unicodes, &plan->glyphs_requested, plan->glyph_map, base);
default: return;
}
}
@@ -1455,13 +1573,13 @@ struct CmapSubtable
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format) {
- case 0: return_trace (u.format0 .sanitize (c));
- case 4: return_trace (u.format4 .sanitize (c));
- case 6: return_trace (u.format6 .sanitize (c));
- case 10: return_trace (u.format10.sanitize (c));
- case 12: return_trace (u.format12.sanitize (c));
- case 13: return_trace (u.format13.sanitize (c));
- case 14: return_trace (u.format14.sanitize (c));
+ case 0: hb_barrier (); return_trace (u.format0 .sanitize (c));
+ case 4: hb_barrier (); return_trace (u.format4 .sanitize (c));
+ case 6: hb_barrier (); return_trace (u.format6 .sanitize (c));
+ case 10: hb_barrier (); return_trace (u.format10.sanitize (c));
+ case 12: hb_barrier (); return_trace (u.format12.sanitize (c));
+ case 13: hb_barrier (); return_trace (u.format13.sanitize (c));
+ case 14: hb_barrier (); return_trace (u.format14.sanitize (c));
default:return_trace (true);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-face-table-list.hh b/thirdparty/harfbuzz/src/hb-ot-face-table-list.hh
index db1c55490c..dd4befffa1 100644
--- a/thirdparty/harfbuzz/src/hb-ot-face-table-list.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-face-table-list.hh
@@ -96,6 +96,9 @@ HB_OT_CORE_TABLE (OT, avar)
HB_OT_CORE_TABLE (OT, cvar)
HB_OT_ACCELERATOR (OT, gvar)
HB_OT_CORE_TABLE (OT, MVAR)
+#ifndef HB_NO_VAR_COMPOSITES
+HB_OT_CORE_TABLE (OT, VARC)
+#endif
#endif
/* Legacy kern. */
diff --git a/thirdparty/harfbuzz/src/hb-ot-font.cc b/thirdparty/harfbuzz/src/hb-ot-font.cc
index 1da869d697..2495306d24 100644
--- a/thirdparty/harfbuzz/src/hb-ot-font.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-font.cc
@@ -43,6 +43,7 @@
#include "hb-ot-hmtx-table.hh"
#include "hb-ot-post-table.hh"
#include "hb-ot-stat-table.hh" // Just so we compile it; unused otherwise.
+#include "hb-ot-var-varc-table.hh"
#include "hb-ot-vorg-table.hh"
#include "OT/Color/CBDT/CBDT.hh"
#include "OT/Color/COLR/COLR.hh"
@@ -523,6 +524,10 @@ hb_ot_draw_glyph (hb_font_t *font,
{ // Need draw_session to be destructed before emboldening.
hb_draw_session_t draw_session (embolden ? hb_outline_recording_pen_get_funcs () : draw_funcs,
embolden ? &outline : draw_data, font->slant_xy);
+#ifndef HB_NO_VAR_COMPOSITES
+ if (!font->face->table.VARC->get_path (font, glyph, draw_session))
+#endif
+ // Keep the following in synch with VARC::get_path_at()
if (!font->face->table.glyf->get_path (font, glyph, draw_session))
#ifndef HB_NO_CFF
if (!font->face->table.cff2->get_path (font, glyph, draw_session))
@@ -563,6 +568,9 @@ hb_ot_paint_glyph (hb_font_t *font,
if (font->face->table.sbix->paint_glyph (font, glyph, paint_funcs, paint_data)) return;
#endif
#endif
+#ifndef HB_NO_VAR_COMPOSITES
+ if (font->face->table.VARC->paint_glyph (font, glyph, paint_funcs, paint_data, foreground)) return;
+#endif
if (font->face->table.glyf->paint_glyph (font, glyph, paint_funcs, paint_data, foreground)) return;
#ifndef HB_NO_CFF
if (font->face->table.cff2->paint_glyph (font, glyph, paint_funcs, paint_data, foreground)) return;
diff --git a/thirdparty/harfbuzz/src/hb-ot-hmtx-table.hh b/thirdparty/harfbuzz/src/hb-ot-hmtx-table.hh
index e259b33748..493bc6e7a1 100644
--- a/thirdparty/harfbuzz/src/hb-ot-hmtx-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-hmtx-table.hh
@@ -30,6 +30,7 @@
#include "hb-open-type.hh"
#include "hb-ot-maxp-table.hh"
#include "hb-ot-hhea-table.hh"
+#include "hb-ot-os2-table.hh"
#include "hb-ot-var-hvar-table.hh"
#include "hb-ot-var-mvar-table.hh"
#include "hb-ot-metrics.hh"
diff --git a/thirdparty/harfbuzz/src/hb-ot-kern-table.hh b/thirdparty/harfbuzz/src/hb-ot-kern-table.hh
index b87ac8f494..2abda78af8 100644
--- a/thirdparty/harfbuzz/src/hb-ot-kern-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-kern-table.hh
@@ -132,7 +132,7 @@ struct KernSubTable
{
switch (get_type ()) {
/* This method hooks up to hb_font_t's get_h_kerning. Only support Format0. */
- case 0: return u.format0.get_kerning (left, right);
+ case 0: hb_barrier (); return u.format0.get_kerning (left, right);
default:return 0;
}
}
@@ -311,9 +311,9 @@ struct kern
bool has_state_machine () const
{
switch (get_type ()) {
- case 0: return u.ot.has_state_machine ();
+ case 0: hb_barrier (); return u.ot.has_state_machine ();
#ifndef HB_NO_AAT_SHAPE
- case 1: return u.aat.has_state_machine ();
+ case 1: hb_barrier (); return u.aat.has_state_machine ();
#endif
default:return false;
}
@@ -322,9 +322,9 @@ struct kern
bool has_cross_stream () const
{
switch (get_type ()) {
- case 0: return u.ot.has_cross_stream ();
+ case 0: hb_barrier (); return u.ot.has_cross_stream ();
#ifndef HB_NO_AAT_SHAPE
- case 1: return u.aat.has_cross_stream ();
+ case 1: hb_barrier (); return u.aat.has_cross_stream ();
#endif
default:return false;
}
@@ -333,9 +333,9 @@ struct kern
int get_h_kerning (hb_codepoint_t left, hb_codepoint_t right) const
{
switch (get_type ()) {
- case 0: return u.ot.get_h_kerning (left, right);
+ case 0: hb_barrier (); return u.ot.get_h_kerning (left, right);
#ifndef HB_NO_AAT_SHAPE
- case 1: return u.aat.get_h_kerning (left, right);
+ case 1: hb_barrier (); return u.aat.get_h_kerning (left, right);
#endif
default:return 0;
}
@@ -370,9 +370,9 @@ struct kern
AAT::kern_accelerator_data_t create_accelerator_data (unsigned num_glyphs) const
{
switch (get_type ()) {
- case 0: return u.ot.create_accelerator_data (num_glyphs);
+ case 0: hb_barrier (); return u.ot.create_accelerator_data (num_glyphs);
#ifndef HB_NO_AAT_SHAPE
- case 1: return u.aat.create_accelerator_data (num_glyphs);
+ case 1: hb_barrier (); return u.aat.create_accelerator_data (num_glyphs);
#endif
default:return AAT::kern_accelerator_data_t ();
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-layout-base-table.hh b/thirdparty/harfbuzz/src/hb-ot-layout-base-table.hh
index 56290905ce..68a4e7cba7 100644
--- a/thirdparty/harfbuzz/src/hb-ot-layout-base-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-layout-base-table.hh
@@ -172,9 +172,9 @@ struct BaseCoord
hb_direction_t direction) const
{
switch (u.format) {
- case 1: return u.format1.get_coord (font, direction);
- case 2: return u.format2.get_coord (font, direction);
- case 3: return u.format3.get_coord (font, var_store, direction);
+ case 1: hb_barrier (); return u.format1.get_coord (font, direction);
+ case 2: hb_barrier (); return u.format2.get_coord (font, direction);
+ case 3: hb_barrier (); return u.format3.get_coord (font, var_store, direction);
default:return 0;
}
}
@@ -182,7 +182,7 @@ struct BaseCoord
void collect_variation_indices (hb_set_t& varidx_set /* OUT */) const
{
switch (u.format) {
- case 3: u.format3.collect_variation_indices (varidx_set);
+ case 3: hb_barrier (); u.format3.collect_variation_indices (varidx_set);
default:return;
}
}
@@ -193,9 +193,9 @@ struct BaseCoord
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
- case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
- case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
- case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
+ case 1: hb_barrier (); return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
+ case 2: hb_barrier (); return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
+ case 3: hb_barrier (); return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
@@ -206,9 +206,9 @@ struct BaseCoord
if (unlikely (!u.format.sanitize (c))) return_trace (false);
hb_barrier ();
switch (u.format) {
- case 1: return_trace (u.format1.sanitize (c));
- case 2: return_trace (u.format2.sanitize (c));
- case 3: return_trace (u.format3.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c));
+ case 3: hb_barrier (); return_trace (u.format3.sanitize (c));
default:return_trace (false);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-layout-common.hh b/thirdparty/harfbuzz/src/hb-ot-layout-common.hh
index 65c8309573..8216f54ca1 100644
--- a/thirdparty/harfbuzz/src/hb-ot-layout-common.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-layout-common.hh
@@ -646,8 +646,7 @@ struct FeatureParamsCharacterVariants
return;
unsigned last_name_id = (unsigned) firstParamUILabelNameID + (unsigned) numNamedParameters - 1;
- if (last_name_id >= 256 && last_name_id <= 32767)
- nameids_to_retain->add_range (firstParamUILabelNameID, last_name_id);
+ nameids_to_retain->add_range (firstParamUILabelNameID, last_name_id);
}
bool subset (hb_subset_context_t *c) const
@@ -2068,11 +2067,11 @@ struct ClassDef
unsigned int get_class (hb_codepoint_t glyph_id) const
{
switch (u.format) {
- case 1: return u.format1.get_class (glyph_id);
- case 2: return u.format2.get_class (glyph_id);
+ case 1: hb_barrier (); return u.format1.get_class (glyph_id);
+ case 2: hb_barrier (); return u.format2.get_class (glyph_id);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.get_class (glyph_id);
- case 4: return u.format4.get_class (glyph_id);
+ case 3: hb_barrier (); return u.format3.get_class (glyph_id);
+ case 4: hb_barrier (); return u.format4.get_class (glyph_id);
#endif
default:return 0;
}
@@ -2081,11 +2080,11 @@ struct ClassDef
unsigned get_population () const
{
switch (u.format) {
- case 1: return u.format1.get_population ();
- case 2: return u.format2.get_population ();
+ case 1: hb_barrier (); return u.format1.get_population ();
+ case 2: hb_barrier (); return u.format2.get_population ();
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.get_population ();
- case 4: return u.format4.get_population ();
+ case 3: hb_barrier (); return u.format3.get_population ();
+ case 4: hb_barrier (); return u.format4.get_population ();
#endif
default:return NOT_COVERED;
}
@@ -2147,11 +2146,11 @@ struct ClassDef
switch (u.format)
{
- case 1: return_trace (u.format1.serialize (c, it));
- case 2: return_trace (u.format2.serialize (c, it));
+ case 1: hb_barrier (); return_trace (u.format1.serialize (c, it));
+ case 2: hb_barrier (); return_trace (u.format2.serialize (c, it));
#ifndef HB_NO_BEYOND_64K
- case 3: return_trace (u.format3.serialize (c, it));
- case 4: return_trace (u.format4.serialize (c, it));
+ case 3: hb_barrier (); return_trace (u.format3.serialize (c, it));
+ case 4: hb_barrier (); return_trace (u.format4.serialize (c, it));
#endif
default:return_trace (false);
}
@@ -2165,11 +2164,11 @@ struct ClassDef
{
TRACE_SUBSET (this);
switch (u.format) {
- case 1: return_trace (u.format1.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
- case 2: return_trace (u.format2.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
+ case 1: hb_barrier (); return_trace (u.format1.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
+ case 2: hb_barrier (); return_trace (u.format2.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
#ifndef HB_NO_BEYOND_64K
- case 3: return_trace (u.format3.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
- case 4: return_trace (u.format4.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
+ case 3: hb_barrier (); return_trace (u.format3.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
+ case 4: hb_barrier (); return_trace (u.format4.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
#endif
default:return_trace (false);
}
@@ -2181,11 +2180,11 @@ struct ClassDef
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format) {
- case 1: return_trace (u.format1.sanitize (c));
- case 2: return_trace (u.format2.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c));
#ifndef HB_NO_BEYOND_64K
- case 3: return_trace (u.format3.sanitize (c));
- case 4: return_trace (u.format4.sanitize (c));
+ case 3: hb_barrier (); return_trace (u.format3.sanitize (c));
+ case 4: hb_barrier (); return_trace (u.format4.sanitize (c));
#endif
default:return_trace (true);
}
@@ -2194,11 +2193,11 @@ struct ClassDef
unsigned cost () const
{
switch (u.format) {
- case 1: return u.format1.cost ();
- case 2: return u.format2.cost ();
+ case 1: hb_barrier (); return u.format1.cost ();
+ case 2: hb_barrier (); return u.format2.cost ();
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.cost ();
- case 4: return u.format4.cost ();
+ case 3: hb_barrier (); return u.format3.cost ();
+ case 4: hb_barrier (); return u.format4.cost ();
#endif
default:return 0u;
}
@@ -2210,11 +2209,11 @@ struct ClassDef
bool collect_coverage (set_t *glyphs) const
{
switch (u.format) {
- case 1: return u.format1.collect_coverage (glyphs);
- case 2: return u.format2.collect_coverage (glyphs);
+ case 1: hb_barrier (); return u.format1.collect_coverage (glyphs);
+ case 2: hb_barrier (); return u.format2.collect_coverage (glyphs);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.collect_coverage (glyphs);
- case 4: return u.format4.collect_coverage (glyphs);
+ case 3: hb_barrier (); return u.format3.collect_coverage (glyphs);
+ case 4: hb_barrier (); return u.format4.collect_coverage (glyphs);
#endif
default:return false;
}
@@ -2226,11 +2225,11 @@ struct ClassDef
bool collect_class (set_t *glyphs, unsigned int klass) const
{
switch (u.format) {
- case 1: return u.format1.collect_class (glyphs, klass);
- case 2: return u.format2.collect_class (glyphs, klass);
+ case 1: hb_barrier (); return u.format1.collect_class (glyphs, klass);
+ case 2: hb_barrier (); return u.format2.collect_class (glyphs, klass);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.collect_class (glyphs, klass);
- case 4: return u.format4.collect_class (glyphs, klass);
+ case 3: hb_barrier (); return u.format3.collect_class (glyphs, klass);
+ case 4: hb_barrier (); return u.format4.collect_class (glyphs, klass);
#endif
default:return false;
}
@@ -2239,11 +2238,11 @@ struct ClassDef
bool intersects (const hb_set_t *glyphs) const
{
switch (u.format) {
- case 1: return u.format1.intersects (glyphs);
- case 2: return u.format2.intersects (glyphs);
+ case 1: hb_barrier (); return u.format1.intersects (glyphs);
+ case 2: hb_barrier (); return u.format2.intersects (glyphs);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.intersects (glyphs);
- case 4: return u.format4.intersects (glyphs);
+ case 3: hb_barrier (); return u.format3.intersects (glyphs);
+ case 4: hb_barrier (); return u.format4.intersects (glyphs);
#endif
default:return false;
}
@@ -2251,11 +2250,11 @@ struct ClassDef
bool intersects_class (const hb_set_t *glyphs, unsigned int klass) const
{
switch (u.format) {
- case 1: return u.format1.intersects_class (glyphs, klass);
- case 2: return u.format2.intersects_class (glyphs, klass);
+ case 1: hb_barrier (); return u.format1.intersects_class (glyphs, klass);
+ case 2: hb_barrier (); return u.format2.intersects_class (glyphs, klass);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.intersects_class (glyphs, klass);
- case 4: return u.format4.intersects_class (glyphs, klass);
+ case 3: hb_barrier (); return u.format3.intersects_class (glyphs, klass);
+ case 4: hb_barrier (); return u.format4.intersects_class (glyphs, klass);
#endif
default:return false;
}
@@ -2264,11 +2263,11 @@ struct ClassDef
void intersected_class_glyphs (const hb_set_t *glyphs, unsigned klass, hb_set_t *intersect_glyphs) const
{
switch (u.format) {
- case 1: return u.format1.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
- case 2: return u.format2.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
+ case 1: hb_barrier (); return u.format1.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
+ case 2: hb_barrier (); return u.format2.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
- case 4: return u.format4.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
+ case 3: hb_barrier (); return u.format3.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
+ case 4: hb_barrier (); return u.format4.intersected_class_glyphs (glyphs, klass, intersect_glyphs);
#endif
default:return;
}
@@ -2277,11 +2276,11 @@ struct ClassDef
void intersected_classes (const hb_set_t *glyphs, hb_set_t *intersect_classes) const
{
switch (u.format) {
- case 1: return u.format1.intersected_classes (glyphs, intersect_classes);
- case 2: return u.format2.intersected_classes (glyphs, intersect_classes);
+ case 1: hb_barrier (); return u.format1.intersected_classes (glyphs, intersect_classes);
+ case 2: hb_barrier (); return u.format2.intersected_classes (glyphs, intersect_classes);
#ifndef HB_NO_BEYOND_64K
- case 3: return u.format3.intersected_classes (glyphs, intersect_classes);
- case 4: return u.format4.intersected_classes (glyphs, intersect_classes);
+ case 3: hb_barrier (); return u.format3.intersected_classes (glyphs, intersect_classes);
+ case 4: hb_barrier (); return u.format4.intersected_classes (glyphs, intersect_classes);
#endif
default:return;
}
@@ -2421,12 +2420,12 @@ struct delta_row_encoding_t
int combined_width = 0;
for (unsigned i = 0; i < chars.length; i++)
combined_width += hb_max (chars.arrayZ[i], other_encoding.chars.arrayZ[i]);
-
+
hb_vector_t<uint8_t> combined_columns;
combined_columns.alloc (columns.length);
for (unsigned i = 0; i < columns.length; i++)
combined_columns.push (columns.arrayZ[i] | other_encoding.columns.arrayZ[i]);
-
+
int combined_overhead = get_chars_overhead (combined_columns);
int combined_gain = (int) overhead + (int) other_encoding.overhead - combined_overhead
- (combined_width - (int) width) * items.length
@@ -2471,6 +2470,8 @@ struct VarRegionAxis
int peak = peakCoord.to_int ();
if (peak == 0 || coord == peak)
return 1.f;
+ else if (coord == 0) // Faster
+ return 0.f;
int start = startCoord.to_int (), end = endCoord.to_int ();
@@ -2494,8 +2495,6 @@ struct VarRegionAxis
{
TRACE_SANITIZE (this);
return_trace (c->check_struct (this));
- /* TODO Handle invalid start/peak/end configs, so we don't
- * have to do that at runtime. */
}
bool serialize (hb_serialize_context_t *c) const
@@ -2511,6 +2510,33 @@ struct VarRegionAxis
public:
DEFINE_SIZE_STATIC (6);
};
+struct SparseVarRegionAxis
+{
+ float evaluate (const int *coords, unsigned int coord_len) const
+ {
+ unsigned i = axisIndex;
+ int coord = i < coord_len ? coords[i] : 0;
+ return axis.evaluate (coord);
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (c->check_struct (this));
+ }
+
+ bool serialize (hb_serialize_context_t *c) const
+ {
+ TRACE_SERIALIZE (this);
+ return_trace (c->embed (this));
+ }
+
+ public:
+ HBUINT16 axisIndex;
+ VarRegionAxis axis;
+ public:
+ DEFINE_SIZE_STATIC (8);
+};
#define REGION_CACHE_ITEM_CACHE_INVALID 2.f
@@ -2675,6 +2701,65 @@ struct VarRegionList
DEFINE_SIZE_ARRAY (4, axesZ);
};
+struct SparseVariationRegion : Array16Of<SparseVarRegionAxis>
+{
+ float evaluate (const int *coords, unsigned int coord_len) const
+ {
+ float v = 1.f;
+ unsigned int count = len;
+ for (unsigned int i = 0; i < count; i++)
+ {
+ float factor = arrayZ[i].evaluate (coords, coord_len);
+ if (factor == 0.f)
+ return 0.;
+ v *= factor;
+ }
+ return v;
+ }
+};
+
+struct SparseVarRegionList
+{
+ using cache_t = float;
+
+ float evaluate (unsigned int region_index,
+ const int *coords, unsigned int coord_len,
+ cache_t *cache = nullptr) const
+ {
+ if (unlikely (region_index >= regions.len))
+ return 0.;
+
+ float *cached_value = nullptr;
+ if (cache)
+ {
+ cached_value = &(cache[region_index]);
+ if (likely (*cached_value != REGION_CACHE_ITEM_CACHE_INVALID))
+ return *cached_value;
+ }
+
+ const SparseVariationRegion &region = this+regions[region_index];
+
+ float v = region.evaluate (coords, coord_len);
+
+ if (cache)
+ *cached_value = v;
+ return v;
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (regions.sanitize (c, this));
+ }
+
+ public:
+ Array16Of<Offset32To<SparseVariationRegion>>
+ regions;
+ public:
+ DEFINE_SIZE_ARRAY (2, regions);
+};
+
+
struct VarData
{
unsigned int get_item_count () const
@@ -2682,7 +2767,7 @@ struct VarData
unsigned int get_region_index_count () const
{ return regionIndices.len; }
-
+
unsigned get_region_index (unsigned i) const
{ return i >= regionIndices.len ? -1 : regionIndices[i]; }
@@ -3036,6 +3121,61 @@ struct VarData
DEFINE_SIZE_ARRAY (6, regionIndices);
};
+struct MultiVarData
+{
+ unsigned int get_size () const
+ { return min_size
+ - regionIndices.min_size + regionIndices.get_size ()
+ + StructAfter<CFF2Index> (regionIndices).get_size ();
+ }
+
+ void get_delta (unsigned int inner,
+ const int *coords, unsigned int coord_count,
+ const SparseVarRegionList &regions,
+ hb_array_t<float> out,
+ SparseVarRegionList::cache_t *cache = nullptr) const
+ {
+ auto &deltaSets = StructAfter<decltype (deltaSetsX)> (regionIndices);
+
+ auto values_iter = deltaSets[inner];
+
+ unsigned regionCount = regionIndices.len;
+ unsigned count = out.length;
+ for (unsigned regionIndex = 0; regionIndex < regionCount; regionIndex++)
+ {
+ float scalar = regions.evaluate (regionIndices.arrayZ[regionIndex],
+ coords, coord_count,
+ cache);
+ if (scalar == 1.f)
+ for (unsigned i = 0; i < count; i++)
+ out.arrayZ[i] += *values_iter++;
+ else if (scalar)
+ for (unsigned i = 0; i < count; i++)
+ out.arrayZ[i] += *values_iter++ * scalar;
+ else
+ values_iter += count;
+ }
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (format.sanitize (c) &&
+ hb_barrier () &&
+ format == 1 &&
+ regionIndices.sanitize (c) &&
+ hb_barrier () &&
+ StructAfter<decltype (deltaSetsX)> (regionIndices).sanitize (c));
+ }
+
+ protected:
+ HBUINT8 format; // 1
+ Array16Of<HBUINT16> regionIndices;
+ TupleList deltaSetsX;
+ public:
+ DEFINE_SIZE_MIN (8);
+};
+
struct ItemVariationStore
{
friend struct item_variations_t;
@@ -3088,7 +3228,7 @@ struct ItemVariationStore
return get_delta (outer, inner, coords, coord_count, cache);
}
float get_delta (unsigned int index,
- hb_array_t<int> coords,
+ hb_array_t<const int> coords,
VarRegionList::cache_t *cache = nullptr) const
{
return get_delta (index,
@@ -3121,7 +3261,7 @@ struct ItemVariationStore
return_trace (false);
#endif
if (unlikely (!c->extend_min (this))) return_trace (false);
-
+
format = 1;
if (!regions.serialize_serialize (c, axis_tags, region_list))
return_trace (false);
@@ -3136,7 +3276,7 @@ struct ItemVariationStore
for (unsigned i = 0; i < num_var_data; i++)
if (!dataSets[i].serialize_serialize (c, has_long, vardata_encodings[i].items))
return_trace (false);
-
+
return_trace (true);
}
@@ -3295,8 +3435,358 @@ struct ItemVariationStore
DEFINE_SIZE_ARRAY_SIZED (8, dataSets);
};
+struct MultiItemVariationStore
+{
+ using cache_t = SparseVarRegionList::cache_t;
+
+ cache_t *create_cache () const
+ {
+#ifdef HB_NO_VAR
+ return nullptr;
+#endif
+ auto &r = this+regions;
+ unsigned count = r.regions.len;
+
+ float *cache = (float *) hb_malloc (sizeof (float) * count);
+ if (unlikely (!cache)) return nullptr;
+
+ for (unsigned i = 0; i < count; i++)
+ cache[i] = REGION_CACHE_ITEM_CACHE_INVALID;
+
+ return cache;
+ }
+
+ static void destroy_cache (cache_t *cache) { hb_free (cache); }
+
+ private:
+ void get_delta (unsigned int outer, unsigned int inner,
+ const int *coords, unsigned int coord_count,
+ hb_array_t<float> out,
+ VarRegionList::cache_t *cache = nullptr) const
+ {
+#ifdef HB_NO_VAR
+ return;
+#endif
+
+ if (unlikely (outer >= dataSets.len))
+ return;
+
+ return (this+dataSets[outer]).get_delta (inner,
+ coords, coord_count,
+ this+regions,
+ out,
+ cache);
+ }
+
+ public:
+ void get_delta (unsigned int index,
+ const int *coords, unsigned int coord_count,
+ hb_array_t<float> out,
+ VarRegionList::cache_t *cache = nullptr) const
+ {
+ unsigned int outer = index >> 16;
+ unsigned int inner = index & 0xFFFF;
+ get_delta (outer, inner, coords, coord_count, out, cache);
+ }
+ void get_delta (unsigned int index,
+ hb_array_t<const int> coords,
+ hb_array_t<float> out,
+ VarRegionList::cache_t *cache = nullptr) const
+ {
+ return get_delta (index,
+ coords.arrayZ, coords.length,
+ out,
+ cache);
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+#ifdef HB_NO_VAR
+ return true;
+#endif
+
+ TRACE_SANITIZE (this);
+ return_trace (c->check_struct (this) &&
+ hb_barrier () &&
+ format == 1 &&
+ regions.sanitize (c, this) &&
+ dataSets.sanitize (c, this));
+ }
+
+ protected:
+ HBUINT16 format; // 1
+ Offset32To<SparseVarRegionList> regions;
+ Array16OfOffset32To<MultiVarData> dataSets;
+ public:
+ DEFINE_SIZE_ARRAY_SIZED (8, dataSets);
+};
+
#undef REGION_CACHE_ITEM_CACHE_INVALID
+template <typename MapCountT>
+struct DeltaSetIndexMapFormat01
+{
+ friend struct DeltaSetIndexMap;
+
+ unsigned get_size () const
+ { return min_size + mapCount * get_width (); }
+
+ private:
+ DeltaSetIndexMapFormat01* copy (hb_serialize_context_t *c) const
+ {
+ TRACE_SERIALIZE (this);
+ return_trace (c->embed (this));
+ }
+
+ template <typename T>
+ bool serialize (hb_serialize_context_t *c, const T &plan)
+ {
+ unsigned int width = plan.get_width ();
+ unsigned int inner_bit_count = plan.get_inner_bit_count ();
+ const hb_array_t<const uint32_t> output_map = plan.get_output_map ();
+
+ TRACE_SERIALIZE (this);
+ if (unlikely (output_map.length && ((((inner_bit_count-1)&~0xF)!=0) || (((width-1)&~0x3)!=0))))
+ return_trace (false);
+ if (unlikely (!c->extend_min (this))) return_trace (false);
+
+ entryFormat = ((width-1)<<4)|(inner_bit_count-1);
+ mapCount = output_map.length;
+ HBUINT8 *p = c->allocate_size<HBUINT8> (width * output_map.length);
+ if (unlikely (!p)) return_trace (false);
+ for (unsigned int i = 0; i < output_map.length; i++)
+ {
+ unsigned int v = output_map.arrayZ[i];
+ if (v)
+ {
+ unsigned int outer = v >> 16;
+ unsigned int inner = v & 0xFFFF;
+ unsigned int u = (outer << inner_bit_count) | inner;
+ for (unsigned int w = width; w > 0;)
+ {
+ p[--w] = u;
+ u >>= 8;
+ }
+ }
+ p += width;
+ }
+ return_trace (true);
+ }
+
+ uint32_t map (unsigned int v) const /* Returns 16.16 outer.inner. */
+ {
+ /* If count is zero, pass value unchanged. This takes
+ * care of direct mapping for advance map. */
+ if (!mapCount)
+ return v;
+
+ if (v >= mapCount)
+ v = mapCount - 1;
+
+ unsigned int u = 0;
+ { /* Fetch it. */
+ unsigned int w = get_width ();
+ const HBUINT8 *p = mapDataZ.arrayZ + w * v;
+ for (; w; w--)
+ u = (u << 8) + *p++;
+ }
+
+ { /* Repack it. */
+ unsigned int n = get_inner_bit_count ();
+ unsigned int outer = u >> n;
+ unsigned int inner = u & ((1 << n) - 1);
+ u = (outer<<16) | inner;
+ }
+
+ return u;
+ }
+
+ unsigned get_map_count () const { return mapCount; }
+ unsigned get_width () const { return ((entryFormat >> 4) & 3) + 1; }
+ unsigned get_inner_bit_count () const { return (entryFormat & 0xF) + 1; }
+
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (c->check_struct (this) &&
+ hb_barrier () &&
+ c->check_range (mapDataZ.arrayZ,
+ mapCount,
+ get_width ()));
+ }
+
+ protected:
+ HBUINT8 format; /* Format identifier--format = 0 */
+ HBUINT8 entryFormat; /* A packed field that describes the compressed
+ * representation of delta-set indices. */
+ MapCountT mapCount; /* The number of mapping entries. */
+ UnsizedArrayOf<HBUINT8>
+ mapDataZ; /* The delta-set index mapping data. */
+
+ public:
+ DEFINE_SIZE_ARRAY (2+MapCountT::static_size, mapDataZ);
+};
+
+struct DeltaSetIndexMap
+{
+ template <typename T>
+ bool serialize (hb_serialize_context_t *c, const T &plan)
+ {
+ TRACE_SERIALIZE (this);
+ unsigned length = plan.get_output_map ().length;
+ u.format = length <= 0xFFFF ? 0 : 1;
+ switch (u.format) {
+ case 0: hb_barrier (); return_trace (u.format0.serialize (c, plan));
+ case 1: hb_barrier (); return_trace (u.format1.serialize (c, plan));
+ default:return_trace (false);
+ }
+ }
+
+ uint32_t map (unsigned v) const
+ {
+ switch (u.format) {
+ case 0: hb_barrier (); return (u.format0.map (v));
+ case 1: hb_barrier (); return (u.format1.map (v));
+ default:return v;
+ }
+ }
+
+ unsigned get_map_count () const
+ {
+ switch (u.format) {
+ case 0: hb_barrier (); return u.format0.get_map_count ();
+ case 1: hb_barrier (); return u.format1.get_map_count ();
+ default:return 0;
+ }
+ }
+
+ unsigned get_width () const
+ {
+ switch (u.format) {
+ case 0: hb_barrier (); return u.format0.get_width ();
+ case 1: hb_barrier (); return u.format1.get_width ();
+ default:return 0;
+ }
+ }
+
+ unsigned get_inner_bit_count () const
+ {
+ switch (u.format) {
+ case 0: hb_barrier (); return u.format0.get_inner_bit_count ();
+ case 1: hb_barrier (); return u.format1.get_inner_bit_count ();
+ default:return 0;
+ }
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ if (!u.format.sanitize (c)) return_trace (false);
+ hb_barrier ();
+ switch (u.format) {
+ case 0: hb_barrier (); return_trace (u.format0.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c));
+ default:return_trace (true);
+ }
+ }
+
+ DeltaSetIndexMap* copy (hb_serialize_context_t *c) const
+ {
+ TRACE_SERIALIZE (this);
+ switch (u.format) {
+ case 0: hb_barrier (); return_trace (reinterpret_cast<DeltaSetIndexMap *> (u.format0.copy (c)));
+ case 1: hb_barrier (); return_trace (reinterpret_cast<DeltaSetIndexMap *> (u.format1.copy (c)));
+ default:return_trace (nullptr);
+ }
+ }
+
+ protected:
+ union {
+ HBUINT8 format; /* Format identifier */
+ DeltaSetIndexMapFormat01<HBUINT16> format0;
+ DeltaSetIndexMapFormat01<HBUINT32> format1;
+ } u;
+ public:
+ DEFINE_SIZE_UNION (1, format);
+};
+
+
+struct ItemVarStoreInstancer
+{
+ ItemVarStoreInstancer (const ItemVariationStore *varStore,
+ const DeltaSetIndexMap *varIdxMap,
+ hb_array_t<const int> coords,
+ VarRegionList::cache_t *cache = nullptr) :
+ varStore (varStore), varIdxMap (varIdxMap), coords (coords), cache (cache)
+ {
+ if (!varStore)
+ varStore = &Null(ItemVariationStore);
+ }
+
+ operator bool () const { return varStore && bool (coords); }
+
+ float operator[] (uint32_t varIdx) const
+ { return (*this) (varIdx); }
+
+ float operator() (uint32_t varIdx, unsigned short offset = 0) const
+ {
+ if (varIdxMap)
+ varIdx = varIdxMap->map (VarIdx::add (varIdx, offset));
+ else
+ varIdx += offset;
+ return coords ? varStore->get_delta (varIdx, coords, cache) : 0.f;
+ }
+
+ const ItemVariationStore *varStore;
+ const DeltaSetIndexMap *varIdxMap;
+ hb_array_t<const int> coords;
+ VarRegionList::cache_t *cache;
+};
+
+struct MultiItemVarStoreInstancer
+{
+ MultiItemVarStoreInstancer (const MultiItemVariationStore *varStore,
+ const DeltaSetIndexMap *varIdxMap,
+ hb_array_t<const int> coords,
+ SparseVarRegionList::cache_t *cache = nullptr) :
+ varStore (varStore), varIdxMap (varIdxMap), coords (coords), cache (cache)
+ {
+ if (!varStore)
+ varStore = &Null(MultiItemVariationStore);
+ }
+
+ operator bool () const { return varStore && bool (coords); }
+
+ float operator[] (uint32_t varIdx) const
+ {
+ float v = 0;
+ (*this) (hb_array (&v, 1), varIdx);
+ return v;
+ }
+
+ void operator() (hb_array_t<float> out, uint32_t varIdx, unsigned short offset = 0) const
+ {
+ if (coords)
+ {
+ if (varIdxMap)
+ varIdx = varIdxMap->map (VarIdx::add (varIdx, offset));
+ else
+ varIdx += offset;
+ varStore->get_delta (varIdx, coords, out, cache);
+ }
+ else
+ for (unsigned i = 0; i < out.length; i++)
+ out.arrayZ[i] = 0.f;
+ }
+
+ const MultiItemVariationStore *varStore;
+ const DeltaSetIndexMap *varIdxMap;
+ hb_array_t<const int> coords;
+ SparseVarRegionList::cache_t *cache;
+};
+
+
/*
* Feature Variations
*/
@@ -3308,7 +3798,16 @@ enum Cond_with_Var_flag_t
DROP_RECORD_WITH_VAR = 3,
};
-struct ConditionFormat1
+struct Condition;
+
+template <typename Instancer>
+static bool
+_hb_recurse_condition_evaluate (const struct Condition &condition,
+ const int *coords,
+ unsigned int coord_len,
+ Instancer *instancer);
+
+struct ConditionAxisRange
{
friend struct Condition;
@@ -3401,7 +3900,9 @@ struct ConditionFormat1
return KEEP_RECORD_WITH_VAR;
}
- bool evaluate (const int *coords, unsigned int coord_len) const
+ template <typename Instancer>
+ bool evaluate (const int *coords, unsigned int coord_len,
+ Instancer *instancer HB_UNUSED) const
{
int coord = axisIndex < coord_len ? coords[axisIndex] : 0;
return filterRangeMinValue.to_int () <= coord && coord <= filterRangeMaxValue.to_int ();
@@ -3422,12 +3923,199 @@ struct ConditionFormat1
DEFINE_SIZE_STATIC (8);
};
+struct ConditionValue
+{
+ friend struct Condition;
+
+ bool subset (hb_subset_context_t *c) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ private:
+ template <typename Instancer>
+ bool evaluate (const int *coords, unsigned int coord_len,
+ Instancer *instancer) const
+ {
+ signed value = defaultValue;
+ value += (*instancer)[varIdx];
+ return value > 0;
+ }
+
+ bool subset (hb_subset_context_t *c,
+ hb_subset_layout_context_t *l,
+ bool insert_catch_all) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (c->check_struct (this));
+ }
+
+ protected:
+ HBUINT16 format; /* Format identifier--format = 2 */
+ HBINT16 defaultValue; /* Value at default instance. */
+ VarIdx varIdx; /* Variation index */
+ public:
+ DEFINE_SIZE_STATIC (8);
+};
+
+struct ConditionAnd
+{
+ friend struct Condition;
+
+ bool subset (hb_subset_context_t *c) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ private:
+ template <typename Instancer>
+ bool evaluate (const int *coords, unsigned int coord_len,
+ Instancer *instancer) const
+ {
+ unsigned int count = conditions.len;
+ for (unsigned int i = 0; i < count; i++)
+ if (!_hb_recurse_condition_evaluate (this+conditions.arrayZ[i],
+ coords, coord_len,
+ instancer))
+ return false;
+ return true;
+ }
+
+ bool subset (hb_subset_context_t *c,
+ hb_subset_layout_context_t *l,
+ bool insert_catch_all) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (conditions.sanitize (c, this));
+ }
+
+ protected:
+ HBUINT16 format; /* Format identifier--format = 3 */
+ Array8OfOffset24To<struct Condition> conditions;
+ public:
+ DEFINE_SIZE_ARRAY (3, conditions);
+};
+
+struct ConditionOr
+{
+ friend struct Condition;
+
+ bool subset (hb_subset_context_t *c) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ private:
+ template <typename Instancer>
+ bool evaluate (const int *coords, unsigned int coord_len,
+ Instancer *instancer) const
+ {
+ unsigned int count = conditions.len;
+ for (unsigned int i = 0; i < count; i++)
+ if (_hb_recurse_condition_evaluate (this+conditions.arrayZ[i],
+ coords, coord_len,
+ instancer))
+ return true;
+ return false;
+ }
+
+ bool subset (hb_subset_context_t *c,
+ hb_subset_layout_context_t *l,
+ bool insert_catch_all) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (conditions.sanitize (c, this));
+ }
+
+ protected:
+ HBUINT16 format; /* Format identifier--format = 4 */
+ Array8OfOffset24To<struct Condition> conditions;
+ public:
+ DEFINE_SIZE_ARRAY (3, conditions);
+};
+
+struct ConditionNegate
+{
+ friend struct Condition;
+
+ bool subset (hb_subset_context_t *c) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ private:
+ template <typename Instancer>
+ bool evaluate (const int *coords, unsigned int coord_len,
+ Instancer *instancer) const
+ {
+ return !_hb_recurse_condition_evaluate (this+condition,
+ coords, coord_len,
+ instancer);
+ }
+
+ bool subset (hb_subset_context_t *c,
+ hb_subset_layout_context_t *l,
+ bool insert_catch_all) const
+ {
+ TRACE_SUBSET (this);
+ // TODO(subset)
+ return_trace (false);
+ }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (condition.sanitize (c, this));
+ }
+
+ protected:
+ HBUINT16 format; /* Format identifier--format = 5 */
+ Offset24To<struct Condition> condition;
+ public:
+ DEFINE_SIZE_STATIC (5);
+};
+
struct Condition
{
- bool evaluate (const int *coords, unsigned int coord_len) const
+ template <typename Instancer>
+ bool evaluate (const int *coords, unsigned int coord_len,
+ Instancer *instancer) const
{
switch (u.format) {
- case 1: return u.format1.evaluate (coords, coord_len);
+ case 1: hb_barrier (); return u.format1.evaluate (coords, coord_len, instancer);
+ case 2: hb_barrier (); return u.format2.evaluate (coords, coord_len, instancer);
+ case 3: hb_barrier (); return u.format3.evaluate (coords, coord_len, instancer);
+ case 4: hb_barrier (); return u.format4.evaluate (coords, coord_len, instancer);
+ case 5: hb_barrier (); return u.format5.evaluate (coords, coord_len, instancer);
default:return false;
}
}
@@ -3436,7 +4124,8 @@ struct Condition
hb_map_t *condition_map /* OUT */) const
{
switch (u.format) {
- case 1: return u.format1.keep_with_variations (c, condition_map);
+ case 1: hb_barrier (); return u.format1.keep_with_variations (c, condition_map);
+ // TODO(subset)
default: c->apply = false; return KEEP_COND_WITH_VAR;
}
}
@@ -3447,7 +4136,11 @@ struct Condition
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
- case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
+ case 1: hb_barrier (); return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
+ case 2: hb_barrier (); return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
+ case 3: hb_barrier (); return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
+ case 4: hb_barrier (); return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
+ case 5: hb_barrier (); return_trace (c->dispatch (u.format5, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
@@ -3458,7 +4151,11 @@ struct Condition
if (!u.format.sanitize (c)) return_trace (false);
hb_barrier ();
switch (u.format) {
- case 1: return_trace (u.format1.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c));
+ case 3: hb_barrier (); return_trace (u.format3.sanitize (c));
+ case 4: hb_barrier (); return_trace (u.format4.sanitize (c));
+ case 5: hb_barrier (); return_trace (u.format5.sanitize (c));
default:return_trace (true);
}
}
@@ -3466,19 +4163,51 @@ struct Condition
protected:
union {
HBUINT16 format; /* Format identifier */
- ConditionFormat1 format1;
+ ConditionAxisRange format1;
+ ConditionValue format2;
+ ConditionAnd format3;
+ ConditionOr format4;
+ ConditionNegate format5;
} u;
public:
DEFINE_SIZE_UNION (2, format);
};
+template <typename Instancer>
+bool
+_hb_recurse_condition_evaluate (const struct Condition &condition,
+ const int *coords,
+ unsigned int coord_len,
+ Instancer *instancer)
+{
+ return condition.evaluate (coords, coord_len, instancer);
+}
+
+struct ConditionList
+{
+ const Condition& operator[] (unsigned i) const
+ { return this+conditions[i]; }
+
+ bool sanitize (hb_sanitize_context_t *c) const
+ {
+ TRACE_SANITIZE (this);
+ return_trace (conditions.sanitize (c, this));
+ }
+
+ protected:
+ Array32OfOffset32To<Condition> conditions;
+ public:
+ DEFINE_SIZE_ARRAY (4, conditions);
+};
+
struct ConditionSet
{
- bool evaluate (const int *coords, unsigned int coord_len) const
+ bool evaluate (const int *coords, unsigned int coord_len,
+ ItemVarStoreInstancer *instancer) const
{
unsigned int count = conditions.len;
for (unsigned int i = 0; i < count; i++)
- if (!(this+conditions.arrayZ[i]).evaluate (coords, coord_len))
+ if (!(this+conditions.arrayZ[i]).evaluate (coords, coord_len, instancer))
return false;
return true;
}
@@ -3812,13 +4541,14 @@ struct FeatureVariations
static constexpr unsigned NOT_FOUND_INDEX = 0xFFFFFFFFu;
bool find_index (const int *coords, unsigned int coord_len,
- unsigned int *index) const
+ unsigned int *index,
+ ItemVarStoreInstancer *instancer) const
{
unsigned int count = varRecords.len;
for (unsigned int i = 0; i < count; i++)
{
const FeatureVariationRecord &record = varRecords.arrayZ[i];
- if ((this+record.conditions).evaluate (coords, coord_len))
+ if ((this+record.conditions).evaluate (coords, coord_len, instancer))
{
*index = i;
return true;
@@ -4079,7 +4809,7 @@ struct VariationDevice
}
protected:
- VarIdx varIdx;
+ VarIdx varIdx; /* Variation index */
HBUINT16 deltaFormat; /* Format identifier for this table: 0x0x8000 */
public:
DEFINE_SIZE_STATIC (6);
diff --git a/thirdparty/harfbuzz/src/hb-ot-layout-gsubgpos.hh b/thirdparty/harfbuzz/src/hb-ot-layout-gsubgpos.hh
index 6b760b1108..2c9056c705 100644
--- a/thirdparty/harfbuzz/src/hb-ot-layout-gsubgpos.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-layout-gsubgpos.hh
@@ -406,6 +406,7 @@ struct hb_ot_apply_context_t :
void set_ignore_zwnj (bool ignore_zwnj_) { ignore_zwnj = ignore_zwnj_; }
void set_ignore_zwj (bool ignore_zwj_) { ignore_zwj = ignore_zwj_; }
+ void set_ignore_hidden (bool ignore_hidden_) { ignore_hidden = ignore_hidden_; }
void set_lookup_props (unsigned int lookup_props_) { lookup_props = lookup_props_; }
void set_mask (hb_mask_t mask_) { mask = mask_; }
void set_per_syllable (bool per_syllable_) { per_syllable = per_syllable_; }
@@ -451,9 +452,10 @@ struct hb_ot_apply_context_t :
if (!c->check_glyph_property (&info, lookup_props))
return SKIP_YES;
- if (unlikely (_hb_glyph_info_is_default_ignorable_and_not_hidden (&info) &&
+ if (unlikely (_hb_glyph_info_is_default_ignorable (&info) &&
(ignore_zwnj || !_hb_glyph_info_is_zwnj (&info)) &&
- (ignore_zwj || !_hb_glyph_info_is_zwj (&info))))
+ (ignore_zwj || !_hb_glyph_info_is_zwj (&info)) &&
+ (ignore_hidden || !_hb_glyph_info_is_hidden (&info))))
return SKIP_MAYBE;
return SKIP_NO;
@@ -464,6 +466,7 @@ struct hb_ot_apply_context_t :
hb_mask_t mask = -1;
bool ignore_zwnj = false;
bool ignore_zwj = false;
+ bool ignore_hidden = false;
bool per_syllable = false;
uint8_t syllable = 0;
match_func_t match_func = nullptr;
@@ -486,6 +489,8 @@ struct hb_ot_apply_context_t :
matcher.set_ignore_zwnj (c->table_index == 1 || (context_match && c->auto_zwnj));
/* Ignore ZWJ if we are matching context, or asked to. */
matcher.set_ignore_zwj (context_match || c->auto_zwj);
+ /* Ignore hidden glyphs (like CGJ) during GPOS. */
+ matcher.set_ignore_hidden (c->table_index == 1);
matcher.set_mask (context_match ? -1 : c->lookup_mask);
/* Per syllable matching is only for GSUB. */
matcher.set_per_syllable (c->table_index == 0 && c->per_syllable);
@@ -2901,12 +2906,12 @@ struct Context
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
- case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
- case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
- case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
+ case 1: hb_barrier (); return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
+ case 2: hb_barrier (); return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
+ case 3: hb_barrier (); return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
- case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
- case 5: return_trace (c->dispatch (u.format5, std::forward<Ts> (ds)...));
+ case 4: hb_barrier (); return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
+ case 5: hb_barrier (); return_trace (c->dispatch (u.format5, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
@@ -3390,6 +3395,15 @@ struct ChainRuleSet
*
* Replicated from LigatureSet::apply(). */
+ /* If the input skippy has non-auto joiners behavior (as in Indic shapers),
+ * skip this fast path, as we don't distinguish between input & lookahead
+ * matching in the fast path.
+ *
+ * https://github.com/harfbuzz/harfbuzz/issues/4813
+ */
+ if (!c->auto_zwnj || !c->auto_zwj)
+ goto slow;
+
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
skippy_iter.reset (c->buffer->idx);
skippy_iter.set_match_func (match_always, nullptr);
@@ -3429,10 +3443,10 @@ struct ChainRuleSet
}
matched = skippy_iter.next ();
if (likely (matched && !skippy_iter.may_skip (c->buffer->info[skippy_iter.idx])))
- {
+ {
second = &c->buffer->info[skippy_iter.idx];
unsafe_to2 = skippy_iter.idx + 1;
- }
+ }
auto match_input = lookup_context.funcs.match[1];
auto match_lookahead = lookup_context.funcs.match[2];
@@ -4225,12 +4239,12 @@ struct ChainContext
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
- case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
- case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
- case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
+ case 1: hb_barrier (); return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
+ case 2: hb_barrier (); return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
+ case 3: hb_barrier (); return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
#ifndef HB_NO_BEYOND_64K
- case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
- case 5: return_trace (c->dispatch (u.format5, std::forward<Ts> (ds)...));
+ case 4: hb_barrier (); return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
+ case 5: hb_barrier (); return_trace (c->dispatch (u.format5, std::forward<Ts> (ds)...));
#endif
default:return_trace (c->default_return_value ());
}
@@ -4314,7 +4328,7 @@ struct Extension
unsigned int get_type () const
{
switch (u.format) {
- case 1: return u.format1.get_type ();
+ case 1: hb_barrier (); return u.format1.get_type ();
default:return 0;
}
}
@@ -4322,7 +4336,7 @@ struct Extension
const X& get_subtable () const
{
switch (u.format) {
- case 1: return u.format1.template get_subtable<typename T::SubTable> ();
+ case 1: hb_barrier (); return u.format1.template get_subtable<typename T::SubTable> ();
default:return Null (typename T::SubTable);
}
}
@@ -4334,7 +4348,7 @@ struct Extension
typename hb_subset_context_t::return_t dispatch (hb_subset_context_t *c, Ts&&... ds) const
{
switch (u.format) {
- case 1: return u.format1.subset (c);
+ case 1: hb_barrier (); return u.format1.subset (c);
default: return c->default_return_value ();
}
}
@@ -4345,7 +4359,7 @@ struct Extension
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
- case 1: return_trace (u.format1.dispatch (c, std::forward<Ts> (ds)...));
+ case 1: hb_barrier (); return_trace (u.format1.dispatch (c, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
@@ -4560,9 +4574,9 @@ struct GSUBGPOS
unsigned int get_size () const
{
switch (u.version.major) {
- case 1: return u.version1.get_size ();
+ case 1: hb_barrier (); return u.version1.get_size ();
#ifndef HB_NO_BEYOND_64K
- case 2: return u.version2.get_size ();
+ case 2: hb_barrier (); return u.version2.get_size ();
#endif
default: return u.version.static_size;
}
@@ -4575,9 +4589,9 @@ struct GSUBGPOS
if (unlikely (!u.version.sanitize (c))) return_trace (false);
hb_barrier ();
switch (u.version.major) {
- case 1: return_trace (u.version1.sanitize<TLookup> (c));
+ case 1: hb_barrier (); return_trace (u.version1.sanitize<TLookup> (c));
#ifndef HB_NO_BEYOND_64K
- case 2: return_trace (u.version2.sanitize<TLookup> (c));
+ case 2: hb_barrier (); return_trace (u.version2.sanitize<TLookup> (c));
#endif
default: return_trace (true);
}
@@ -4587,9 +4601,9 @@ struct GSUBGPOS
bool subset (hb_subset_layout_context_t *c) const
{
switch (u.version.major) {
- case 1: return u.version1.subset<TLookup> (c);
+ case 1: hb_barrier (); return u.version1.subset<TLookup> (c);
#ifndef HB_NO_BEYOND_64K
- case 2: return u.version2.subset<TLookup> (c);
+ case 2: hb_barrier (); return u.version2.subset<TLookup> (c);
#endif
default: return false;
}
@@ -4598,9 +4612,9 @@ struct GSUBGPOS
const ScriptList &get_script_list () const
{
switch (u.version.major) {
- case 1: return this+u.version1.scriptList;
+ case 1: hb_barrier (); return this+u.version1.scriptList;
#ifndef HB_NO_BEYOND_64K
- case 2: return this+u.version2.scriptList;
+ case 2: hb_barrier (); return this+u.version2.scriptList;
#endif
default: return Null (ScriptList);
}
@@ -4608,9 +4622,9 @@ struct GSUBGPOS
const FeatureList &get_feature_list () const
{
switch (u.version.major) {
- case 1: return this+u.version1.featureList;
+ case 1: hb_barrier (); return this+u.version1.featureList;
#ifndef HB_NO_BEYOND_64K
- case 2: return this+u.version2.featureList;
+ case 2: hb_barrier (); return this+u.version2.featureList;
#endif
default: return Null (FeatureList);
}
@@ -4618,9 +4632,9 @@ struct GSUBGPOS
unsigned int get_lookup_count () const
{
switch (u.version.major) {
- case 1: return (this+u.version1.lookupList).len;
+ case 1: hb_barrier (); return (this+u.version1.lookupList).len;
#ifndef HB_NO_BEYOND_64K
- case 2: return (this+u.version2.lookupList).len;
+ case 2: hb_barrier (); return (this+u.version2.lookupList).len;
#endif
default: return 0;
}
@@ -4628,9 +4642,9 @@ struct GSUBGPOS
const Lookup& get_lookup (unsigned int i) const
{
switch (u.version.major) {
- case 1: return (this+u.version1.lookupList)[i];
+ case 1: hb_barrier (); return (this+u.version1.lookupList)[i];
#ifndef HB_NO_BEYOND_64K
- case 2: return (this+u.version2.lookupList)[i];
+ case 2: hb_barrier (); return (this+u.version2.lookupList)[i];
#endif
default: return Null (Lookup);
}
@@ -4638,9 +4652,9 @@ struct GSUBGPOS
const FeatureVariations &get_feature_variations () const
{
switch (u.version.major) {
- case 1: return (u.version.to_int () >= 0x00010001u ? this+u.version1.featureVars : Null (FeatureVariations));
+ case 1: hb_barrier (); return (u.version.to_int () >= 0x00010001u && hb_barrier () ? this+u.version1.featureVars : Null (FeatureVariations));
#ifndef HB_NO_BEYOND_64K
- case 2: return this+u.version2.featureVars;
+ case 2: hb_barrier (); return this+u.version2.featureVars;
#endif
default: return Null (FeatureVariations);
}
@@ -4674,13 +4688,14 @@ struct GSUBGPOS
{ return get_feature_list ().find_index (tag, index); }
bool find_variations_index (const int *coords, unsigned int num_coords,
- unsigned int *index) const
+ unsigned int *index,
+ ItemVarStoreInstancer *instancer) const
{
#ifdef HB_NO_VAR
*index = FeatureVariations::NOT_FOUND_INDEX;
return false;
#endif
- return get_feature_variations ().find_index (coords, num_coords, index);
+ return get_feature_variations ().find_index (coords, num_coords, index, instancer);
}
const Feature& get_feature_variation (unsigned int feature_index,
unsigned int variations_index) const
diff --git a/thirdparty/harfbuzz/src/hb-ot-layout.cc b/thirdparty/harfbuzz/src/hb-ot-layout.cc
index 613c97fd9e..66c2eb4d8e 100644
--- a/thirdparty/harfbuzz/src/hb-ot-layout.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-layout.cc
@@ -1443,8 +1443,12 @@ hb_ot_layout_table_find_feature_variations (hb_face_t *face,
unsigned int *variations_index /* out */)
{
const OT::GSUBGPOS &g = get_gsubgpos_table (face, table_tag);
+ const OT::GDEF &gdef = *face->table.GDEF->table;
- return g.find_variations_index (coords, num_coords, variations_index);
+ auto instancer = OT::ItemVarStoreInstancer(&gdef.get_var_store(), nullptr,
+ hb_array (coords, num_coords));
+
+ return g.find_variations_index (coords, num_coords, variations_index, &instancer);
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-layout.hh b/thirdparty/harfbuzz/src/hb-ot-layout.hh
index d71889331d..3a8d36ac49 100644
--- a/thirdparty/harfbuzz/src/hb-ot-layout.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-layout.hh
@@ -173,12 +173,12 @@ _hb_next_syllable (hb_buffer_t *buffer, unsigned int start)
/* Design:
* unicode_props() is a two-byte number. The low byte includes:
- * - General_Category: 5 bits.
+ * - Extended General_Category: 5 bits.
* - A bit each for:
* * Is it Default_Ignorable(); we have a modified Default_Ignorable().
* * Whether it's one of the four Mongolian Free Variation Selectors,
* CGJ, or other characters that are hidden but should not be ignored
- * like most other Default_Ignorable()s do during matching.
+ * like most other Default_Ignorable()s do during GSUB matching.
* * Whether it's a grapheme continuation.
*
* The high-byte has different meanings, switched by the Gen-Cat:
@@ -311,12 +311,15 @@ _hb_glyph_info_is_default_ignorable (const hb_glyph_info_t *info)
return (info->unicode_props() & UPROPS_MASK_IGNORABLE) &&
!_hb_glyph_info_substituted (info);
}
+static inline void
+_hb_glyph_info_clear_default_ignorable (hb_glyph_info_t *info)
+{
+ info->unicode_props() &= ~ UPROPS_MASK_IGNORABLE;
+}
static inline bool
-_hb_glyph_info_is_default_ignorable_and_not_hidden (const hb_glyph_info_t *info)
+_hb_glyph_info_is_hidden (const hb_glyph_info_t *info)
{
- return ((info->unicode_props() & (UPROPS_MASK_IGNORABLE|UPROPS_MASK_HIDDEN))
- == UPROPS_MASK_IGNORABLE) &&
- !_hb_glyph_info_substituted (info);
+ return info->unicode_props() & UPROPS_MASK_HIDDEN;
}
static inline void
_hb_glyph_info_unhide (hb_glyph_info_t *info)
diff --git a/thirdparty/harfbuzz/src/hb-ot-post-table-v2subset.hh b/thirdparty/harfbuzz/src/hb-ot-post-table-v2subset.hh
index d44233610a..67d1b6aa71 100644
--- a/thirdparty/harfbuzz/src/hb-ot-post-table-v2subset.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-post-table-v2subset.hh
@@ -84,9 +84,9 @@ HB_INTERNAL bool postV2Tail::subset (hb_subset_context_t *c) const
old_gid_new_index_map.alloc (num_glyphs);
glyph_name_to_new_index.alloc (num_glyphs);
- for (hb_codepoint_t new_gid = 0; new_gid < num_glyphs; new_gid++)
+ for (auto _ : c->plan->new_to_old_gid_list)
{
- hb_codepoint_t old_gid = reverse_glyph_map.get (new_gid);
+ hb_codepoint_t old_gid = _.second;
unsigned old_index = glyphNameIndex[old_gid];
unsigned new_index;
@@ -125,13 +125,22 @@ HB_INTERNAL bool postV2Tail::subset (hb_subset_context_t *c) const
old_gid_new_index_map.set (old_gid, new_index);
}
+ if (old_gid_new_index_map.in_error())
+ return_trace (false);
+
auto index_iter =
+ hb_range (num_glyphs)
- | hb_map (reverse_glyph_map)
- | hb_map_retains_sorting ([&](hb_codepoint_t old_gid)
+ | hb_map_retains_sorting ([&](hb_codepoint_t new_gid)
{
- unsigned new_index = old_gid_new_index_map.get (old_gid);
- return hb_pair_t<unsigned, unsigned> (old_gid, new_index);
+ hb_codepoint_t *old_gid;
+ /* use 0 for retain-gid holes, which refers to the name .notdef,
+ * as the glyphNameIndex entry for that glyph ID."*/
+ unsigned new_index = 0;
+ if (reverse_glyph_map.has (new_gid, &old_gid)) {
+ new_index = old_gid_new_index_map.get (*old_gid);
+ return hb_pair_t<unsigned, unsigned> (*old_gid, new_index);
+ }
+ return hb_pair_t<unsigned, unsigned> (new_gid, new_index);
})
;
diff --git a/thirdparty/harfbuzz/src/hb-ot-post-table.hh b/thirdparty/harfbuzz/src/hb-ot-post-table.hh
index 4191879037..18ecea1825 100644
--- a/thirdparty/harfbuzz/src/hb-ot-post-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-post-table.hh
@@ -301,7 +301,7 @@ struct post
return_trace (c->check_struct (this) &&
hb_barrier () &&
(version.to_int () == 0x00010000 ||
- (version.to_int () == 0x00020000 && v2X.sanitize (c)) ||
+ (version.to_int () == 0x00020000 && hb_barrier () && v2X.sanitize (c)) ||
version.to_int () == 0x00030000));
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc b/thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc
index 69dbec0783..b86649d778 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-shape-normalize.cc
@@ -74,23 +74,6 @@
* Indic shaper may want to disallow recomposing of two matras.
*/
-static bool
-decompose_unicode (const hb_ot_shape_normalize_context_t *c,
- hb_codepoint_t ab,
- hb_codepoint_t *a,
- hb_codepoint_t *b)
-{
- return (bool) c->unicode->decompose (ab, a, b);
-}
-
-static bool
-compose_unicode (const hb_ot_shape_normalize_context_t *c,
- hb_codepoint_t a,
- hb_codepoint_t b,
- hb_codepoint_t *ab)
-{
- return (bool) c->unicode->compose (a, b, ab);
-}
static inline void
set_glyph (hb_glyph_info_t &info, hb_font_t *font)
@@ -170,7 +153,7 @@ decompose_current_character (const hb_ot_shape_normalize_context_t *c, bool shor
hb_codepoint_t u = buffer->cur().codepoint;
hb_codepoint_t glyph = 0;
- if (shortest && c->font->get_nominal_glyph (u, &glyph, c->not_found))
+ if (shortest && c->font->get_nominal_glyph (u, &glyph, buffer->not_found))
{
next_char (buffer, glyph);
return;
@@ -182,7 +165,7 @@ decompose_current_character (const hb_ot_shape_normalize_context_t *c, bool shor
return;
}
- if (!shortest && c->font->get_nominal_glyph (u, &glyph, c->not_found))
+ if (!shortest && c->font->get_nominal_glyph (u, &glyph, buffer->not_found))
{
next_char (buffer, glyph);
return;
@@ -237,6 +220,13 @@ handle_variation_selector_cluster (const hb_ot_shape_normalize_context_t *c,
/* Just pass on the two characters separately, let GSUB do its magic. */
set_glyph (buffer->cur(), font);
(void) buffer->next_glyph ();
+
+ buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK;
+ _hb_glyph_info_set_general_category (&buffer->cur(),
+ _HB_UNICODE_GENERAL_CATEGORY_VARIATION_SELECTOR);
+ if (buffer->not_found_variation_selector != HB_CODEPOINT_INVALID)
+ _hb_glyph_info_clear_default_ignorable (&buffer->cur());
+
set_glyph (buffer->cur(), font);
(void) buffer->next_glyph ();
}
@@ -307,15 +297,15 @@ _hb_ot_shape_normalize (const hb_ot_shape_plan_t *plan,
mode = HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS;
}
- const hb_ot_shape_normalize_context_t c = {
+ hb_ot_shape_normalize_context_t c = {
plan,
buffer,
font,
buffer->unicode,
- buffer->not_found,
- plan->shaper->decompose ? plan->shaper->decompose : decompose_unicode,
- plan->shaper->compose ? plan->shaper->compose : compose_unicode
+ plan->shaper->decompose ? plan->shaper->decompose : hb_ot_shape_normalize_context_t::decompose_unicode,
+ plan->shaper->compose ? plan->shaper->compose : hb_ot_shape_normalize_context_t::compose_unicode
};
+ c.override_decompose_and_compose (plan->shaper->decompose, plan->shaper->compose);
bool always_short_circuit = mode == HB_OT_SHAPE_NORMALIZATION_MODE_NONE;
bool might_short_circuit = always_short_circuit ||
diff --git a/thirdparty/harfbuzz/src/hb-ot-shape-normalize.hh b/thirdparty/harfbuzz/src/hb-ot-shape-normalize.hh
index 12c78a2352..f12cb35c0f 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shape-normalize.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-shape-normalize.hh
@@ -28,6 +28,7 @@
#define HB_OT_SHAPE_NORMALIZE_HH
#include "hb.hh"
+#include "hb-unicode.hh"
/* buffer var allocations, used during the normalization process */
@@ -52,11 +53,42 @@ HB_INTERNAL void _hb_ot_shape_normalize (const hb_ot_shape_plan_t *shaper,
struct hb_ot_shape_normalize_context_t
{
+ static bool
+ decompose_unicode (const hb_ot_shape_normalize_context_t *c,
+ hb_codepoint_t ab,
+ hb_codepoint_t *a,
+ hb_codepoint_t *b)
+ {
+ return (bool) c->unicode->decompose (ab, a, b);
+ }
+
+ static bool
+ compose_unicode (const hb_ot_shape_normalize_context_t *c,
+ hb_codepoint_t a,
+ hb_codepoint_t b,
+ hb_codepoint_t *ab)
+ {
+ return (bool) c->unicode->compose (a, b, ab);
+ }
+
+ void
+ override_decompose_and_compose (bool (*decompose) (const hb_ot_shape_normalize_context_t *c,
+ hb_codepoint_t ab,
+ hb_codepoint_t *a,
+ hb_codepoint_t *b),
+ bool (*compose) (const hb_ot_shape_normalize_context_t *c,
+ hb_codepoint_t a,
+ hb_codepoint_t b,
+ hb_codepoint_t *ab))
+ {
+ this->decompose = decompose ? decompose : decompose_unicode;
+ this->compose = compose ? compose : compose_unicode;
+ }
+
const hb_ot_shape_plan_t *plan;
hb_buffer_t *buffer;
hb_font_t *font;
hb_unicode_funcs_t *unicode;
- const hb_codepoint_t not_found;
bool (*decompose) (const hb_ot_shape_normalize_context_t *c,
hb_codepoint_t ab,
hb_codepoint_t *a,
diff --git a/thirdparty/harfbuzz/src/hb-ot-shape.cc b/thirdparty/harfbuzz/src/hb-ot-shape.cc
index 148830022e..1aca39101e 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shape.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-shape.cc
@@ -85,7 +85,7 @@ hb_ot_shape_planner_t::hb_ot_shape_planner_t (hb_face_t *fac
, apply_morx (_hb_apply_morx (face, props))
#endif
{
- shaper = hb_ot_shaper_categorize (this);
+ shaper = hb_ot_shaper_categorize (props.script, props.direction, map.chosen_script[0]);
script_zero_marks = shaper->zero_width_marks != HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE;
script_fallback_mark_positioning = shaper->fallback_position;
@@ -838,6 +838,29 @@ hb_ot_zero_width_default_ignorables (const hb_buffer_t *buffer)
}
static void
+hb_ot_deal_with_variation_selectors (hb_buffer_t *buffer)
+{
+ if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_VARIATION_SELECTOR_FALLBACK) ||
+ buffer->not_found_variation_selector == HB_CODEPOINT_INVALID)
+ return;
+
+ unsigned int count = buffer->len;
+ hb_glyph_info_t *info = buffer->info;
+ hb_glyph_position_t *pos = buffer->pos;
+
+ for (unsigned int i = 0; i < count; i++)
+ {
+ if (_hb_glyph_info_get_general_category (&info[i]) ==
+ _HB_UNICODE_GENERAL_CATEGORY_VARIATION_SELECTOR)
+ {
+ info[i].codepoint = buffer->not_found_variation_selector;
+ pos[i].x_advance = pos[i].y_advance = pos[i].x_offset = pos[i].y_offset = 0;
+ _hb_glyph_info_set_general_category (&info[i], HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK);
+ }
+ }
+}
+
+static void
hb_ot_hide_default_ignorables (hb_buffer_t *buffer,
hb_font_t *font)
{
@@ -966,6 +989,7 @@ hb_ot_substitute_post (const hb_ot_shape_context_t *c)
hb_aat_layout_remove_deleted_glyphs (c->buffer);
#endif
+ hb_ot_deal_with_variation_selectors (c->buffer);
hb_ot_hide_default_ignorables (c->buffer, c->font);
if (c->plan->shaper->postprocess_glyphs &&
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-joining-list.hh b/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-joining-list.hh
index a5a7b84af4..e38686e3eb 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-joining-list.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-joining-list.hh
@@ -6,10 +6,10 @@
*
* on files with these headers:
*
- * # ArabicShaping-15.1.0.txt
- * # Date: 2023-01-05
- * # Scripts-15.1.0.txt
- * # Date: 2023-07-28, 16:01:07 GMT
+ * # ArabicShaping-16.0.0.txt
+ * # Date: 2024-07-30
+ * # Scripts-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:40 GMT
*/
#ifndef HB_OT_SHAPER_ARABIC_JOINING_LIST_HH
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-table.hh b/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-table.hh
index 336a1391e9..d9917a1587 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-arabic-table.hh
@@ -6,10 +6,10 @@
*
* on files with these headers:
*
- * # ArabicShaping-15.1.0.txt
- * # Date: 2023-01-05
- * # Blocks-15.1.0.txt
- * # Date: 2023-07-28, 15:47:20 GMT
+ * # ArabicShaping-16.0.0.txt
+ * # Date: 2024-07-30
+ * # Blocks-16.0.0.txt
+ * # Date: 2024-02-02
* UnicodeData.txt does not have a header.
*/
@@ -136,7 +136,13 @@ static const uint8_t joining_table[] =
/* 10D00 */ L,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 10D20 */ D,D,R,D,
-#define joining_offset_0x10f30u 1182
+#define joining_offset_0x10ec2u 1182
+
+ /* Arabic Extended-C */
+
+ /* 10EC0 */ R,D,D,
+
+#define joining_offset_0x10f30u 1185
/* Sogdian */
@@ -155,14 +161,14 @@ static const uint8_t joining_table[] =
/* 10FA0 */ D,U,D,D,R,R,R,U,D,R,R,D,D,R,D,D,
/* 10FC0 */ U,D,R,R,D,U,U,U,U,R,D,L,
-#define joining_offset_0x110bdu 1338
+#define joining_offset_0x110bdu 1341
/* Kaithi */
/* 110A0 */ U,X,X,
/* 110C0 */ X,X,X,X,X,X,X,X,X,X,X,X,X,U,
-#define joining_offset_0x1e900u 1355
+#define joining_offset_0x1e900u 1358
/* Adlam */
@@ -170,7 +176,7 @@ static const uint8_t joining_table[] =
/* 1E920 */ D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,D,
/* 1E940 */ D,D,D,D,X,X,X,X,X,X,X,T,
-}; /* Table items: 1431; occupancy: 57% */
+}; /* Table items: 1434; occupancy: 57% */
static unsigned int
@@ -198,6 +204,7 @@ joining_type (hb_codepoint_t u)
if (hb_in_range<hb_codepoint_t> (u, 0x10AC0u, 0x10AEFu)) return joining_table[u - 0x10AC0u + joining_offset_0x10ac0u];
if (hb_in_range<hb_codepoint_t> (u, 0x10B80u, 0x10BAFu)) return joining_table[u - 0x10B80u + joining_offset_0x10b80u];
if (hb_in_range<hb_codepoint_t> (u, 0x10D00u, 0x10D23u)) return joining_table[u - 0x10D00u + joining_offset_0x10d00u];
+ if (hb_in_range<hb_codepoint_t> (u, 0x10EC2u, 0x10EC4u)) return joining_table[u - 0x10EC2u + joining_offset_0x10ec2u];
if (hb_in_range<hb_codepoint_t> (u, 0x10F30u, 0x10FCBu)) return joining_table[u - 0x10F30u + joining_offset_0x10f30u];
break;
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-arabic.cc b/thirdparty/harfbuzz/src/hb-ot-shaper-arabic.cc
index d70746ed2b..4e3b512b48 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-arabic.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-arabic.cc
@@ -233,10 +233,7 @@ collect_features_arabic (hb_ot_shape_planner_t *plan)
map->enable_feature (HB_TAG('c','a','l','t'), F_MANUAL_ZWJ);
/* https://github.com/harfbuzz/harfbuzz/issues/1573 */
if (!map->has_feature (HB_TAG('r','c','l','t')))
- {
map->add_gsub_pause (nullptr);
- map->enable_feature (HB_TAG('r','c','l','t'), F_MANUAL_ZWJ);
- }
map->enable_feature (HB_TAG('l','i','g','a'), F_MANUAL_ZWJ);
map->enable_feature (HB_TAG('c','l','i','g'), F_MANUAL_ZWJ);
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-hebrew.cc b/thirdparty/harfbuzz/src/hb-ot-shaper-hebrew.cc
index e18edd6b3f..7e5482a96c 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-hebrew.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-hebrew.cc
@@ -78,7 +78,7 @@ compose_hebrew (const hb_ot_shape_normalize_context_t *c,
return found;
#endif
- if (!found && !c->plan->has_gpos_mark)
+ if (!found && (c->plan && !c->plan->has_gpos_mark))
{
/* Special-case Hebrew presentation forms that are excluded from
* standard normalization, but wanted for old fonts. */
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-indic-table.cc b/thirdparty/harfbuzz/src/hb-ot-shaper-indic-table.cc
index d9899a633c..adea32efda 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-indic-table.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-indic-table.cc
@@ -6,12 +6,12 @@
*
* on files with these headers:
*
- * # IndicSyllabicCategory-15.1.0.txt
- * # Date: 2023-01-05
- * # IndicPositionalCategory-15.1.0.txt
- * # Date: 2023-01-05
- * # Blocks-15.1.0.txt
- * # Date: 2023-07-28, 15:47:20 GMT
+ * # IndicSyllabicCategory-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:21 GMT
+ * # IndicPositionalCategory-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:21 GMT
+ * # Blocks-16.0.0.txt
+ * # Date: 2024-02-02
*/
#include "hb.hh"
@@ -89,7 +89,7 @@ static_assert (OT_VPst == M_Cat(VPst), "");
#define _OT_MW OT_MW /* 2 chars; MW */
#define _OT_MY OT_MY /* 3 chars; MY */
#define _OT_N OT_N /* 17 chars; N */
-#define _OT_GB OT_PLACEHOLDER /* 165 chars; PLACEHOLDER */
+#define _OT_GB OT_PLACEHOLDER /* 185 chars; PLACEHOLDER */
#define _OT_PT OT_PT /* 8 chars; PT */
#define _OT_R OT_Ra /* 14 chars; Ra */
#define _OT_Rf OT_Repha /* 1 chars; Repha */
@@ -112,7 +112,7 @@ static_assert (OT_VPst == M_Cat(VPst), "");
#define _POS_A POS_AFTER_MAIN /* 3 chars; AFTER_MAIN */
#define _POS_AP POS_AFTER_POST /* 50 chars; AFTER_POST */
#define _POS_AS POS_AFTER_SUB /* 51 chars; AFTER_SUB */
-#define _POS_C POS_BASE_C /* 833 chars; BASE_C */
+#define _POS_C POS_BASE_C /* 853 chars; BASE_C */
#define _POS_BS POS_BEFORE_SUB /* 25 chars; BEFORE_SUB */
#define _POS_B POS_BELOW_C /* 13 chars; BELOW_C */
#define _POS_X POS_END /* 71 chars; END */
@@ -458,7 +458,16 @@ static const uint16_t indic_table[] = {
/* 11338 */ _(X,X), _(X,X), _(X,X), _(N,X), _(N,X), _(X,X), _(X,X), _(X,X),
-}; /* Table items: 1728; occupancy: 71% */
+#define indic_offset_0x116d0u 1728
+
+
+ /* Myanmar Extended-C */
+
+ /* 116D0 */ _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C),
+ /* 116D8 */ _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(GB,C),
+ /* 116E0 */ _(GB,C), _(GB,C), _(GB,C), _(GB,C), _(X,X), _(X,X), _(X,X), _(X,X),
+
+}; /* Table items: 1752; occupancy: 71% */
uint16_t
hb_indic_get_categories (hb_codepoint_t u)
@@ -498,6 +507,7 @@ hb_indic_get_categories (hb_codepoint_t u)
case 0x11u:
if (hb_in_range<hb_codepoint_t> (u, 0x11300u, 0x11307u)) return indic_table[u - 0x11300u + indic_offset_0x11300u];
if (hb_in_range<hb_codepoint_t> (u, 0x11338u, 0x1133Fu)) return indic_table[u - 0x11338u + indic_offset_0x11338u];
+ if (hb_in_range<hb_codepoint_t> (u, 0x116D0u, 0x116E7u)) return indic_table[u - 0x116D0u + indic_offset_0x116d0u];
break;
default:
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-use-table.hh b/thirdparty/harfbuzz/src/hb-ot-shaper-use-table.hh
index d581b65c07..e8218834e7 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-use-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-use-table.hh
@@ -6,18 +6,18 @@
*
* on files with these headers:
*
- * # IndicSyllabicCategory-15.1.0.txt
- * # Date: 2023-01-05
- * # IndicPositionalCategory-15.1.0.txt
- * # Date: 2023-01-05
- * # ArabicShaping-15.1.0.txt
- * # Date: 2023-01-05
- * # DerivedCoreProperties-15.1.0.txt
- * # Date: 2023-08-07, 15:21:24 GMT
- * # Blocks-15.1.0.txt
- * # Date: 2023-07-28, 15:47:20 GMT
- * # Scripts-15.1.0.txt
- * # Date: 2023-07-28, 16:01:07 GMT
+ * # IndicSyllabicCategory-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:21 GMT
+ * # IndicPositionalCategory-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:21 GMT
+ * # ArabicShaping-16.0.0.txt
+ * # Date: 2024-07-30
+ * # DerivedCoreProperties-16.0.0.txt
+ * # Date: 2024-05-31, 18:09:32 GMT
+ * # Blocks-16.0.0.txt
+ * # Date: 2024-02-02
+ * # Scripts-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:40 GMT
* # Override values For Indic_Syllabic_Category
* # Not derivable
* # Initial version based on Unicode 7.0 by Andrew Glass 2014-03-17
@@ -27,6 +27,7 @@
* # Updated for Unicode 14.0 by Andrew Glass 2021-09-25
* # Updated for Unicode 15.0 by Andrew Glass 2022-09-16
* # Updated for Unicode 15.1 by Andrew Glass 2023-09-14
+ * # Updated for Unicode 16.0 by Andrew Glass 2024-09-11
* # Override values For Indic_Positional_Category
* # Not derivable
* # Initial version based on Unicode 7.0 by Andrew Glass 2014-03-17
@@ -38,6 +39,7 @@
* # Updated for Unicode 14.0 by Andrew Glass 2021-09-28
* # Updated for Unicode 15.0 by Andrew Glass 2022-09-16
* # Updated for Unicode 15.1 by Andrew Glass 2023-09-14
+ * # Updated for Unicode 16.0 by Andrew Glass 2024-09-11
* UnicodeData.txt does not have a header.
*/
@@ -99,16 +101,16 @@
#ifndef HB_OPTIMIZE_SIZE
static const uint8_t
-hb_use_u8[3187] =
+hb_use_u8[3343] =
{
- 16, 50, 51, 51, 51, 52, 51, 83, 118, 131, 51, 57, 58, 179, 195, 61,
+ 16, 50, 51, 51, 51, 52, 51, 83, 118, 131, 57, 58, 59, 195, 211, 62,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
- 14, 0, 1, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 4, 2, 2,
+ 15, 0, 1, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 4, 2, 2,
5, 6, 2, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 2, 2, 17,
18, 19, 20, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 2, 33, 2, 2, 2,
@@ -121,24 +123,26 @@ hb_use_u8[3187] =
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 47, 48, 2,
49, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 50, 51, 2, 2, 2,
- 2, 2, 2, 2, 2, 52, 53, 2, 54, 2, 2, 55, 2, 2, 56, 57,
- 58, 59, 60, 61, 62, 63, 64, 65, 2, 66, 67, 2, 68, 69, 70, 71,
- 2, 72, 2, 73, 74, 75, 76, 2, 2, 77, 78, 79, 80, 2, 81, 82,
- 2, 83, 83, 83, 83, 83, 83, 83, 83, 84, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 52, 53, 2, 54, 2, 2, 55, 56, 2, 57, 58,
+ 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 2, 70, 71, 72, 73,
+ 2, 74, 2, 75, 76, 77, 78, 2, 2, 79, 80, 81, 82, 2, 83, 84,
+ 2, 85, 85, 85, 85, 85, 85, 85, 85, 86, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 87, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 88, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 89, 90, 2, 2, 2, 91, 2, 2, 2, 92,
+ 93, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 94, 94, 94, 95, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 85, 86, 2, 2, 2, 2, 2, 2, 2, 87,
- 88, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 89, 89, 89, 90, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 96, 97, 2, 2, 2, 2, 2,
+ 2, 2, 2, 98, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 91, 92, 2, 2, 2, 2, 2,
- 2, 2, 2, 93, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 94, 2, 2, 95, 2, 2, 2, 96, 2, 2, 2, 2, 2,
- 2, 2, 2, 97, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 98, 98, 99, 100, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
- 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
- 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 2, 2, 2, 99, 2, 2, 100, 2, 2, 2, 101, 2, 102, 2, 2, 2,
+ 2, 2, 2, 103, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 104, 104, 105, 106, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104,
+ 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, 104,
+ 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4,
0, 5, 0, 0, 0, 0, 0, 6, 0, 0, 7, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -167,7 +171,7 @@ hb_use_u8[3187] =
0, 0, 0, 27, 31, 2, 9, 0, 0, 10, 29, 30, 2, 2, 2, 9,
2, 2, 2, 30, 2, 2, 0, 17, 45, 0, 0, 35, 47, 0, 0, 0,
9, 50, 51, 0, 0, 0, 0, 0, 0, 11, 29, 2, 2, 2, 2, 9,
- 2, 2, 2, 2, 2, 2, 52, 53, 23, 23, 19, 31, 48, 33, 48, 34,
+ 2, 2, 2, 2, 2, 2, 52, 53, 23, 19, 20, 31, 48, 33, 48, 34,
54, 0, 0, 0, 35, 0, 0, 0, 30, 12, 29, 30, 2, 2, 2, 2,
2, 2, 2, 2, 9, 0, 2, 2, 2, 2, 30, 2, 2, 2, 2, 30,
0, 2, 2, 2, 9, 0, 55, 0, 35, 23, 22, 31, 31, 18, 48, 48,
@@ -195,88 +199,95 @@ hb_use_u8[3187] =
0, 2, 2, 100, 101, 102, 103, 61, 63, 104, 16, 45, 22, 59, 21, 80,
48, 48, 76, 11, 11, 11, 105, 46, 40, 11, 106, 74, 2, 2, 2, 2,
2, 2, 2, 107, 22, 20, 20, 22, 48, 48, 22, 108, 2, 2, 2, 9,
- 0, 0, 0, 0, 0, 0, 109, 110, 111, 111, 111, 0, 0, 0, 0, 0,
- 0, 106, 74, 2, 2, 2, 2, 2, 2, 60, 61, 59, 25, 22, 112, 61,
+ 0, 0, 0, 0, 0, 0, 109, 110, 110, 110, 110, 0, 0, 0, 0, 0,
+ 0, 106, 74, 2, 2, 2, 2, 2, 2, 60, 61, 59, 25, 22, 111, 61,
2, 2, 2, 2, 107, 22, 23, 45, 45, 102, 14, 0, 0, 0, 0, 0,
- 0, 2, 2, 61, 18, 48, 23, 113, 102, 102, 102, 114, 115, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 0, 30, 2, 11, 46, 116, 116, 116, 11, 116,
- 116, 15, 116, 116, 116, 26, 0, 40, 0, 0, 0, 117, 51, 11, 5, 0,
- 0, 0, 0, 0, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 6, 119,
- 120, 42, 42, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 120,
- 121, 120, 120, 120, 120, 120, 120, 120, 120, 0, 0, 122, 0, 0, 0, 0,
- 0, 0, 7, 122, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 123, 123, 0, 0,
+ 0, 2, 2, 61, 18, 48, 23, 112, 102, 102, 102, 113, 114, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 0, 30, 2, 11, 46, 115, 115, 115, 11, 115,
+ 115, 15, 115, 115, 115, 26, 0, 40, 0, 0, 0, 116, 51, 11, 5, 0,
+ 0, 0, 0, 0, 0, 0, 117, 0, 0, 0, 0, 0, 0, 0, 6, 118,
+ 119, 42, 42, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 119,
+ 120, 119, 119, 119, 119, 119, 119, 119, 119, 0, 0, 121, 0, 0, 0, 0,
+ 0, 0, 7, 121, 0, 0, 0, 0, 0, 46, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 122, 122, 0, 0,
0, 2, 2, 2, 2, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
- 124, 0, 123, 123, 0, 0, 0, 0, 0, 2, 53, 2, 108, 2, 10, 2,
+ 123, 0, 122, 122, 0, 0, 0, 0, 0, 2, 53, 2, 108, 2, 10, 2,
2, 2, 65, 19, 16, 0, 0, 31, 0, 2, 2, 0, 0, 0, 0, 0,
- 0, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 125, 23, 23, 23, 23,
- 23, 23, 23, 126, 0, 0, 0, 0, 0, 11, 11, 11, 11, 11, 11, 11,
- 11, 11, 2, 0, 0, 0, 0, 0, 52, 2, 2, 2, 22, 22, 127, 116,
- 0, 2, 2, 2, 128, 20, 59, 20, 113, 102, 129, 0, 0, 0, 0, 0,
- 0, 11, 130, 2, 2, 2, 2, 2, 2, 2, 131, 23, 22, 20, 48, 132,
- 133, 134, 0, 0, 0, 0, 0, 0, 0, 2, 2, 52, 30, 2, 2, 2,
- 2, 2, 2, 2, 2, 10, 22, 59, 99, 76, 135, 136, 137, 0, 0, 0,
- 0, 2, 138, 2, 2, 2, 2, 139, 0, 30, 2, 42, 5, 0, 79, 15,
- 2, 53, 22, 140, 52, 53, 2, 2, 105, 10, 9, 0, 0, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 141, 21, 25, 0, 0, 142, 143, 0, 0, 0,
- 0, 2, 65, 45, 23, 80, 47, 144, 0, 81, 81, 81, 81, 81, 81, 81,
- 81, 0, 0, 0, 0, 0, 0, 0, 6, 120, 120, 120, 120, 121, 0, 0,
+ 0, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 124, 23, 23, 23, 23,
+ 23, 23, 23, 125, 0, 0, 0, 0, 0, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 2, 0, 0, 0, 0, 0, 52, 2, 2, 2, 22, 22, 126, 115,
+ 0, 2, 2, 2, 127, 20, 59, 20, 112, 102, 128, 0, 0, 0, 0, 0,
+ 0, 11, 129, 2, 2, 2, 2, 2, 2, 2, 130, 23, 22, 20, 48, 131,
+ 132, 133, 0, 0, 0, 0, 0, 0, 0, 2, 2, 52, 30, 2, 2, 2,
+ 2, 2, 2, 2, 2, 10, 22, 59, 99, 76, 134, 135, 136, 0, 0, 0,
+ 0, 2, 137, 2, 2, 2, 2, 138, 0, 30, 2, 42, 5, 0, 79, 15,
+ 2, 53, 22, 139, 52, 53, 2, 2, 105, 10, 9, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 140, 21, 25, 0, 0, 141, 142, 0, 0, 0,
+ 0, 2, 65, 45, 23, 80, 47, 143, 0, 81, 81, 81, 81, 81, 81, 81,
+ 81, 0, 0, 0, 0, 0, 0, 0, 6, 119, 119, 119, 119, 120, 0, 0,
0, 2, 2, 2, 2, 2, 9, 2, 2, 2, 9, 2, 30, 2, 2, 2,
- 2, 2, 30, 2, 2, 2, 30, 9, 0, 128, 20, 27, 31, 0, 0, 145,
- 146, 2, 2, 30, 2, 30, 2, 2, 2, 2, 2, 2, 0, 14, 37, 0,
- 147, 2, 2, 13, 37, 0, 30, 2, 2, 2, 0, 0, 0, 0, 0, 0,
+ 2, 2, 30, 2, 2, 2, 30, 9, 0, 127, 20, 27, 31, 0, 0, 144,
+ 145, 2, 2, 30, 2, 30, 2, 2, 2, 2, 2, 2, 0, 14, 37, 0,
+ 146, 2, 2, 13, 37, 0, 30, 2, 2, 2, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 30, 2, 2, 9, 2, 2, 11, 41, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 27, 38, 0, 2, 2, 2, 116, 116, 116, 116,
- 116, 148, 2, 9, 0, 0, 0, 0, 0, 2, 14, 14, 0, 0, 0, 0,
+ 0, 2, 2, 2, 0, 27, 22, 22, 30, 2, 2, 2, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 27, 38, 0, 2, 2, 2, 115, 115, 115, 115,
+ 115, 147, 2, 9, 0, 0, 0, 0, 0, 2, 14, 14, 0, 0, 0, 0,
0, 9, 2, 2, 9, 2, 2, 2, 2, 30, 2, 9, 0, 30, 2, 0,
- 0, 149, 150, 151, 2, 2, 2, 2, 2, 2, 2, 2, 2, 22, 22, 20,
- 20, 20, 22, 22, 134, 0, 0, 0, 0, 0, 152, 152, 152, 152, 152, 152,
- 152, 152, 152, 152, 2, 2, 2, 2, 2, 53, 52, 53, 0, 0, 0, 0,
- 153, 11, 74, 2, 2, 2, 2, 2, 2, 18, 19, 21, 16, 24, 37, 0,
+ 0, 148, 149, 150, 2, 2, 2, 2, 2, 2, 2, 2, 2, 22, 22, 20,
+ 20, 20, 22, 22, 133, 0, 0, 0, 0, 0, 151, 151, 151, 151, 151, 151,
+ 151, 151, 151, 151, 2, 2, 2, 2, 2, 53, 52, 53, 0, 0, 0, 0,
+ 152, 11, 74, 2, 2, 2, 2, 2, 2, 18, 19, 21, 16, 24, 37, 0,
0, 0, 31, 0, 0, 0, 0, 0, 0, 11, 49, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 128, 20, 22, 154, 22, 21, 155, 156, 2, 2, 2, 2,
- 2, 0, 0, 65, 157, 0, 0, 0, 0, 2, 13, 0, 0, 0, 0, 0,
- 0, 2, 65, 25, 20, 20, 20, 22, 22, 108, 158, 0, 0, 56, 159, 31,
- 160, 30, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 23,
- 19, 22, 22, 161, 44, 0, 0, 0, 49, 128, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 127, 20, 22, 153, 22, 21, 154, 155, 2, 2, 2, 2,
+ 2, 0, 0, 65, 156, 0, 0, 0, 0, 2, 13, 0, 0, 0, 0, 0,
+ 0, 2, 65, 25, 20, 20, 20, 22, 22, 108, 157, 0, 0, 56, 158, 31,
+ 159, 30, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 23,
+ 19, 22, 22, 160, 44, 0, 0, 0, 49, 127, 0, 0, 0, 0, 0, 0,
0, 2, 2, 2, 9, 9, 2, 2, 30, 2, 2, 2, 2, 2, 2, 2,
- 30, 2, 2, 2, 2, 2, 2, 2, 10, 18, 19, 21, 22, 162, 31, 0,
+ 30, 2, 2, 2, 2, 2, 2, 2, 10, 18, 19, 21, 22, 161, 31, 0,
0, 11, 11, 30, 2, 2, 2, 9, 30, 9, 2, 30, 2, 2, 58, 17,
23, 16, 23, 47, 32, 33, 32, 34, 0, 0, 0, 0, 35, 0, 0, 0,
2, 2, 23, 0, 11, 11, 11, 46, 0, 11, 11, 46, 0, 0, 0, 0,
- 0, 2, 2, 65, 25, 20, 20, 20, 22, 23, 126, 15, 17, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 0, 0, 163, 164, 0, 0, 0, 0, 0, 0,
- 0, 18, 19, 20, 20, 66, 99, 25, 160, 11, 165, 9, 0, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 2, 2, 65, 25, 20, 20, 0, 48, 48, 11,
- 166, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 20,
- 0, 23, 19, 20, 20, 21, 16, 82, 166, 38, 0, 0, 0, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 10, 167, 25, 20, 22, 22, 165, 9, 0, 0,
- 0, 2, 2, 2, 2, 2, 9, 43, 136, 23, 22, 20, 76, 21, 22, 0,
- 0, 2, 2, 2, 9, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 18,
- 19, 20, 21, 22, 105, 166, 37, 0, 0, 2, 2, 2, 9, 30, 0, 2,
- 2, 2, 2, 30, 9, 2, 2, 2, 2, 23, 23, 18, 32, 33, 12, 168,
- 169, 170, 171, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 2,
- 2, 65, 25, 20, 20, 0, 22, 23, 29, 108, 0, 33, 0, 0, 0, 0,
- 0, 52, 20, 22, 22, 22, 140, 2, 2, 2, 172, 173, 11, 15, 174, 72,
- 175, 0, 0, 1, 147, 0, 0, 0, 0, 52, 20, 22, 16, 19, 20, 2,
- 2, 2, 2, 158, 158, 158, 176, 176, 176, 176, 176, 176, 15, 177, 0, 30,
- 0, 22, 20, 20, 31, 22, 22, 11, 166, 0, 61, 61, 61, 61, 61, 61,
- 61, 66, 21, 82, 46, 0, 0, 0, 0, 2, 2, 2, 9, 2, 30, 2,
- 2, 52, 22, 22, 31, 0, 38, 22, 27, 11, 159, 178, 174, 0, 0, 0,
- 0, 2, 2, 2, 30, 9, 2, 2, 2, 2, 2, 2, 2, 2, 23, 23,
- 47, 22, 35, 82, 68, 0, 0, 0, 0, 2, 179, 66, 47, 0, 0, 0,
- 0, 11, 180, 2, 2, 2, 2, 2, 2, 2, 2, 23, 22, 20, 31, 0,
- 48, 16, 143, 0, 0, 0, 0, 0, 0, 181, 181, 181, 181, 181, 181, 181,
- 181, 182, 182, 182, 183, 184, 182, 181, 181, 185, 181, 181, 186, 187, 187, 187,
- 187, 187, 187, 187, 0, 0, 0, 0, 0, 11, 11, 11, 46, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 9, 0, 58, 188, 20, 20, 20, 20, 20, 20,
- 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0,
- 40, 116, 26, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,
- 0, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 58,
- 37, 0, 6, 120, 120, 120, 121, 0, 0, 11, 11, 11, 49, 2, 2, 2,
- 0, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2,
- 46, 2, 2, 2, 2, 2, 2, 11, 11, 2, 2, 2, 2, 2, 2, 22,
- 22, 2, 2, 44, 44, 44, 92, 0, 0, O, O, O, GB, B, B, O,
+ 0, 2, 2, 2, 2, 2, 30, 0, 9, 2, 2, 2, 30, 45, 59, 20,
+ 20, 31, 33, 32, 32, 25, 162, 29, 163, 164, 37, 0, 0, 0, 0, 0,
+ 0, 12, 26, 0, 0, 0, 0, 0, 0, 2, 2, 65, 25, 20, 20, 20,
+ 22, 23, 125, 15, 17, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0,
+ 165, 166, 0, 0, 0, 0, 0, 0, 0, 18, 19, 20, 20, 66, 99, 25,
+ 159, 11, 167, 9, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 65, 25, 20, 20, 0, 48, 48, 11, 168, 37, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 2, 20, 0, 23, 19, 20, 20, 21, 16, 82,
+ 168, 38, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 10, 169,
+ 25, 20, 22, 22, 167, 9, 0, 0, 0, 2, 2, 2, 2, 2, 9, 43,
+ 135, 23, 22, 20, 76, 21, 22, 0, 0, 2, 2, 2, 9, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 18, 19, 20, 21, 22, 105, 168, 37, 0,
+ 0, 2, 2, 2, 9, 30, 0, 2, 2, 2, 2, 30, 9, 2, 2, 2,
+ 2, 23, 23, 18, 32, 33, 12, 170, 164, 171, 172, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 0, 2, 2, 2, 65, 25, 20, 20, 0, 22, 23,
+ 29, 108, 0, 33, 0, 0, 0, 0, 0, 52, 20, 22, 22, 22, 139, 2,
+ 2, 2, 173, 174, 11, 15, 175, 61, 176, 0, 0, 1, 146, 0, 0, 0,
+ 0, 52, 20, 22, 16, 19, 20, 2, 2, 2, 2, 157, 157, 157, 177, 177,
+ 177, 177, 177, 177, 15, 178, 0, 30, 0, 22, 20, 20, 31, 22, 22, 11,
+ 168, 0, 61, 61, 61, 61, 61, 61, 61, 66, 21, 82, 46, 0, 0, 0,
+ 0, 2, 2, 2, 9, 2, 30, 2, 2, 52, 22, 22, 31, 0, 38, 22,
+ 27, 11, 158, 179, 180, 0, 0, 0, 0, 2, 2, 2, 30, 9, 2, 2,
+ 2, 2, 2, 2, 2, 2, 23, 23, 47, 22, 35, 82, 68, 0, 0, 0,
+ 0, 2, 181, 66, 47, 0, 0, 0, 0, 11, 182, 2, 2, 2, 2, 2,
+ 2, 2, 2, 23, 22, 20, 31, 0, 48, 16, 142, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 155, 0, 0, 183, 183, 183, 183, 183, 183, 183,
+ 183, 184, 184, 184, 185, 186, 184, 183, 183, 187, 183, 183, 188, 189, 189, 189,
+ 189, 189, 189, 189, 0, 0, 0, 0, 0, 183, 183, 183, 183, 183, 190, 0,
+ 0, 2, 2, 2, 2, 2, 2, 2, 22, 22, 22, 22, 22, 22, 191, 192,
+ 193, 11, 11, 11, 46, 0, 0, 0, 0, 29, 74, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 65, 47, 0, 2, 2, 2, 2, 2, 9, 0,
+ 58, 194, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 0, 0, 0, 40, 115, 26, 0, 0, 0, 0, 0,
+ 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 30, 2, 2, 2, 2, 2, 0, 58, 37, 0, 6, 119, 119, 119, 120, 0,
+ 0, 11, 11, 11, 49, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 46, 2, 2, 2, 2, 2, 2, 11,
+ 11, 2, 2, 2, 2, 2, 2, 22, 22, 2, 2, 2, 2, 2, 2, 2,
+ 20, 2, 2, 44, 44, 44, 92, 0, 0, O, O, O, GB, B, B, O,
SB, O, SE, GB, O, O, WJ,FMPst,FMPst, O, CGJ, B, O, B,VMAbv,VMAbv,
VMAbv, O,VMAbv, B,CMBlw,CMBlw,CMBlw,VMAbv,VMPst, VAbv, VPst,CMBlw, B, VPst, VPre, VPst,
VBlw, VBlw, VBlw, VBlw, VAbv, VAbv, VAbv, VPst, VPst, VPst, H, VPre, VPst,VMBlw, O, O,
@@ -290,20 +301,20 @@ hb_use_u8[3187] =
FMAbv, FAbv,CMAbv,FMAbv,VMAbv,FMAbv, VAbv, IS,FMAbv, B,FMAbv, B, CGJ, WJ, CGJ, GB,
CMAbv,CMAbv, B, GB, B, VAbv, SUB, FPst, FPst,VMBlw, FPst, FPst, FBlw,VMAbv,FMBlw, VAbv,
VPre, B, MPre, MBlw, SUB, FAbv, FAbv, MAbv, SUB, Sk, VPst, VAbv,VMAbv,VMAbv, FAbv,CMAbv,
- VPst, H, B, O,SMAbv,SMBlw,SMAbv,SMAbv,SMAbv, VPst, IS, VBlw, FAbv,VMPre,VMPre,FMAbv,
- CMBlw,VMBlw,VMBlw,VMAbv, CS, O,FMAbv, ZWNJ, CGJ, WJ, WJ, WJ, O,FMPst, O, SB,
- SE, O, H, MPst, VPst, H,VMAbv, VAbv,VMBlw, B, VBlw, FPst, VPst, FAbv,VMPst, B,
- CMAbv, VAbv, MBlw, MPst, MBlw, H, O, VBlw, MPst, MPre, MAbv, MBlw, O, B, FAbv, FAbv,
- FPst, VBlw, B, B, VPre, O,VMPst, IS, O,VMPst, VBlw, VPst,VMBlw,VMBlw,VMAbv, O,
- IS,VMBlw, B,VMPst,VMAbv,VMPst, CS, CS, B, N, N, O, HN, VPre, VBlw, VAbv,
- IS,CMAbv, O, VPst, B, R, R,CMBlw, VAbv, VPre,VMAbv,VMAbv, H, VAbv,CMBlw,FMAbv,
- B, CS, CS, H,CMBlw,VMPst, H,VMPst, VAbv,VMAbv, VPst, IS, R, MPst, R, MPst,
- CMBlw, B,FMBlw, VBlw,VMAbv, R, MBlw, MBlw, GB, FBlw, FBlw,CMAbv, IS, VBlw, IS, GB,
- VAbv, R,VMPst, G, G, J, J, J, SB, SE, J, HR, G, G, HM, HM,
- HM, O, VBlw,
+ VPst, H, B, O,SMAbv,SMAbv,SMAbv, VPst, IS, VBlw, FAbv,VMPre,VMPre,FMAbv,CMBlw,VMBlw,
+ VMBlw,VMAbv, CS, O,FMAbv, ZWNJ, CGJ, WJ, WJ, WJ, O,FMPst, O, SB, SE, O,
+ H, MPst, VPst, H,VMAbv, VAbv,VMBlw, B, VBlw, FPst, VPst, FAbv,VMPst, B,CMAbv, VAbv,
+ MBlw, MPst, MBlw, H, O, VBlw, MPst, MPre, MAbv, MBlw, O, B, FAbv, FAbv, FPst, VBlw,
+ B, B, VPre, O,VMPst, IS, O,VMPst, VBlw, VPst,VMBlw,VMBlw,VMAbv, O, IS,VMBlw,
+ B,VMPst,VMAbv,VMPst, CS, CS, B, N, N, O, HN, VPre, VBlw, VAbv, IS,CMAbv,
+ O, VPst, B, R, R,CMBlw, VAbv, VPre,VMAbv,VMAbv, H, VAbv,CMBlw,VMPst, O,VMAbv,
+ CMBlw, IS, R,FMAbv, B, CS, CS, H,CMBlw,VMPst, H,VMPst, VAbv,VMAbv, VPst, MPst,
+ R, MPst,CMBlw, B,FMBlw, VBlw,VMAbv, CS, SUB, SUB, GB, FBlw, FBlw,CMAbv, IS, VBlw,
+ IS, R, MBlw, GB, VAbv, R,VMPst, G, G, J, J, J, SB, SE, J, HR,
+ G, G, HM, HM, HM, G, O, MPre, MPre, MPst,VMAbv, MBlw, VBlw, O, VBlw,
};
static const uint16_t
-hb_use_u16[808] =
+hb_use_u16[856] =
{
0, 0, 1, 2, 0, 3, 0, 3, 0, 0, 4, 5, 0, 6, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,
@@ -332,28 +343,31 @@ hb_use_u16[808] =
0, 0, 0, 0, 0, 0, 0,164, 0, 0, 0, 0, 0, 0, 0,165,
0, 0, 0, 0, 0, 0, 0,166,166,167, 34,168, 0, 0, 0, 0,
169,170, 10,171, 95, 0, 0, 0, 0, 0, 0, 0, 70, 10,172, 0,
- 10,173,174, 0, 0, 0, 0, 0, 10, 10,175, 2, 0, 0, 0, 0,
- 10, 10,176,173, 0, 0, 0, 0, 0, 0, 0, 10,177,178, 0, 10,
- 179, 0, 0,180,181, 0, 0, 0,182, 10, 10,183,184,185,186,187,
- 188, 10, 10,189,190, 0, 0, 0,191, 10,192,193,194, 10, 10,195,
- 188, 10, 10,196,197,106,198,103, 10, 34,199,200,201, 0, 0, 0,
- 202,203, 95, 10, 10,204,205, 2,206, 21, 22,207,208,209,210,211,
- 10, 10, 10,212,213,214,215, 0,198, 10, 10,216,217, 2, 0, 0,
- 10, 10,218,219,220,221, 0, 0, 10, 10, 10,222,223, 2, 0, 0,
- 10, 10,224,225, 2, 0, 0, 0, 10,226,227,104,228, 0, 0, 0,
- 10, 10,229,230, 0, 0, 0, 0,231,232, 10,233,234, 2, 0, 0,
- 0, 0,235, 10, 10,236,237, 0,238, 10, 10,239,240,241, 10, 10,
- 242,243, 0, 0, 0, 0, 0, 0, 22, 10,218,244, 8, 10, 71, 19,
- 10,245, 74,246, 0, 0, 0, 0,247, 10, 10,248,249, 2,250, 10,
- 251,252, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,253,
- 254, 49, 10,255,256, 2, 0, 0,257,257,257,257,257,257,257,257,
- 257,257,257,258,259,260, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
- 10, 10, 10,261, 0, 0, 0, 0, 10, 10, 10, 10,262,263,264,264,
- 265,266, 0, 0, 0, 0,267, 0, 10, 10, 10, 10, 10, 10, 10, 10,
- 10, 10, 10, 10, 10,268, 0, 0, 10, 10, 10, 10, 10, 10,106, 71,
- 95,269, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,270,
- 10, 10, 71,271,272, 0, 0, 0, 0, 10,273, 0, 10, 10,274, 2,
- 0, 0, 0, 0, 0, 10,275, 2, 10, 10, 10, 10,276, 2, 0, 0,
+ 10,173,174, 0, 0, 0, 0, 0, 10, 10,175, 2, 9, 10,176, 10,
+ 177, 0, 0, 0, 0, 0, 0, 0, 10, 10,178,173, 0, 0, 0, 0,
+ 0, 0, 0, 10,179,180, 0, 10,181, 0, 0,182,183, 0, 0, 0,
+ 184, 10, 10,185,186,187,188,189,190, 10, 10,191,192, 0, 0, 0,
+ 193, 10,194,195,196, 10, 10,197,190, 10, 10,198,199,106,200,103,
+ 10, 34,201,202,203, 0, 0, 0,204,205, 95, 10, 10,206,207, 2,
+ 208, 21, 22,209,210,211,212,213,214, 10, 10,215,216,217,218, 0,
+ 10, 10, 10,219,220,221,222, 0,200, 10, 10,223,224, 2, 0, 0,
+ 10, 10,225,226,227,228, 0, 0, 10, 10, 10,229,230, 2, 0, 0,
+ 10, 10,231,232, 2, 10,141, 0, 10,233,234,104,235, 0, 0, 0,
+ 10, 10,236,237, 0, 0, 0, 0,238,239, 10,240,241, 2, 0, 0,
+ 0, 0,242, 10, 10,243,244, 0,245, 10, 10,246,247,248, 10, 10,
+ 249,250, 0, 0, 0, 0, 0, 0, 22, 10,225,251, 8, 10, 71, 19,
+ 10,252, 74,253, 0, 0, 0, 0,254, 10, 10,255,256, 2,257, 10,
+ 258,259, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,260,
+ 261, 49, 10,262,263,264, 0, 0,265,265,265,265,265,265,265,265,
+ 265,265,265,266,267,268,265,265,265,265,265,265,265,265,265,269,
+ 10,270,271, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
+ 10, 10, 10,272, 0, 0, 0, 0, 0, 0, 0, 0,273, 10,274, 2,
+ 10, 10, 10, 10,275,276,277,277,278,279, 0, 0, 0, 0,280, 0,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,177, 0,281,
+ 10, 10, 10, 10, 10, 10,106, 71, 95,282, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,283, 10, 10, 71,284,285, 0, 0, 0,
+ 0, 10,286, 0, 10, 10,287, 2, 0, 0, 0, 0, 0, 10,288, 2,
+ 0, 0, 0, 0, 0, 10,289,106, 10, 10, 10, 10,290, 2, 0, 0,
130,130,130,130,130,130,130,130,163,163,163,163,163,163,163,163,
163,163,163,163,163,163,163,130,
};
@@ -366,23 +380,23 @@ hb_use_b4 (const uint8_t* a, unsigned i)
static inline uint_fast8_t
hb_use_get_category (unsigned u)
{
- return u<921600u?hb_use_u8[2809+(((hb_use_u8[593+(((hb_use_u16[((hb_use_u8[113+(((hb_use_b4(hb_use_u8,u>>1>>3>>3>>5))<<5)+((u>>1>>3>>3)&31u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:O;
+ return u<921600u?hb_use_u8[2953+(((hb_use_u8[625+(((hb_use_u16[((hb_use_u8[113+(((hb_use_b4(hb_use_u8,u>>1>>3>>3>>5))<<5)+((u>>1>>3>>3)&31u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:O;
}
#else
static const uint8_t
-hb_use_u8[3483] =
+hb_use_u8[3655] =
{
- 16, 50, 51, 51, 51, 52, 51, 83, 118, 131, 51, 57, 58, 179, 195, 61,
+ 16, 50, 51, 51, 51, 52, 51, 83, 118, 131, 57, 58, 59, 195, 211, 62,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
- 14, 0, 1, 1, 2, 1, 1, 3, 4, 5, 6, 7, 8, 9, 10, 1,
+ 15, 0, 1, 1, 2, 1, 1, 3, 4, 5, 6, 7, 8, 9, 10, 1,
11, 12, 1, 1, 1, 1, 1, 1, 13, 14, 15, 16, 17, 18, 19, 1,
1, 20, 1, 1, 1, 1, 21, 1, 22, 1, 1, 1, 1, 1, 23, 24,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -390,14 +404,15 @@ hb_use_u8[3483] =
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29,
30, 1, 1, 1, 1, 1, 31, 1, 1, 1, 1, 32, 33, 1, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 1, 48, 49, 50,
- 51, 52, 52, 52, 52, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 54, 55, 1, 1, 1,
- 56, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 58, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 59, 1, 1,
- 1, 1, 60, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 61, 62, 1, 63, 1, 1, 1, 1, 64, 1, 1, 1, 1, 1,
- 1, 65, 66, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
- 65, 0, 1, 2, 2, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0,
+ 51, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
+ 52, 52, 52, 52, 54, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 55, 1, 1, 1, 1, 1, 1, 1, 1, 56, 57, 1, 58, 1,
+ 59, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 60, 61, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 1,
+ 1, 1, 63, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 64, 65, 1, 66, 67, 1, 1, 1, 68, 1, 1, 1, 1, 1,
+ 1, 69, 70, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 0, 1, 2, 2, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 7, 8, 0, 0, 9, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
@@ -414,23 +429,25 @@ hb_use_u8[3483] =
122, 0, 0, 0, 0, 0, 0, 56, 123, 124, 0, 0, 0, 0, 0, 0,
125, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 127, 128, 129, 0,
0, 130, 131, 132, 0, 0, 0, 51, 133, 0, 0, 0, 0, 134, 135, 0,
- 0, 56, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 137, 0,
- 0, 0, 101, 138, 101, 139, 140, 141, 0, 142, 143, 144, 145, 146, 147, 148,
- 0, 149, 150, 151, 152, 146, 153, 154, 155, 156, 157, 158, 0, 159, 160, 161,
- 162, 163, 164, 165, 166, 0, 0, 0, 0, 56, 167, 168, 169, 170, 171, 172,
- 0, 0, 0, 0, 0, 56, 173, 174, 0, 56, 175, 176, 0, 56, 177, 67,
- 0, 178, 179, 180, 0, 0, 0, 0, 0, 56, 181, 0, 0, 0, 0, 0,
- 0, 182, 183, 184, 0, 0, 185, 186, 187, 188, 189, 190, 56, 191, 0, 0,
- 0, 192, 193, 194, 195, 196, 197, 0, 0, 198, 199, 200, 201, 202, 67, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 203, 204, 205, 206, 0, 0, 0, 0,
- 0, 207, 207, 207, 207, 207, 207, 207, 207, 207, 208, 209, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 67, 0, 56, 210, 0, 0, 0, 0, 0,
- 0, 56, 56, 211, 212, 213, 0, 0, 214, 56, 56, 56, 56, 56, 56, 56,
- 56, 56, 56, 56, 56, 56, 56, 215, 0, 56, 56, 56, 216, 217, 0, 0,
- 0, 0, 0, 0, 218, 0, 0, 0, 0, 56, 219, 220, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 101, 221, 56, 222, 0, 0, 0, 0, 0, 0, 101,
- 223, 56, 56, 224, 0, 0, 0, 0, 0, 225, 225, 225, 225, 225, 225, 225,
- 225, 226, 226, 226, 226, 226, 226, 226, 227, 0, 0, 0, 0, 0, 0, 0,
+ 0, 56, 136, 7, 137, 138, 0, 0, 0, 0, 0, 0, 0, 56, 139, 0,
+ 0, 0, 101, 140, 101, 141, 142, 143, 0, 144, 145, 146, 147, 148, 149, 150,
+ 0, 151, 152, 153, 154, 148, 155, 156, 157, 158, 159, 160, 0, 161, 162, 163,
+ 164, 165, 166, 167, 168, 169, 170, 171, 172, 56, 173, 174, 175, 176, 177, 178,
+ 0, 0, 0, 0, 0, 56, 179, 180, 0, 56, 181, 182, 0, 56, 183, 184,
+ 185, 186, 187, 188, 0, 0, 0, 0, 0, 56, 189, 0, 0, 0, 0, 0,
+ 0, 190, 191, 192, 0, 0, 193, 194, 195, 196, 197, 198, 56, 199, 0, 0,
+ 0, 200, 201, 202, 203, 204, 205, 0, 0, 206, 207, 208, 209, 210, 67, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 211, 212, 213, 214, 0, 0, 0, 0,
+ 0, 215, 215, 215, 215, 215, 215, 215, 215, 215, 216, 217, 215, 215, 215, 215,
+ 215, 215, 215, 215, 215, 215, 215, 215, 218, 219, 220, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 67, 0, 56, 221, 0, 0, 0, 0, 0,
+ 0, 0, 0, 222, 223, 0, 0, 0, 0, 56, 56, 224, 225, 226, 0, 0,
+ 227, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 228,
+ 229, 56, 56, 56, 230, 231, 0, 0, 0, 0, 0, 0, 232, 0, 0, 0,
+ 0, 56, 233, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101, 235, 56,
+ 236, 0, 0, 0, 0, 0, 0, 101, 237, 0, 0, 0, 0, 0, 0, 101,
+ 238, 56, 56, 239, 0, 0, 0, 0, 0, 240, 240, 240, 240, 240, 240, 240,
+ 240, 241, 241, 241, 241, 241, 241, 241, 242, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 2, 2, 2, 0, 0,
0, 0, 0, 0, 0, 0, 3, 4, 0, 5, 0, 0, 0, 0, 0, 6,
0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
@@ -460,7 +477,7 @@ hb_use_u8[3483] =
0, 10, 29, 30, 2, 2, 2, 9, 2, 2, 2, 30, 2, 2, 0, 17,
45, 0, 0, 35, 47, 0, 0, 0, 9, 50, 51, 0, 0, 0, 0, 0,
0, 11, 29, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 52, 53,
- 23, 23, 19, 31, 48, 33, 48, 34, 54, 0, 0, 0, 35, 0, 0, 0,
+ 23, 19, 20, 31, 48, 33, 48, 34, 54, 0, 0, 0, 35, 0, 0, 0,
30, 12, 29, 30, 2, 2, 2, 2, 2, 2, 2, 2, 9, 0, 2, 2,
2, 2, 30, 2, 2, 2, 2, 30, 0, 2, 2, 2, 9, 0, 55, 0,
35, 23, 22, 31, 31, 18, 48, 48, 25, 0, 23, 0, 0, 0, 0, 0,
@@ -488,87 +505,94 @@ hb_use_u8[3483] =
63, 104, 16, 45, 22, 59, 21, 80, 48, 48, 76, 11, 11, 11, 105, 46,
40, 11, 106, 74, 2, 2, 2, 2, 2, 2, 2, 107, 22, 20, 20, 22,
48, 48, 22, 108, 2, 2, 2, 9, 0, 0, 0, 0, 0, 0, 109, 110,
- 111, 111, 111, 0, 0, 0, 0, 0, 0, 106, 74, 2, 2, 2, 2, 2,
- 2, 60, 61, 59, 25, 22, 112, 61, 2, 2, 2, 2, 107, 22, 23, 45,
- 45, 102, 14, 0, 0, 0, 0, 0, 0, 2, 2, 61, 18, 48, 23, 113,
- 102, 102, 102, 114, 115, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 30,
- 2, 11, 46, 116, 116, 116, 11, 116, 116, 15, 116, 116, 116, 26, 0, 40,
- 0, 0, 0, 117, 51, 11, 5, 0, 0, 0, 0, 0, 0, 0, 118, 0,
- 0, 0, 0, 0, 0, 0, 6, 119, 120, 42, 42, 5, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 120, 120, 121, 120, 120, 120, 120, 120, 120, 120,
- 120, 0, 0, 122, 0, 0, 0, 0, 0, 0, 7, 122, 0, 0, 0, 0,
+ 110, 110, 110, 0, 0, 0, 0, 0, 0, 106, 74, 2, 2, 2, 2, 2,
+ 2, 60, 61, 59, 25, 22, 111, 61, 2, 2, 2, 2, 107, 22, 23, 45,
+ 45, 102, 14, 0, 0, 0, 0, 0, 0, 2, 2, 61, 18, 48, 23, 112,
+ 102, 102, 102, 113, 114, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 30,
+ 2, 11, 46, 115, 115, 115, 11, 115, 115, 15, 115, 115, 115, 26, 0, 40,
+ 0, 0, 0, 116, 51, 11, 5, 0, 0, 0, 0, 0, 0, 0, 117, 0,
+ 0, 0, 0, 0, 0, 0, 6, 118, 119, 42, 42, 5, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 119, 119, 120, 119, 119, 119, 119, 119, 119, 119,
+ 119, 0, 0, 121, 0, 0, 0, 0, 0, 0, 7, 121, 0, 0, 0, 0,
0, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
- 0, 0, 0, 0, 123, 123, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0,
- 30, 0, 0, 0, 0, 0, 0, 0, 124, 0, 123, 123, 0, 0, 0, 0,
+ 0, 0, 0, 0, 122, 122, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0,
+ 30, 0, 0, 0, 0, 0, 0, 0, 123, 0, 122, 122, 0, 0, 0, 0,
0, 2, 53, 2, 108, 2, 10, 2, 2, 2, 65, 19, 16, 0, 0, 31,
0, 2, 2, 0, 0, 0, 0, 0, 0, 29, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 125, 23, 23, 23, 23, 23, 23, 23, 126, 0, 0, 0, 0,
+ 2, 2, 2, 124, 23, 23, 23, 23, 23, 23, 23, 125, 0, 0, 0, 0,
0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 2, 0, 0, 0, 0, 0,
- 52, 2, 2, 2, 22, 22, 127, 116, 0, 2, 2, 2, 128, 20, 59, 20,
- 113, 102, 129, 0, 0, 0, 0, 0, 0, 11, 130, 2, 2, 2, 2, 2,
- 2, 2, 131, 23, 22, 20, 48, 132, 133, 134, 0, 0, 0, 0, 0, 0,
+ 52, 2, 2, 2, 22, 22, 126, 115, 0, 2, 2, 2, 127, 20, 59, 20,
+ 112, 102, 128, 0, 0, 0, 0, 0, 0, 11, 129, 2, 2, 2, 2, 2,
+ 2, 2, 130, 23, 22, 20, 48, 131, 132, 133, 0, 0, 0, 0, 0, 0,
0, 2, 2, 52, 30, 2, 2, 2, 2, 2, 2, 2, 2, 10, 22, 59,
- 99, 76, 135, 136, 137, 0, 0, 0, 0, 2, 138, 2, 2, 2, 2, 139,
- 0, 30, 2, 42, 5, 0, 79, 15, 2, 53, 22, 140, 52, 53, 2, 2,
- 105, 10, 9, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 141, 21,
- 25, 0, 0, 142, 143, 0, 0, 0, 0, 2, 65, 45, 23, 80, 47, 144,
+ 99, 76, 134, 135, 136, 0, 0, 0, 0, 2, 137, 2, 2, 2, 2, 138,
+ 0, 30, 2, 42, 5, 0, 79, 15, 2, 53, 22, 139, 52, 53, 2, 2,
+ 105, 10, 9, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 140, 21,
+ 25, 0, 0, 141, 142, 0, 0, 0, 0, 2, 65, 45, 23, 80, 47, 143,
0, 81, 81, 81, 81, 81, 81, 81, 81, 0, 0, 0, 0, 0, 0, 0,
- 6, 120, 120, 120, 120, 121, 0, 0, 0, 2, 2, 2, 2, 2, 9, 2,
+ 6, 119, 119, 119, 119, 120, 0, 0, 0, 2, 2, 2, 2, 2, 9, 2,
2, 2, 9, 2, 30, 2, 2, 2, 2, 2, 30, 2, 2, 2, 30, 9,
- 0, 128, 20, 27, 31, 0, 0, 145, 146, 2, 2, 30, 2, 30, 2, 2,
- 2, 2, 2, 2, 0, 14, 37, 0, 147, 2, 2, 13, 37, 0, 30, 2,
+ 0, 127, 20, 27, 31, 0, 0, 144, 145, 2, 2, 30, 2, 30, 2, 2,
+ 2, 2, 2, 2, 0, 14, 37, 0, 146, 2, 2, 13, 37, 0, 30, 2,
2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 2, 2,
- 9, 2, 2, 11, 41, 0, 0, 0, 0, 2, 2, 2, 2, 2, 27, 38,
- 0, 2, 2, 2, 116, 116, 116, 116, 116, 148, 2, 9, 0, 0, 0, 0,
+ 9, 2, 2, 11, 41, 0, 0, 0, 0, 2, 2, 2, 0, 27, 22, 22,
+ 30, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 27, 38,
+ 0, 2, 2, 2, 115, 115, 115, 115, 115, 147, 2, 9, 0, 0, 0, 0,
0, 2, 14, 14, 0, 0, 0, 0, 0, 9, 2, 2, 9, 2, 2, 2,
- 2, 30, 2, 9, 0, 30, 2, 0, 0, 149, 150, 151, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 22, 22, 20, 20, 20, 22, 22, 134, 0, 0, 0,
- 0, 0, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 2, 2, 2, 2,
- 2, 53, 52, 53, 0, 0, 0, 0, 153, 11, 74, 2, 2, 2, 2, 2,
+ 2, 30, 2, 9, 0, 30, 2, 0, 0, 148, 149, 150, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 22, 22, 20, 20, 20, 22, 22, 133, 0, 0, 0,
+ 0, 0, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 2, 2, 2, 2,
+ 2, 53, 52, 53, 0, 0, 0, 0, 152, 11, 74, 2, 2, 2, 2, 2,
2, 18, 19, 21, 16, 24, 37, 0, 0, 0, 31, 0, 0, 0, 0, 0,
- 0, 11, 49, 2, 2, 2, 2, 2, 2, 2, 2, 2, 128, 20, 22, 154,
- 22, 21, 155, 156, 2, 2, 2, 2, 2, 0, 0, 65, 157, 0, 0, 0,
+ 0, 11, 49, 2, 2, 2, 2, 2, 2, 2, 2, 2, 127, 20, 22, 153,
+ 22, 21, 154, 155, 2, 2, 2, 2, 2, 0, 0, 65, 156, 0, 0, 0,
0, 2, 13, 0, 0, 0, 0, 0, 0, 2, 65, 25, 20, 20, 20, 22,
- 22, 108, 158, 0, 0, 56, 159, 31, 160, 30, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 23, 19, 22, 22, 161, 44, 0, 0, 0,
- 49, 128, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 9, 9, 2, 2,
+ 22, 108, 157, 0, 0, 56, 158, 31, 159, 30, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 23, 19, 22, 22, 160, 44, 0, 0, 0,
+ 49, 127, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 9, 9, 2, 2,
30, 2, 2, 2, 2, 2, 2, 2, 30, 2, 2, 2, 2, 2, 2, 2,
- 10, 18, 19, 21, 22, 162, 31, 0, 0, 11, 11, 30, 2, 2, 2, 9,
+ 10, 18, 19, 21, 22, 161, 31, 0, 0, 11, 11, 30, 2, 2, 2, 9,
30, 9, 2, 30, 2, 2, 58, 17, 23, 16, 23, 47, 32, 33, 32, 34,
0, 0, 0, 0, 35, 0, 0, 0, 2, 2, 23, 0, 11, 11, 11, 46,
- 0, 11, 11, 46, 0, 0, 0, 0, 0, 2, 2, 65, 25, 20, 20, 20,
- 22, 23, 126, 15, 17, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0,
- 163, 164, 0, 0, 0, 0, 0, 0, 0, 18, 19, 20, 20, 66, 99, 25,
- 160, 11, 165, 9, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2,
- 65, 25, 20, 20, 0, 48, 48, 11, 166, 37, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 2, 2, 20, 0, 23, 19, 20, 20, 21, 16, 82,
- 166, 38, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 10, 167,
- 25, 20, 22, 22, 165, 9, 0, 0, 0, 2, 2, 2, 2, 2, 9, 43,
- 136, 23, 22, 20, 76, 21, 22, 0, 0, 2, 2, 2, 9, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 2, 18, 19, 20, 21, 22, 105, 166, 37, 0,
- 0, 2, 2, 2, 9, 30, 0, 2, 2, 2, 2, 30, 9, 2, 2, 2,
- 2, 23, 23, 18, 32, 33, 12, 168, 169, 170, 171, 0, 0, 0, 0, 0,
- 0, 2, 2, 2, 2, 0, 2, 2, 2, 65, 25, 20, 20, 0, 22, 23,
- 29, 108, 0, 33, 0, 0, 0, 0, 0, 52, 20, 22, 22, 22, 140, 2,
- 2, 2, 172, 173, 11, 15, 174, 72, 175, 0, 0, 1, 147, 0, 0, 0,
- 0, 52, 20, 22, 16, 19, 20, 2, 2, 2, 2, 158, 158, 158, 176, 176,
- 176, 176, 176, 176, 15, 177, 0, 30, 0, 22, 20, 20, 31, 22, 22, 11,
- 166, 0, 61, 61, 61, 61, 61, 61, 61, 66, 21, 82, 46, 0, 0, 0,
- 0, 2, 2, 2, 9, 2, 30, 2, 2, 52, 22, 22, 31, 0, 38, 22,
- 27, 11, 159, 178, 174, 0, 0, 0, 0, 2, 2, 2, 30, 9, 2, 2,
- 2, 2, 2, 2, 2, 2, 23, 23, 47, 22, 35, 82, 68, 0, 0, 0,
- 0, 2, 179, 66, 47, 0, 0, 0, 0, 11, 180, 2, 2, 2, 2, 2,
- 2, 2, 2, 23, 22, 20, 31, 0, 48, 16, 143, 0, 0, 0, 0, 0,
- 0, 181, 181, 181, 181, 181, 181, 181, 181, 182, 182, 182, 183, 184, 182, 181,
- 181, 185, 181, 181, 186, 187, 187, 187, 187, 187, 187, 187, 0, 0, 0, 0,
- 0, 11, 11, 11, 46, 0, 0, 0, 0, 2, 2, 2, 2, 2, 9, 0,
- 58, 188, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
- 20, 20, 20, 20, 20, 0, 0, 0, 40, 116, 26, 0, 0, 0, 0, 0,
- 0, 0, 0, 9, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0,
- 0, 2, 2, 2, 2, 2, 0, 58, 37, 0, 6, 120, 120, 120, 121, 0,
- 0, 11, 11, 11, 49, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0,
- 2, 2, 2, 2, 2, 2, 2, 2, 46, 2, 2, 2, 2, 2, 2, 11,
- 11, 2, 2, 2, 2, 2, 2, 22, 22, 2, 2, 44, 44, 44, 92, 0,
+ 0, 11, 11, 46, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 30, 0,
+ 9, 2, 2, 2, 30, 45, 59, 20, 20, 31, 33, 32, 32, 25, 162, 29,
+ 163, 164, 37, 0, 0, 0, 0, 0, 0, 12, 26, 0, 0, 0, 0, 0,
+ 0, 2, 2, 65, 25, 20, 20, 20, 22, 23, 125, 15, 17, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 0, 0, 165, 166, 0, 0, 0, 0, 0, 0,
+ 0, 18, 19, 20, 20, 66, 99, 25, 159, 11, 167, 9, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 2, 2, 65, 25, 20, 20, 0, 48, 48, 11,
+ 168, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 20,
+ 0, 23, 19, 20, 20, 21, 16, 82, 168, 38, 0, 0, 0, 0, 0, 0,
+ 0, 2, 2, 2, 2, 2, 10, 169, 25, 20, 22, 22, 167, 9, 0, 0,
+ 0, 2, 2, 2, 2, 2, 9, 43, 135, 23, 22, 20, 76, 21, 22, 0,
+ 0, 2, 2, 2, 9, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 18,
+ 19, 20, 21, 22, 105, 168, 37, 0, 0, 2, 2, 2, 9, 30, 0, 2,
+ 2, 2, 2, 30, 9, 2, 2, 2, 2, 23, 23, 18, 32, 33, 12, 170,
+ 164, 171, 172, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 2,
+ 2, 65, 25, 20, 20, 0, 22, 23, 29, 108, 0, 33, 0, 0, 0, 0,
+ 0, 52, 20, 22, 22, 22, 139, 2, 2, 2, 173, 174, 11, 15, 175, 61,
+ 176, 0, 0, 1, 146, 0, 0, 0, 0, 52, 20, 22, 16, 19, 20, 2,
+ 2, 2, 2, 157, 157, 157, 177, 177, 177, 177, 177, 177, 15, 178, 0, 30,
+ 0, 22, 20, 20, 31, 22, 22, 11, 168, 0, 61, 61, 61, 61, 61, 61,
+ 61, 66, 21, 82, 46, 0, 0, 0, 0, 2, 2, 2, 9, 2, 30, 2,
+ 2, 52, 22, 22, 31, 0, 38, 22, 27, 11, 158, 179, 180, 0, 0, 0,
+ 0, 2, 2, 2, 30, 9, 2, 2, 2, 2, 2, 2, 2, 2, 23, 23,
+ 47, 22, 35, 82, 68, 0, 0, 0, 0, 2, 181, 66, 47, 0, 0, 0,
+ 0, 11, 182, 2, 2, 2, 2, 2, 2, 2, 2, 23, 22, 20, 31, 0,
+ 48, 16, 142, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 155, 0,
+ 0, 183, 183, 183, 183, 183, 183, 183, 183, 184, 184, 184, 185, 186, 184, 183,
+ 183, 187, 183, 183, 188, 189, 189, 189, 189, 189, 189, 189, 0, 0, 0, 0,
+ 0, 183, 183, 183, 183, 183, 190, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 22, 22, 22, 22, 22, 22, 191, 192, 193, 11, 11, 11, 46, 0, 0, 0,
+ 0, 29, 74, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 65, 47,
+ 0, 2, 2, 2, 2, 2, 9, 0, 58, 194, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0,
+ 40, 115, 26, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 30, 2, 2, 2, 2, 2, 0, 58,
+ 37, 0, 6, 119, 119, 119, 120, 0, 0, 11, 11, 11, 49, 2, 2, 2,
+ 0, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+ 46, 2, 2, 2, 2, 2, 2, 11, 11, 2, 2, 2, 2, 2, 2, 22,
+ 22, 2, 2, 2, 2, 2, 2, 2, 20, 2, 2, 44, 44, 44, 92, 0,
0, O, O, O, GB, B, B, O, SB, O, SE, GB, O, O, WJ,FMPst,
FMPst, O, CGJ, B, O, B,VMAbv,VMAbv,VMAbv, O,VMAbv, B,CMBlw,CMBlw,CMBlw,VMAbv,
VMPst, VAbv, VPst,CMBlw, B, VPst, VPre, VPst, VBlw, VBlw, VBlw, VBlw, VAbv, VAbv, VAbv, VPst,
@@ -582,20 +606,21 @@ hb_use_u8[3483] =
VMPst, VBlw, VPst, CGJ, CGJ, VPst,VMAbv,VMAbv,FMAbv, FAbv,CMAbv,FMAbv,VMAbv,FMAbv, VAbv, IS,
FMAbv, B,FMAbv, B, CGJ, WJ, CGJ, GB,CMAbv,CMAbv, B, GB, B, VAbv, SUB, FPst,
FPst,VMBlw, FPst, FPst, FBlw,VMAbv,FMBlw, VAbv, VPre, B, MPre, MBlw, SUB, FAbv, FAbv, MAbv,
- SUB, Sk, VPst, VAbv,VMAbv,VMAbv, FAbv,CMAbv, VPst, H, B, O,SMAbv,SMBlw,SMAbv,SMAbv,
- SMAbv, VPst, IS, VBlw, FAbv,VMPre,VMPre,FMAbv,CMBlw,VMBlw,VMBlw,VMAbv, CS, O,FMAbv, ZWNJ,
- CGJ, WJ, WJ, WJ, O,FMPst, O, SB, SE, O, H, MPst, VPst, H,VMAbv, VAbv,
- VMBlw, B, VBlw, FPst, VPst, FAbv,VMPst, B,CMAbv, VAbv, MBlw, MPst, MBlw, H, O, VBlw,
- MPst, MPre, MAbv, MBlw, O, B, FAbv, FAbv, FPst, VBlw, B, B, VPre, O,VMPst, IS,
- O,VMPst, VBlw, VPst,VMBlw,VMBlw,VMAbv, O, IS,VMBlw, B,VMPst,VMAbv,VMPst, CS, CS,
- B, N, N, O, HN, VPre, VBlw, VAbv, IS,CMAbv, O, VPst, B, R, R,CMBlw,
- VAbv, VPre,VMAbv,VMAbv, H, VAbv,CMBlw,FMAbv, B, CS, CS, H,CMBlw,VMPst, H,VMPst,
- VAbv,VMAbv, VPst, IS, R, MPst, R, MPst,CMBlw, B,FMBlw, VBlw,VMAbv, R, MBlw, MBlw,
- GB, FBlw, FBlw,CMAbv, IS, VBlw, IS, GB, VAbv, R,VMPst, G, G, J, J, J,
- SB, SE, J, HR, G, G, HM, HM, HM, O, VBlw,
+ SUB, Sk, VPst, VAbv,VMAbv,VMAbv, FAbv,CMAbv, VPst, H, B, O,SMAbv,SMAbv,SMAbv, VPst,
+ IS, VBlw, FAbv,VMPre,VMPre,FMAbv,CMBlw,VMBlw,VMBlw,VMAbv, CS, O,FMAbv, ZWNJ, CGJ, WJ,
+ WJ, WJ, O,FMPst, O, SB, SE, O, H, MPst, VPst, H,VMAbv, VAbv,VMBlw, B,
+ VBlw, FPst, VPst, FAbv,VMPst, B,CMAbv, VAbv, MBlw, MPst, MBlw, H, O, VBlw, MPst, MPre,
+ MAbv, MBlw, O, B, FAbv, FAbv, FPst, VBlw, B, B, VPre, O,VMPst, IS, O,VMPst,
+ VBlw, VPst,VMBlw,VMBlw,VMAbv, O, IS,VMBlw, B,VMPst,VMAbv,VMPst, CS, CS, B, N,
+ N, O, HN, VPre, VBlw, VAbv, IS,CMAbv, O, VPst, B, R, R,CMBlw, VAbv, VPre,
+ VMAbv,VMAbv, H, VAbv,CMBlw,VMPst, O,VMAbv,CMBlw, IS, R,FMAbv, B, CS, CS, H,
+ CMBlw,VMPst, H,VMPst, VAbv,VMAbv, VPst, MPst, R, MPst,CMBlw, B,FMBlw, VBlw,VMAbv, CS,
+ SUB, SUB, GB, FBlw, FBlw,CMAbv, IS, VBlw, IS, R, MBlw, GB, VAbv, R,VMPst, G,
+ G, J, J, J, SB, SE, J, HR, G, G, HM, HM, HM, G, O, MPre,
+ MPre, MPst,VMAbv, MBlw, VBlw, O, VBlw,
};
static const uint16_t
-hb_use_u16[456] =
+hb_use_u16[486] =
{
0, 0, 1, 2, 0, 3, 4, 5, 0, 6, 7, 0, 8, 0, 9, 10,
11, 12, 10, 13, 14, 10, 10, 15, 16, 17, 18, 19, 20, 21, 22, 23,
@@ -614,18 +639,20 @@ hb_use_u16[456] =
148,149,150, 10, 10,151,152, 2,153, 99,154,155,156, 2, 10,157,
10,158,159, 0,160,161,162, 2,163, 0, 0,164, 0,165, 0,166,
166,167, 34,168,169,170, 10,171, 95, 0,172, 0, 10,173,174, 0,
- 175, 2,176,173,177,178,179, 0, 0,180,181, 0,182, 10, 10,183,
- 184,185,186,187,188, 10, 10,189,190, 0,191, 10,192,193,194, 10,
- 10,195, 10,196,197,106,198,103, 10, 34,199,200,201, 0,202,203,
- 95, 10, 10,204,205, 2,206, 21, 22,207,208,209,210,211, 10,212,
- 213,214,215, 0,198, 10, 10,216,217, 2,218,219,220,221, 10,222,
- 223, 2,224,225, 10,226,227,104,228, 0,229,230,231,232, 10,233,
- 234, 2,235, 10, 10,236,237, 0,238, 10, 10,239,240,241,242,243,
- 22, 10,218,244, 8, 10, 71, 19, 10,245, 74,246,247, 10, 10,248,
- 249, 2,250, 10,251,252, 10,253,254, 49, 10,255,256, 2,257,257,
- 257,258,259,260, 10,261,262,263,264,264,265,266,267, 0, 10,268,
- 106, 71, 95,269, 0,270, 71,271,272, 0,273, 0,274, 2,275, 2,
- 276, 2,130,130,163,163,163,130,
+ 175, 2,176, 10,177, 0,178,173,179,180,181, 0, 0,182,183, 0,
+ 184, 10, 10,185,186,187,188,189,190, 10, 10,191,192, 0,193, 10,
+ 194,195,196, 10, 10,197, 10,198,199,106,200,103, 10, 34,201,202,
+ 203, 0,204,205, 95, 10, 10,206,207, 2,208, 21, 22,209,210,211,
+ 212,213,214, 10, 10,215,216,217,218, 0, 10,219,220,221,222, 0,
+ 200, 10, 10,223,224, 2,225,226,227,228, 10,229,230, 2,231,232,
+ 2, 10,141, 0, 10,233,234,104,235, 0,236,237,238,239, 10,240,
+ 241, 2,242, 10, 10,243,244, 0,245, 10, 10,246,247,248,249,250,
+ 22, 10,225,251, 8, 10, 71, 19, 10,252, 74,253,254, 10, 10,255,
+ 256, 2,257, 10,258,259, 10,260,261, 49, 10,262,263,264,265,265,
+ 265,266,267,268,265,269, 10,270,271, 2, 10,272,273, 10,274, 2,
+ 275,276,277,277,278,279,280, 0, 10,177, 0,281,106, 71, 95,282,
+ 0,283, 71,284,285, 0,286, 0,287, 2,288, 2,289,106,290, 2,
+ 130,130,163,163,163,130,
};
static inline unsigned
@@ -636,7 +663,7 @@ hb_use_b4 (const uint8_t* a, unsigned i)
static inline uint_fast8_t
hb_use_get_category (unsigned u)
{
- return u<921600u?hb_use_u8[3105+(((hb_use_u8[889+(((hb_use_u16[((hb_use_u8[353+(((hb_use_u8[113+(((hb_use_b4(hb_use_u8,u>>1>>3>>1>>3>>4))<<4)+((u>>1>>3>>1>>3)&15u))])<<3)+((u>>1>>3>>1)&7u))])<<1)+((u>>1>>3)&1u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:O;
+ return u<921600u?hb_use_u8[3265+(((hb_use_u8[937+(((hb_use_u16[((hb_use_u8[369+(((hb_use_u8[113+(((hb_use_b4(hb_use_u8,u>>1>>3>>1>>3>>4))<<4)+((u>>1>>3>>1>>3)&15u))])<<3)+((u>>1>>3>>1)&7u))])<<1)+((u>>1>>3)&1u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:O;
}
#endif
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper-vowel-constraints.cc b/thirdparty/harfbuzz/src/hb-ot-shaper-vowel-constraints.cc
index d1ed894596..dbe781e562 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper-vowel-constraints.cc
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper-vowel-constraints.cc
@@ -10,8 +10,8 @@
* # Date: 2015-03-12, 21:17:00 GMT [AG]
* # Date: 2019-11-08, 23:22:00 GMT [AG]
*
- * # Scripts-15.1.0.txt
- * # Date: 2023-07-28, 16:01:07 GMT
+ * # Scripts-16.0.0.txt
+ * # Date: 2024-04-30, 21:48:40 GMT
*/
#include "hb.hh"
diff --git a/thirdparty/harfbuzz/src/hb-ot-shaper.hh b/thirdparty/harfbuzz/src/hb-ot-shaper.hh
index 0207b2bbe3..8a094739cb 100644
--- a/thirdparty/harfbuzz/src/hb-ot-shaper.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-shaper.hh
@@ -174,9 +174,11 @@ HB_OT_SHAPERS_IMPLEMENT_SHAPERS
static inline const hb_ot_shaper_t *
-hb_ot_shaper_categorize (const hb_ot_shape_planner_t *planner)
+hb_ot_shaper_categorize (hb_script_t script,
+ hb_direction_t direction,
+ hb_tag_t gsub_script)
{
- switch ((hb_tag_t) planner->props.script)
+ switch ((hb_tag_t) script)
{
default:
return &_hb_ot_shaper_default;
@@ -192,9 +194,8 @@ hb_ot_shaper_categorize (const hb_ot_shape_planner_t *planner)
* This is because we do fallback shaping for Arabic script (and not others).
* But note that Arabic shaping is applicable only to horizontal layout; for
* vertical text, just use the generic shaper instead. */
- if ((planner->map.chosen_script[0] != HB_OT_TAG_DEFAULT_SCRIPT ||
- planner->props.script == HB_SCRIPT_ARABIC) &&
- HB_DIRECTION_IS_HORIZONTAL(planner->props.direction))
+ if ((gsub_script != HB_OT_TAG_DEFAULT_SCRIPT || script == HB_SCRIPT_ARABIC) &&
+ HB_DIRECTION_IS_HORIZONTAL (direction))
return &_hb_ot_shaper_arabic;
else
return &_hb_ot_shaper_default;
@@ -235,10 +236,10 @@ hb_ot_shaper_categorize (const hb_ot_shape_planner_t *planner)
* Otherwise, use the specific shaper.
*
* If it's indy3 tag, send to USE. */
- if (planner->map.chosen_script[0] == HB_TAG ('D','F','L','T') ||
- planner->map.chosen_script[0] == HB_TAG ('l','a','t','n'))
+ if (gsub_script == HB_TAG ('D','F','L','T') ||
+ gsub_script == HB_TAG ('l','a','t','n'))
return &_hb_ot_shaper_default;
- else if ((planner->map.chosen_script[0] & 0x000000FF) == '3')
+ else if ((gsub_script & 0x000000FF) == '3')
return &_hb_ot_shaper_use;
else
return &_hb_ot_shaper_indic;
@@ -254,9 +255,9 @@ hb_ot_shaper_categorize (const hb_ot_shape_planner_t *planner)
* If designer designed for 'mymr' tag, also send to default
* shaper. That's tag used from before Myanmar shaping spec
* was developed. The shaping spec uses 'mym2' tag. */
- if (planner->map.chosen_script[0] == HB_TAG ('D','F','L','T') ||
- planner->map.chosen_script[0] == HB_TAG ('l','a','t','n') ||
- planner->map.chosen_script[0] == HB_TAG ('m','y','m','r'))
+ if (gsub_script == HB_TAG ('D','F','L','T') ||
+ gsub_script == HB_TAG ('l','a','t','n') ||
+ gsub_script == HB_TAG ('m','y','m','r'))
return &_hb_ot_shaper_default;
else
return &_hb_ot_shaper_myanmar;
@@ -386,13 +387,22 @@ hb_ot_shaper_categorize (const hb_ot_shape_planner_t *planner)
case HB_SCRIPT_KAWI:
case HB_SCRIPT_NAG_MUNDARI:
+ /* Unicode-16.0 additions */
+ case HB_SCRIPT_GARAY:
+ case HB_SCRIPT_GURUNG_KHEMA:
+ case HB_SCRIPT_KIRAT_RAI:
+ case HB_SCRIPT_OL_ONAL:
+ case HB_SCRIPT_SUNUWAR:
+ case HB_SCRIPT_TODHRI:
+ case HB_SCRIPT_TULU_TIGALARI:
+
/* If the designer designed the font for the 'DFLT' script,
* (or we ended up arbitrarily pick 'latn'), use the default shaper.
* Otherwise, use the specific shaper.
* Note that for some simple scripts, there may not be *any*
* GSUB/GPOS needed, so there may be no scripts found! */
- if (planner->map.chosen_script[0] == HB_TAG ('D','F','L','T') ||
- planner->map.chosen_script[0] == HB_TAG ('l','a','t','n'))
+ if (gsub_script == HB_TAG ('D','F','L','T') ||
+ gsub_script == HB_TAG ('l','a','t','n'))
return &_hb_ot_shaper_default;
else
return &_hb_ot_shaper_use;
diff --git a/thirdparty/harfbuzz/src/hb-ot-stat-table.hh b/thirdparty/harfbuzz/src/hb-ot-stat-table.hh
index ea5459ef4e..7a46be1fff 100644
--- a/thirdparty/harfbuzz/src/hb-ot-stat-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-stat-table.hh
@@ -354,10 +354,10 @@ struct AxisValue
{
switch (u.format)
{
- case 1: return u.format1.get_value ();
- case 2: return u.format2.get_value ();
- case 3: return u.format3.get_value ();
- case 4: return u.format4.get_axis_record (axis_index).get_value ();
+ case 1: hb_barrier (); return u.format1.get_value ();
+ case 2: hb_barrier (); return u.format2.get_value ();
+ case 3: hb_barrier (); return u.format3.get_value ();
+ case 4: hb_barrier (); return u.format4.get_axis_record (axis_index).get_value ();
default:return 0.f;
}
}
@@ -366,9 +366,9 @@ struct AxisValue
{
switch (u.format)
{
- case 1: return u.format1.get_axis_index ();
- case 2: return u.format2.get_axis_index ();
- case 3: return u.format3.get_axis_index ();
+ case 1: hb_barrier (); return u.format1.get_axis_index ();
+ case 2: hb_barrier (); return u.format2.get_axis_index ();
+ case 3: hb_barrier (); return u.format3.get_axis_index ();
/* case 4: Makes more sense for variable fonts which are handled by fvar in hb-style */
default:return -1;
}
@@ -378,10 +378,10 @@ struct AxisValue
{
switch (u.format)
{
- case 1: return u.format1.get_value_name_id ();
- case 2: return u.format2.get_value_name_id ();
- case 3: return u.format3.get_value_name_id ();
- case 4: return u.format4.get_value_name_id ();
+ case 1: hb_barrier (); return u.format1.get_value_name_id ();
+ case 2: hb_barrier (); return u.format2.get_value_name_id ();
+ case 3: hb_barrier (); return u.format3.get_value_name_id ();
+ case 4: hb_barrier (); return u.format4.get_value_name_id ();
default:return HB_OT_NAME_ID_INVALID;
}
}
@@ -392,10 +392,10 @@ struct AxisValue
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
TRACE_DISPATCH (this, u.format);
switch (u.format) {
- case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
- case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
- case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
- case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
+ case 1: hb_barrier (); return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
+ case 2: hb_barrier (); return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
+ case 3: hb_barrier (); return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
+ case 4: hb_barrier (); return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
default:return_trace (c->default_return_value ());
}
}
@@ -405,10 +405,10 @@ struct AxisValue
{
switch (u.format)
{
- case 1: return u.format1.keep_axis_value (axis_records, user_axes_location);
- case 2: return u.format2.keep_axis_value (axis_records, user_axes_location);
- case 3: return u.format3.keep_axis_value (axis_records, user_axes_location);
- case 4: return u.format4.keep_axis_value (axis_records, user_axes_location);
+ case 1: hb_barrier (); return u.format1.keep_axis_value (axis_records, user_axes_location);
+ case 2: hb_barrier (); return u.format2.keep_axis_value (axis_records, user_axes_location);
+ case 3: hb_barrier (); return u.format3.keep_axis_value (axis_records, user_axes_location);
+ case 4: hb_barrier (); return u.format4.keep_axis_value (axis_records, user_axes_location);
default:return false;
}
}
@@ -422,10 +422,10 @@ struct AxisValue
switch (u.format)
{
- case 1: return_trace (u.format1.sanitize (c));
- case 2: return_trace (u.format2.sanitize (c));
- case 3: return_trace (u.format3.sanitize (c));
- case 4: return_trace (u.format4.sanitize (c));
+ case 1: hb_barrier (); return_trace (u.format1.sanitize (c));
+ case 2: hb_barrier (); return_trace (u.format2.sanitize (c));
+ case 3: hb_barrier (); return_trace (u.format3.sanitize (c));
+ case 4: hb_barrier (); return_trace (u.format4.sanitize (c));
default:return_trace (true);
}
}
diff --git a/thirdparty/harfbuzz/src/hb-ot-tag-table.hh b/thirdparty/harfbuzz/src/hb-ot-tag-table.hh
index 920b06b9c9..326e019127 100644
--- a/thirdparty/harfbuzz/src/hb-ot-tag-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-tag-table.hh
@@ -6,8 +6,8 @@
*
* on files with these headers:
*
- * <meta name="updated_at" content="2023-09-30 01:21 AM" />
- * File-Date: 2024-03-07
+ * <meta name="updated_at" content="2024-05-31 05:41 PM" />
+ * File-Date: 2024-05-16
*/
#ifndef HB_OT_TAG_TABLE_HH
@@ -26,7 +26,7 @@ static const LangTag ot_languages2[] = {
{HB_TAG('a','y',' ',' '), HB_TAG('A','Y','M',' ')}, /* Aymara [macrolanguage] */
{HB_TAG('a','z',' ',' '), HB_TAG('A','Z','E',' ')}, /* Azerbaijani [macrolanguage] */
{HB_TAG('b','a',' ',' '), HB_TAG('B','S','H',' ')}, /* Bashkir */
- {HB_TAG('b','e',' ',' '), HB_TAG('B','E','L',' ')}, /* Belarusian -> Belarussian */
+ {HB_TAG('b','e',' ',' '), HB_TAG('B','E','L',' ')}, /* Belarusian */
{HB_TAG('b','g',' ',' '), HB_TAG('B','G','R',' ')}, /* Bulgarian */
{HB_TAG('b','i',' ',' '), HB_TAG('B','I','S',' ')}, /* Bislama */
{HB_TAG('b','i',' ',' '), HB_TAG('C','P','P',' ')}, /* Bislama -> Creoles */
@@ -64,6 +64,7 @@ static const LangTag ot_languages2[] = {
{HB_TAG('f','r',' ',' '), HB_TAG('F','R','A',' ')}, /* French */
{HB_TAG('f','y',' ',' '), HB_TAG('F','R','I',' ')}, /* Western Frisian -> Frisian */
{HB_TAG('g','a',' ',' '), HB_TAG('I','R','I',' ')}, /* Irish */
+ {HB_TAG('g','a',' ',' '), HB_TAG('I','R','T',' ')}, /* Irish -> Irish Traditional */
{HB_TAG('g','d',' ',' '), HB_TAG('G','A','E',' ')}, /* Scottish Gaelic */
{HB_TAG('g','l',' ',' '), HB_TAG('G','A','L',' ')}, /* Galician */
{HB_TAG('g','n',' ',' '), HB_TAG('G','U','A',' ')}, /* Guarani [macrolanguage] */
@@ -132,7 +133,7 @@ static const LangTag ot_languages2[] = {
{HB_TAG('m','l',' ',' '), HB_TAG('M','A','L',' ')}, /* Malayalam -> Malayalam Traditional */
{HB_TAG('m','l',' ',' '), HB_TAG('M','L','R',' ')}, /* Malayalam -> Malayalam Reformed */
{HB_TAG('m','n',' ',' '), HB_TAG('M','N','G',' ')}, /* Mongolian [macrolanguage] */
- {HB_TAG('m','o',' ',' '), HB_TAG('M','O','L',' ')}, /* Moldavian (retired code) -> Romanian (Moldova) */
+ {HB_TAG('m','o',' ',' '), HB_TAG('M','O','L',' ')}, /* Moldavian (retired code) */
{HB_TAG('m','o',' ',' '), HB_TAG('R','O','M',' ')}, /* Moldavian (retired code) -> Romanian */
{HB_TAG('m','r',' ',' '), HB_TAG('M','A','R',' ')}, /* Marathi */
{HB_TAG('m','s',' ',' '), HB_TAG('M','L','Y',' ')}, /* Malay [macrolanguage] */
@@ -223,6 +224,7 @@ static const LangTag ot_languages2[] = {
static const LangTag ot_languages3[] = {
{HB_TAG('a','a','e',' '), HB_TAG('S','Q','I',' ')}, /* Arbëreshë Albanian -> Albanian */
{HB_TAG('a','a','o',' '), HB_TAG('A','R','A',' ')}, /* Algerian Saharan Arabic -> Arabic */
+/*{HB_TAG('a','a','q',' '), HB_TAG('A','A','Q',' ')},*/ /* Eastern Abnaki -> Eastern Abenaki */
{HB_TAG('a','a','t',' '), HB_TAG('S','Q','I',' ')}, /* Arvanitika Albanian -> Albanian */
{HB_TAG('a','b','a',' '), HB_TAG_NONE }, /* Abé != Abaza */
{HB_TAG('a','b','h',' '), HB_TAG('A','R','A',' ')}, /* Tajiki Arabic -> Arabic */
@@ -238,6 +240,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('a','c','r',' '), HB_TAG('M','Y','N',' ')}, /* Achi -> Mayan */
{HB_TAG('a','c','w',' '), HB_TAG('A','R','A',' ')}, /* Hijazi Arabic -> Arabic */
{HB_TAG('a','c','x',' '), HB_TAG('A','R','A',' ')}, /* Omani Arabic -> Arabic */
+ {HB_TAG('a','c','y',' '), HB_TAG('A','C','Y',' ')}, /* Cypriot Arabic */
{HB_TAG('a','c','y',' '), HB_TAG('A','R','A',' ')}, /* Cypriot Arabic -> Arabic */
{HB_TAG('a','d','a',' '), HB_TAG('D','N','G',' ')}, /* Adangme -> Dangme */
{HB_TAG('a','d','f',' '), HB_TAG('A','R','A',' ')}, /* Dhofari Arabic -> Arabic */
@@ -288,6 +291,7 @@ static const LangTag ot_languages3[] = {
/*{HB_TAG('a','s','t',' '), HB_TAG('A','S','T',' ')},*/ /* Asturian */
/*{HB_TAG('a','t','h',' '), HB_TAG('A','T','H',' ')},*/ /* Athapascan [collection] -> Athapaskan */
{HB_TAG('a','t','j',' '), HB_TAG('R','C','R',' ')}, /* Atikamekw -> R-Cree */
+/*{HB_TAG('a','t','s',' '), HB_TAG('A','T','S',' ')},*/ /* Gros Ventre (Atsina) */
{HB_TAG('a','t','v',' '), HB_TAG('A','L','T',' ')}, /* Northern Altai -> Altai */
{HB_TAG('a','u','j',' '), HB_TAG('B','B','R',' ')}, /* Awjilah -> Berber */
{HB_TAG('a','u','z',' '), HB_TAG('A','R','A',' ')}, /* Uzbeki Arabic -> Arabic */
@@ -326,6 +330,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('b','c','l',' '), HB_TAG('B','I','K',' ')}, /* Central Bikol -> Bikol */
{HB_TAG('b','c','q',' '), HB_TAG('B','C','H',' ')}, /* Bench */
{HB_TAG('b','c','r',' '), HB_TAG('A','T','H',' ')}, /* Babine -> Athapaskan */
+/*{HB_TAG('b','d','c',' '), HB_TAG('B','D','C',' ')},*/ /* Emberá-Baudó */
/*{HB_TAG('b','d','y',' '), HB_TAG('B','D','Y',' ')},*/ /* Bandjalang */
{HB_TAG('b','e','a',' '), HB_TAG('A','T','H',' ')}, /* Beaver -> Athapaskan */
{HB_TAG('b','e','b',' '), HB_TAG('B','T','I',' ')}, /* Bebele -> Beti */
@@ -421,6 +426,8 @@ static const LangTag ot_languages3[] = {
{HB_TAG('c','a','f',' '), HB_TAG('A','T','H',' ')}, /* Southern Carrier -> Athapaskan */
{HB_TAG('c','a','k',' '), HB_TAG('C','A','K',' ')}, /* Kaqchikel */
{HB_TAG('c','a','k',' '), HB_TAG('M','Y','N',' ')}, /* Kaqchikel -> Mayan */
+/*{HB_TAG('c','a','y',' '), HB_TAG('C','A','Y',' ')},*/ /* Cayuga */
+/*{HB_TAG('c','b','g',' '), HB_TAG('C','B','G',' ')},*/ /* Chimila */
{HB_TAG('c','b','k',' '), HB_TAG('C','B','K',' ')}, /* Chavacano -> Zamboanga Chavacano */
{HB_TAG('c','b','k',' '), HB_TAG('C','P','P',' ')}, /* Chavacano -> Creoles */
{HB_TAG('c','b','l',' '), HB_TAG('Q','I','N',' ')}, /* Bualkhaw Chin -> Chin */
@@ -467,6 +474,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('c','l','j',' '), HB_TAG('Q','I','N',' ')}, /* Laitu Chin -> Chin */
{HB_TAG('c','l','s',' '), HB_TAG('S','A','N',' ')}, /* Classical Sanskrit -> Sanskrit */
{HB_TAG('c','l','t',' '), HB_TAG('Q','I','N',' ')}, /* Lautu Chin -> Chin */
+/*{HB_TAG('c','m','i',' '), HB_TAG('C','M','I',' ')},*/ /* Emberá-Chamí */
{HB_TAG('c','m','n',' '), HB_TAG('Z','H','S',' ')}, /* Mandarin Chinese -> Chinese, Simplified */
{HB_TAG('c','m','r',' '), HB_TAG('Q','I','N',' ')}, /* Mro-Khimi Chin -> Chin */
{HB_TAG('c','n','b',' '), HB_TAG('Q','I','N',' ')}, /* Chinbon Chin -> Chin */
@@ -480,6 +488,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('c','n','w',' '), HB_TAG('Q','I','N',' ')}, /* Ngawn Chin -> Chin */
{HB_TAG('c','o','a',' '), HB_TAG('M','L','Y',' ')}, /* Cocos Islands Malay -> Malay */
{HB_TAG('c','o','b',' '), HB_TAG('M','Y','N',' ')}, /* Chicomuceltec -> Mayan */
+/*{HB_TAG('c','o','o',' '), HB_TAG('C','O','O',' ')},*/ /* Comox */
/*{HB_TAG('c','o','p',' '), HB_TAG('C','O','P',' ')},*/ /* Coptic */
{HB_TAG('c','o','q',' '), HB_TAG('A','T','H',' ')}, /* Coquille -> Athapaskan */
{HB_TAG('c','p','a',' '), HB_TAG('C','C','H','N')}, /* Palantla Chinantec -> Chinantec */
@@ -529,6 +538,7 @@ static const LangTag ot_languages3[] = {
/*{HB_TAG('c','t','g',' '), HB_TAG('C','T','G',' ')},*/ /* Chittagonian */
{HB_TAG('c','t','h',' '), HB_TAG('Q','I','N',' ')}, /* Thaiphum Chin -> Chin */
{HB_TAG('c','t','l',' '), HB_TAG('C','C','H','N')}, /* Tlacoatzintepec Chinantec -> Chinantec */
+/*{HB_TAG('c','t','o',' '), HB_TAG('C','T','O',' ')},*/ /* Emberá-Catío */
{HB_TAG('c','t','s',' '), HB_TAG('B','I','K',' ')}, /* Northern Catanduanes Bikol -> Bikol */
/*{HB_TAG('c','t','t',' '), HB_TAG('C','T','T',' ')},*/ /* Wayanad Chetti */
{HB_TAG('c','t','u',' '), HB_TAG('M','Y','N',' ')}, /* Chol -> Mayan */
@@ -552,7 +562,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('d','e','p',' '), HB_TAG('C','P','P',' ')}, /* Pidgin Delaware -> Creoles */
{HB_TAG('d','g','o',' '), HB_TAG('D','G','O',' ')}, /* Dogri (individual language) */
{HB_TAG('d','g','o',' '), HB_TAG('D','G','R',' ')}, /* Dogri (macrolanguage) */
- {HB_TAG('d','g','r',' '), HB_TAG('A','T','H',' ')}, /* Dogrib -> Athapaskan */
+ {HB_TAG('d','g','r',' '), HB_TAG('A','T','H',' ')}, /* Tlicho -> Athapaskan */
{HB_TAG('d','h','d',' '), HB_TAG('M','A','W',' ')}, /* Dhundari -> Marwari */
/*{HB_TAG('d','h','g',' '), HB_TAG('D','H','G',' ')},*/ /* Dhangu */
{HB_TAG('d','h','v',' '), HB_TAG_NONE }, /* Dehu != Divehi (Dhivehi, Maldivian) (deprecated) */
@@ -591,6 +601,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('e','k','y',' '), HB_TAG('K','R','N',' ')}, /* Eastern Kayah -> Karen */
{HB_TAG('e','m','k',' '), HB_TAG('E','M','K',' ')}, /* Eastern Maninkakan */
{HB_TAG('e','m','k',' '), HB_TAG('M','N','K',' ')}, /* Eastern Maninkakan -> Maninka */
+/*{HB_TAG('e','m','p',' '), HB_TAG('E','M','P',' ')},*/ /* Northern Emberá */
{HB_TAG('e','m','y',' '), HB_TAG('M','Y','N',' ')}, /* Epigraphic Mayan -> Mayan */
{HB_TAG('e','n','b',' '), HB_TAG('K','A','L',' ')}, /* Markweeta -> Kalenjin */
{HB_TAG('e','n','f',' '), HB_TAG('F','N','E',' ')}, /* Forest Enets */
@@ -655,6 +666,7 @@ static const LangTag ot_languages3[] = {
/*{HB_TAG('g','e','z',' '), HB_TAG('G','E','Z',' ')},*/ /* Geez */
{HB_TAG('g','g','o',' '), HB_TAG('G','O','N',' ')}, /* Southern Gondi (retired code) -> Gondi */
{HB_TAG('g','h','a',' '), HB_TAG('B','B','R',' ')}, /* Ghadamès -> Berber */
+ {HB_TAG('g','h','c',' '), HB_TAG('I','R','T',' ')}, /* Hiberno-Scottish Gaelic -> Irish Traditional */
{HB_TAG('g','h','k',' '), HB_TAG('K','R','N',' ')}, /* Geko Karen -> Karen */
{HB_TAG('g','h','o',' '), HB_TAG('B','B','R',' ')}, /* Ghomara -> Berber */
{HB_TAG('g','i','b',' '), HB_TAG('C','P','P',' ')}, /* Gibanawa -> Creoles */
@@ -744,6 +756,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('h','s','n',' '), HB_TAG('Z','H','S',' ')}, /* Xiang Chinese -> Chinese, Simplified */
{HB_TAG('h','u','j',' '), HB_TAG('H','M','N',' ')}, /* Northern Guiyang Hmong -> Hmong */
{HB_TAG('h','u','p',' '), HB_TAG('A','T','H',' ')}, /* Hupa -> Athapaskan */
+/*{HB_TAG('h','u','r',' '), HB_TAG('H','U','R',' ')},*/ /* Halkomelem */
{HB_TAG('h','u','s',' '), HB_TAG('M','Y','N',' ')}, /* Huastec -> Mayan */
{HB_TAG('h','w','c',' '), HB_TAG('C','P','P',' ')}, /* Hawai'i Creole English -> Creoles */
{HB_TAG('h','y','w',' '), HB_TAG('H','Y','E',' ')}, /* Western Armenian -> Armenian */
@@ -781,6 +794,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('j','b','n',' '), HB_TAG('B','B','R',' ')}, /* Nafusi -> Berber */
/*{HB_TAG('j','b','o',' '), HB_TAG('J','B','O',' ')},*/ /* Lojban */
/*{HB_TAG('j','c','t',' '), HB_TAG('J','C','T',' ')},*/ /* Krymchak */
+/*{HB_TAG('j','d','t',' '), HB_TAG('J','D','T',' ')},*/ /* Judeo-Tat */
{HB_TAG('j','g','o',' '), HB_TAG('B','M','L',' ')}, /* Ngomba -> Bamileke */
{HB_TAG('j','i','i',' '), HB_TAG_NONE }, /* Jiiddu != Yiddish */
{HB_TAG('j','k','m',' '), HB_TAG('K','R','N',' ')}, /* Mobwa Karen -> Karen */
@@ -795,6 +809,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('k','a','m',' '), HB_TAG('K','M','B',' ')}, /* Kamba (Kenya) */
{HB_TAG('k','a','r',' '), HB_TAG('K','R','N',' ')}, /* Karen [collection] */
/*{HB_TAG('k','a','w',' '), HB_TAG('K','A','W',' ')},*/ /* Kawi (Old Javanese) */
+/*{HB_TAG('k','b','c',' '), HB_TAG('K','B','C',' ')},*/ /* Kadiwéu */
{HB_TAG('k','b','d',' '), HB_TAG('K','A','B',' ')}, /* Kabardian */
{HB_TAG('k','b','y',' '), HB_TAG('K','N','R',' ')}, /* Manga Kanuri -> Kanuri */
{HB_TAG('k','c','a',' '), HB_TAG('K','H','K',' ')}, /* Khanty -> Khanty-Kazim */
@@ -830,6 +845,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('k','j','b',' '), HB_TAG('M','Y','N',' ')}, /* Q'anjob'al -> Mayan */
/*{HB_TAG('k','j','d',' '), HB_TAG('K','J','D',' ')},*/ /* Southern Kiwai */
{HB_TAG('k','j','h',' '), HB_TAG('K','H','A',' ')}, /* Khakas -> Khakass */
+/*{HB_TAG('k','j','j',' '), HB_TAG('K','J','J',' ')},*/ /* Khinalugh -> Khinalug */
{HB_TAG('k','j','p',' '), HB_TAG('K','J','P',' ')}, /* Pwo Eastern Karen -> Eastern Pwo Karen */
{HB_TAG('k','j','p',' '), HB_TAG('K','R','N',' ')}, /* Pwo Eastern Karen -> Karen */
{HB_TAG('k','j','t',' '), HB_TAG('K','R','N',' ')}, /* Phrae Pwo Karen -> Karen */
@@ -931,6 +947,7 @@ static const LangTag ot_languages3[] = {
/*{HB_TAG('l','i','j',' '), HB_TAG('L','I','J',' ')},*/ /* Ligurian */
{HB_TAG('l','i','r',' '), HB_TAG('C','P','P',' ')}, /* Liberian English -> Creoles */
/*{HB_TAG('l','i','s',' '), HB_TAG('L','I','S',' ')},*/ /* Lisu */
+/*{HB_TAG('l','i','v',' '), HB_TAG('L','I','V',' ')},*/ /* Liv */
{HB_TAG('l','i','w',' '), HB_TAG('M','L','Y',' ')}, /* Col -> Malay */
{HB_TAG('l','i','y',' '), HB_TAG('B','A','D','0')}, /* Banda-Bambari -> Banda */
/*{HB_TAG('l','j','p',' '), HB_TAG('L','J','P',' ')},*/ /* Lampung Api -> Lampung */
@@ -996,12 +1013,14 @@ static const LangTag ot_languages3[] = {
{HB_TAG('m','e','n',' '), HB_TAG('M','D','E',' ')}, /* Mende (Sierra Leone) */
{HB_TAG('m','e','o',' '), HB_TAG('M','L','Y',' ')}, /* Kedah Malay -> Malay */
/*{HB_TAG('m','e','r',' '), HB_TAG('M','E','R',' ')},*/ /* Meru */
+/*{HB_TAG('m','e','v',' '), HB_TAG('M','E','V',' ')},*/ /* Mano */
{HB_TAG('m','f','a',' '), HB_TAG('M','F','A',' ')}, /* Pattani Malay */
{HB_TAG('m','f','a',' '), HB_TAG('M','L','Y',' ')}, /* Pattani Malay -> Malay */
{HB_TAG('m','f','b',' '), HB_TAG('M','L','Y',' ')}, /* Bangka -> Malay */
{HB_TAG('m','f','e',' '), HB_TAG('M','F','E',' ')}, /* Morisyen */
{HB_TAG('m','f','e',' '), HB_TAG('C','P','P',' ')}, /* Morisyen -> Creoles */
{HB_TAG('m','f','p',' '), HB_TAG('C','P','P',' ')}, /* Makassar Malay -> Creoles */
+ {HB_TAG('m','g','a',' '), HB_TAG('S','G','A',' ')}, /* Middle Irish (900-1200) -> Old Irish */
{HB_TAG('m','h','c',' '), HB_TAG('M','Y','N',' ')}, /* Mocho -> Mayan */
{HB_TAG('m','h','r',' '), HB_TAG('L','M','A',' ')}, /* Eastern Mari -> Low Mari */
{HB_TAG('m','h','v',' '), HB_TAG('A','R','K',' ')}, /* Arakanese (retired code) -> Rakhine */
@@ -1154,6 +1173,8 @@ static const LangTag ot_languages3[] = {
{HB_TAG('o','k','i',' '), HB_TAG('K','A','L',' ')}, /* Okiek -> Kalenjin */
{HB_TAG('o','k','m',' '), HB_TAG('K','O','H',' ')}, /* Middle Korean (10th-16th cent.) -> Korean Old Hangul */
{HB_TAG('o','k','r',' '), HB_TAG('I','J','O',' ')}, /* Kirike -> Ijo */
+/*{HB_TAG('o','n','e',' '), HB_TAG('O','N','E',' ')},*/ /* Oneida */
+/*{HB_TAG('o','n','o',' '), HB_TAG('O','N','O',' ')},*/ /* Onondaga */
{HB_TAG('o','n','x',' '), HB_TAG('C','P','P',' ')}, /* Onin Based Pidgin -> Creoles */
{HB_TAG('o','o','r',' '), HB_TAG('C','P','P',' ')}, /* Oorlams -> Creoles */
{HB_TAG('o','r','c',' '), HB_TAG('O','R','O',' ')}, /* Orma -> Oromo */
@@ -1194,7 +1215,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('p','i','s',' '), HB_TAG('C','P','P',' ')}, /* Pijin -> Creoles */
{HB_TAG('p','k','h',' '), HB_TAG('Q','I','N',' ')}, /* Pankhu -> Chin */
{HB_TAG('p','k','o',' '), HB_TAG('K','A','L',' ')}, /* Pökoot -> Kalenjin */
- {HB_TAG('p','l','g',' '), HB_TAG_NONE }, /* Pilagá != Palaung */
+ {HB_TAG('p','l','g',' '), HB_TAG('P','L','G','0')}, /* Pilagá */
{HB_TAG('p','l','k',' '), HB_TAG_NONE }, /* Kohistani Shina != Polish */
{HB_TAG('p','l','l',' '), HB_TAG('P','L','G',' ')}, /* Shwe Palaung -> Palaung */
{HB_TAG('p','l','n',' '), HB_TAG('C','P','P',' ')}, /* Palenquero -> Creoles */
@@ -1354,6 +1375,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('s','d','h',' '), HB_TAG('K','U','R',' ')}, /* Southern Kurdish -> Kurdish */
{HB_TAG('s','d','n',' '), HB_TAG('S','R','D',' ')}, /* Gallurese Sardinian -> Sardinian */
{HB_TAG('s','d','s',' '), HB_TAG('B','B','R',' ')}, /* Sened -> Berber */
+/*{HB_TAG('s','e','e',' '), HB_TAG('S','E','E',' ')},*/ /* Seneca */
{HB_TAG('s','e','h',' '), HB_TAG('S','N','A',' ')}, /* Sena */
{HB_TAG('s','e','k',' '), HB_TAG('A','T','H',' ')}, /* Sekani -> Athapaskan */
/*{HB_TAG('s','e','l',' '), HB_TAG('S','E','L',' ')},*/ /* Selkup */
@@ -1375,6 +1397,7 @@ static const LangTag ot_languages3[] = {
/*{HB_TAG('s','i','d',' '), HB_TAG('S','I','D',' ')},*/ /* Sidamo */
{HB_TAG('s','i','g',' '), HB_TAG_NONE }, /* Paasaal != Silte Gurage */
{HB_TAG('s','i','z',' '), HB_TAG('B','B','R',' ')}, /* Siwi -> Berber */
+/*{HB_TAG('s','j','a',' '), HB_TAG('S','J','A',' ')},*/ /* Epena */
{HB_TAG('s','j','d',' '), HB_TAG('K','S','M',' ')}, /* Kildin Sami */
{HB_TAG('s','j','o',' '), HB_TAG('S','I','B',' ')}, /* Xibe -> Sibe */
{HB_TAG('s','j','s',' '), HB_TAG('B','B','R',' ')}, /* Senhaja De Srair -> Berber */
@@ -1411,6 +1434,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('s','s','m',' '), HB_TAG_NONE }, /* Semnam != Southern Sami */
{HB_TAG('s','t','a',' '), HB_TAG('C','P','P',' ')}, /* Settla -> Creoles */
/*{HB_TAG('s','t','q',' '), HB_TAG('S','T','Q',' ')},*/ /* Saterfriesisch -> Saterland Frisian */
+/*{HB_TAG('s','t','r',' '), HB_TAG('S','T','R',' ')},*/ /* Straits Salish */
{HB_TAG('s','t','v',' '), HB_TAG('S','I','G',' ')}, /* Silt'e -> Silte Gurage */
/*{HB_TAG('s','u','k',' '), HB_TAG('S','U','K',' ')},*/ /* Sukuma */
{HB_TAG('s','u','q',' '), HB_TAG('S','U','R',' ')}, /* Suri */
@@ -1432,6 +1456,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('t','a','a',' '), HB_TAG('A','T','H',' ')}, /* Lower Tanana -> Athapaskan */
/*{HB_TAG('t','a','b',' '), HB_TAG('T','A','B',' ')},*/ /* Tabassaran -> Tabasaran */
{HB_TAG('t','a','j',' '), HB_TAG_NONE }, /* Eastern Tamang != Tajiki */
+ {HB_TAG('t','a','q',' '), HB_TAG('T','A','Q',' ')}, /* Tamasheq */
{HB_TAG('t','a','q',' '), HB_TAG('T','M','H',' ')}, /* Tamasheq -> Tamashek */
{HB_TAG('t','a','q',' '), HB_TAG('B','B','R',' ')}, /* Tamasheq -> Berber */
{HB_TAG('t','a','s',' '), HB_TAG('C','P','P',' ')}, /* Tay Boi -> Creoles */
@@ -1443,6 +1468,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('t','c','s',' '), HB_TAG('C','P','P',' ')}, /* Torres Strait Creole -> Creoles */
{HB_TAG('t','c','y',' '), HB_TAG('T','U','L',' ')}, /* Tulu */
{HB_TAG('t','c','z',' '), HB_TAG('Q','I','N',' ')}, /* Thado Chin -> Chin */
+/*{HB_TAG('t','d','c',' '), HB_TAG('T','D','C',' ')},*/ /* Emberá-Tadó */
/*{HB_TAG('t','d','d',' '), HB_TAG('T','D','D',' ')},*/ /* Tai Nüa -> Dehong Dai */
{HB_TAG('t','d','x',' '), HB_TAG('M','L','G',' ')}, /* Tandroy-Mahafaly Malagasy -> Malagasy */
{HB_TAG('t','e','c',' '), HB_TAG('K','A','L',' ')}, /* Terik -> Kalenjin */
@@ -1456,9 +1482,12 @@ static const LangTag ot_languages3[] = {
{HB_TAG('t','g','r',' '), HB_TAG_NONE }, /* Tareng != Tigre */
{HB_TAG('t','g','x',' '), HB_TAG('A','T','H',' ')}, /* Tagish -> Athapaskan */
{HB_TAG('t','g','y',' '), HB_TAG_NONE }, /* Togoyo != Tigrinya */
+/*{HB_TAG('t','h','p',' '), HB_TAG('T','H','P',' ')},*/ /* Thompson */
{HB_TAG('t','h','t',' '), HB_TAG('A','T','H',' ')}, /* Tahltan -> Athapaskan */
+ {HB_TAG('t','h','v',' '), HB_TAG('T','H','V',' ')}, /* Tahaggart Tamahaq */
{HB_TAG('t','h','v',' '), HB_TAG('T','M','H',' ')}, /* Tahaggart Tamahaq -> Tamashek */
{HB_TAG('t','h','v',' '), HB_TAG('B','B','R',' ')}, /* Tahaggart Tamahaq -> Berber */
+ {HB_TAG('t','h','z',' '), HB_TAG('T','H','Z',' ')}, /* Tayart Tamajeq */
{HB_TAG('t','h','z',' '), HB_TAG('T','M','H',' ')}, /* Tayart Tamajeq -> Tamashek */
{HB_TAG('t','h','z',' '), HB_TAG('B','B','R',' ')}, /* Tayart Tamajeq -> Berber */
{HB_TAG('t','i','a',' '), HB_TAG('B','B','R',' ')}, /* Tidikelt Tamazight -> Berber */
@@ -1469,6 +1498,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('t','k','g',' '), HB_TAG('M','L','G',' ')}, /* Tesaka Malagasy -> Malagasy */
{HB_TAG('t','k','m',' '), HB_TAG_NONE }, /* Takelma != Turkmen */
/*{HB_TAG('t','l','i',' '), HB_TAG('T','L','I',' ')},*/ /* Tlingit */
+/*{HB_TAG('t','l','y',' '), HB_TAG('T','L','Y',' ')},*/ /* Talysh */
{HB_TAG('t','m','g',' '), HB_TAG('C','P','P',' ')}, /* Ternateño -> Creoles */
{HB_TAG('t','m','h',' '), HB_TAG('T','M','H',' ')}, /* Tamashek [macrolanguage] */
{HB_TAG('t','m','h',' '), HB_TAG('B','B','R',' ')}, /* Tamashek [macrolanguage] -> Berber */
@@ -1494,11 +1524,13 @@ static const LangTag ot_languages3[] = {
/*{HB_TAG('t','s','j',' '), HB_TAG('T','S','J',' ')},*/ /* Tshangla */
{HB_TAG('t','t','c',' '), HB_TAG('M','Y','N',' ')}, /* Tektiteko -> Mayan */
{HB_TAG('t','t','m',' '), HB_TAG('A','T','H',' ')}, /* Northern Tutchone -> Athapaskan */
+ {HB_TAG('t','t','q',' '), HB_TAG('T','T','Q',' ')}, /* Tawallammat Tamajaq */
{HB_TAG('t','t','q',' '), HB_TAG('T','M','H',' ')}, /* Tawallammat Tamajaq -> Tamashek */
{HB_TAG('t','t','q',' '), HB_TAG('B','B','R',' ')}, /* Tawallammat Tamajaq -> Berber */
{HB_TAG('t','u','a',' '), HB_TAG_NONE }, /* Wiarumus != Turoyo Aramaic */
{HB_TAG('t','u','l',' '), HB_TAG_NONE }, /* Tula != Tulu */
/*{HB_TAG('t','u','m',' '), HB_TAG('T','U','M',' ')},*/ /* Tumbuka */
+/*{HB_TAG('t','u','s',' '), HB_TAG('T','U','S',' ')},*/ /* Tuscarora */
{HB_TAG('t','u','u',' '), HB_TAG('A','T','H',' ')}, /* Tututni -> Athapaskan */
{HB_TAG('t','u','v',' '), HB_TAG_NONE }, /* Turkana != Tuvin */
{HB_TAG('t','u','y',' '), HB_TAG('K','A','L',' ')}, /* Tugen -> Kalenjin */
@@ -1515,6 +1547,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('t','z','o',' '), HB_TAG('T','Z','O',' ')}, /* Tzotzil */
{HB_TAG('t','z','o',' '), HB_TAG('M','Y','N',' ')}, /* Tzotzil -> Mayan */
{HB_TAG('u','b','l',' '), HB_TAG('B','I','K',' ')}, /* Buhi'non Bikol -> Bikol */
+/*{HB_TAG('u','d','i',' '), HB_TAG('U','D','I',' ')},*/ /* Udi */
/*{HB_TAG('u','d','m',' '), HB_TAG('U','D','M',' ')},*/ /* Udmurt */
{HB_TAG('u','k','i',' '), HB_TAG('K','U','I',' ')}, /* Kui (India) */
{HB_TAG('u','l','n',' '), HB_TAG('C','P','P',' ')}, /* Unserdeutsch -> Creoles */
@@ -1533,14 +1566,17 @@ static const LangTag ot_languages3[] = {
{HB_TAG('v','k','t',' '), HB_TAG('M','L','Y',' ')}, /* Tenggarong Kutai Malay -> Malay */
{HB_TAG('v','l','s',' '), HB_TAG('F','L','E',' ')}, /* Vlaams -> Dutch (Flemish) */
{HB_TAG('v','m','w',' '), HB_TAG('M','A','K',' ')}, /* Makhuwa */
-/*{HB_TAG('v','r','o',' '), HB_TAG('V','R','O',' ')},*/ /* Võro */
+ {HB_TAG('v','r','o',' '), HB_TAG('V','R','O',' ')}, /* Võro */
+ {HB_TAG('v','r','o',' '), HB_TAG('E','T','I',' ')}, /* Võro -> Estonian */
{HB_TAG('v','s','n',' '), HB_TAG('S','A','N',' ')}, /* Vedic Sanskrit -> Sanskrit */
{HB_TAG('w','a','g',' '), HB_TAG_NONE }, /* Wa'ema != Wagdi */
/*{HB_TAG('w','a','r',' '), HB_TAG('W','A','R',' ')},*/ /* Waray (Philippines) -> Waray-Waray */
+/*{HB_TAG('w','b','l',' '), HB_TAG('W','B','L',' ')},*/ /* Wakhi */
{HB_TAG('w','b','m',' '), HB_TAG('W','A',' ',' ')}, /* Wa */
{HB_TAG('w','b','r',' '), HB_TAG('W','A','G',' ')}, /* Wagdi */
{HB_TAG('w','b','r',' '), HB_TAG('R','A','J',' ')}, /* Wagdi -> Rajasthani */
/*{HB_TAG('w','c','i',' '), HB_TAG('W','C','I',' ')},*/ /* Waci Gbe */
+/*{HB_TAG('w','d','t',' '), HB_TAG('W','D','T',' ')},*/ /* Wendat */
{HB_TAG('w','e','a',' '), HB_TAG('K','R','N',' ')}, /* Wewaw -> Karen */
{HB_TAG('w','e','s',' '), HB_TAG('C','P','P',' ')}, /* Cameroon Pidgin -> Creoles */
{HB_TAG('w','e','u',' '), HB_TAG('Q','I','N',' ')}, /* Rawngtu Chin -> Chin */
@@ -1552,6 +1588,9 @@ static const LangTag ot_languages3[] = {
{HB_TAG('w','s','g',' '), HB_TAG('G','O','N',' ')}, /* Adilabad Gondi -> Gondi */
/*{HB_TAG('w','t','m',' '), HB_TAG('W','T','M',' ')},*/ /* Mewati */
{HB_TAG('w','u','u',' '), HB_TAG('Z','H','S',' ')}, /* Wu Chinese -> Chinese, Simplified */
+ {HB_TAG('w','y','a',' '), HB_TAG('W','D','T',' ')}, /* Wyandot (retired code) -> Wendat */
+ {HB_TAG('w','y','a',' '), HB_TAG('W','Y','N',' ')}, /* Wyandot (retired code) */
+/*{HB_TAG('w','y','n',' '), HB_TAG('W','Y','N',' ')},*/ /* Wyandot */
{HB_TAG('x','a','l',' '), HB_TAG('K','L','M',' ')}, /* Kalmyk */
{HB_TAG('x','a','l',' '), HB_TAG('T','O','D',' ')}, /* Kalmyk -> Todo */
{HB_TAG('x','a','n',' '), HB_TAG('S','E','K',' ')}, /* Xamtanga -> Sekota */
@@ -1593,6 +1632,7 @@ static const LangTag ot_languages3[] = {
{HB_TAG('y','o','s',' '), HB_TAG('Q','I','N',' ')}, /* Yos (retired code) -> Chin */
{HB_TAG('y','u','a',' '), HB_TAG('M','Y','N',' ')}, /* Yucateco -> Mayan */
{HB_TAG('y','u','e',' '), HB_TAG('Z','H','H',' ')}, /* Yue Chinese -> Chinese, Traditional, Hong Kong SAR */
+/*{HB_TAG('y','u','f',' '), HB_TAG('Y','U','F',' ')},*/ /* Havasupai-Walapai-Yavapai */
/*{HB_TAG('y','w','q',' '), HB_TAG('Y','W','Q',' ')},*/ /* Wuding-Luquan Yi */
{HB_TAG('z','c','h',' '), HB_TAG('Z','H','A',' ')}, /* Central Hongshuihe Zhuang -> Zhuang */
{HB_TAG('z','d','j',' '), HB_TAG('C','M','R',' ')}, /* Ngazidja Comorian -> Comorian */
@@ -2645,7 +2685,7 @@ out:
/* Romanian; Moldova */
unsigned int i;
hb_tag_t possible_tags[] = {
- HB_TAG('M','O','L',' '), /* Romanian (Moldova) */
+ HB_TAG('M','O','L',' '), /* Moldavian */
HB_TAG('R','O','M',' '), /* Romanian */
};
for (i = 0; i < 2 && i < *count; i++)
@@ -2872,7 +2912,7 @@ hb_ot_ambiguous_tag_to_language (hb_tag_t tag)
case HB_TAG('I','P','P','H'): /* Phonetic transcription—IPA conventions */
return hb_language_from_string ("und-fonipa", -1); /* Undetermined; International Phonetic Alphabet */
case HB_TAG('I','R','T',' '): /* Irish Traditional */
- return hb_language_from_string ("ga-Latg", -1); /* Irish; Latin (Gaelic variant) */
+ return hb_language_from_string ("ghc", -1); /* Hiberno-Scottish Gaelic */
case HB_TAG('J','I','I',' '): /* Yiddish */
return hb_language_from_string ("yi", -1); /* Yiddish [macrolanguage] */
case HB_TAG('K','A','L',' '): /* Kalenjin */
@@ -2899,7 +2939,7 @@ hb_ot_ambiguous_tag_to_language (hb_tag_t tag)
return hb_language_from_string ("ms", -1); /* Malay [macrolanguage] */
case HB_TAG('M','N','K',' '): /* Maninka */
return hb_language_from_string ("man", -1); /* Mandingo [macrolanguage] */
- case HB_TAG('M','O','L',' '): /* Romanian (Moldova) */
+ case HB_TAG('M','O','L',' '): /* Moldavian */
return hb_language_from_string ("ro-MD", -1); /* Romanian; Moldova */
case HB_TAG('M','O','N','T'): /* Thailand Mon */
return hb_language_from_string ("mnw-TH", -1); /* Mon; Thailand */
@@ -2927,6 +2967,8 @@ hb_ot_ambiguous_tag_to_language (hb_tag_t tag)
return hb_language_from_string ("ro", -1); /* Romanian */
case HB_TAG('R','O','Y',' '): /* Romany */
return hb_language_from_string ("rom", -1); /* Romany [macrolanguage] */
+ case HB_TAG('S','G','A',' '): /* Old Irish */
+ return hb_language_from_string ("sga", -1); /* Old Irish (to 900) */
case HB_TAG('S','R','B',' '): /* Serbian */
return hb_language_from_string ("sr", -1); /* Serbian */
case HB_TAG('S','X','T',' '): /* Sutu */
@@ -2943,6 +2985,10 @@ hb_ot_ambiguous_tag_to_language (hb_tag_t tag)
return hb_language_from_string ("tmh", -1); /* Tamashek [macrolanguage] */
case HB_TAG('T','O','D',' '): /* Todo */
return hb_language_from_string ("xwo", -1); /* Written Oirat */
+ case HB_TAG('W','D','T',' '): /* Wendat */
+ return hb_language_from_string ("wdt", -1); /* Wendat */
+ case HB_TAG('W','Y','N',' '): /* Wyandot */
+ return hb_language_from_string ("wyn", -1); /* Wyandot */
case HB_TAG('Z','H','H',' '): /* Chinese, Traditional, Hong Kong SAR */
return hb_language_from_string ("zh-HK", -1); /* Chinese [macrolanguage]; Hong Kong */
case HB_TAG('Z','H','S',' '): /* Chinese, Simplified */
diff --git a/thirdparty/harfbuzz/src/hb-ot-var-common.hh b/thirdparty/harfbuzz/src/hb-ot-var-common.hh
index 08227aa1df..efbbfb25d7 100644
--- a/thirdparty/harfbuzz/src/hb-ot-var-common.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-var-common.hh
@@ -33,213 +33,6 @@
namespace OT {
-template <typename MapCountT>
-struct DeltaSetIndexMapFormat01
-{
- friend struct DeltaSetIndexMap;
-
- unsigned get_size () const
- { return min_size + mapCount * get_width (); }
-
- private:
- DeltaSetIndexMapFormat01* copy (hb_serialize_context_t *c) const
- {
- TRACE_SERIALIZE (this);
- return_trace (c->embed (this));
- }
-
- template <typename T>
- bool serialize (hb_serialize_context_t *c, const T &plan)
- {
- unsigned int width = plan.get_width ();
- unsigned int inner_bit_count = plan.get_inner_bit_count ();
- const hb_array_t<const uint32_t> output_map = plan.get_output_map ();
-
- TRACE_SERIALIZE (this);
- if (unlikely (output_map.length && ((((inner_bit_count-1)&~0xF)!=0) || (((width-1)&~0x3)!=0))))
- return_trace (false);
- if (unlikely (!c->extend_min (this))) return_trace (false);
-
- entryFormat = ((width-1)<<4)|(inner_bit_count-1);
- mapCount = output_map.length;
- HBUINT8 *p = c->allocate_size<HBUINT8> (width * output_map.length);
- if (unlikely (!p)) return_trace (false);
- for (unsigned int i = 0; i < output_map.length; i++)
- {
- unsigned int v = output_map.arrayZ[i];
- if (v)
- {
- unsigned int outer = v >> 16;
- unsigned int inner = v & 0xFFFF;
- unsigned int u = (outer << inner_bit_count) | inner;
- for (unsigned int w = width; w > 0;)
- {
- p[--w] = u;
- u >>= 8;
- }
- }
- p += width;
- }
- return_trace (true);
- }
-
- uint32_t map (unsigned int v) const /* Returns 16.16 outer.inner. */
- {
- /* If count is zero, pass value unchanged. This takes
- * care of direct mapping for advance map. */
- if (!mapCount)
- return v;
-
- if (v >= mapCount)
- v = mapCount - 1;
-
- unsigned int u = 0;
- { /* Fetch it. */
- unsigned int w = get_width ();
- const HBUINT8 *p = mapDataZ.arrayZ + w * v;
- for (; w; w--)
- u = (u << 8) + *p++;
- }
-
- { /* Repack it. */
- unsigned int n = get_inner_bit_count ();
- unsigned int outer = u >> n;
- unsigned int inner = u & ((1 << n) - 1);
- u = (outer<<16) | inner;
- }
-
- return u;
- }
-
- unsigned get_map_count () const { return mapCount; }
- unsigned get_width () const { return ((entryFormat >> 4) & 3) + 1; }
- unsigned get_inner_bit_count () const { return (entryFormat & 0xF) + 1; }
-
-
- bool sanitize (hb_sanitize_context_t *c) const
- {
- TRACE_SANITIZE (this);
- return_trace (c->check_struct (this) &&
- hb_barrier () &&
- c->check_range (mapDataZ.arrayZ,
- mapCount,
- get_width ()));
- }
-
- protected:
- HBUINT8 format; /* Format identifier--format = 0 */
- HBUINT8 entryFormat; /* A packed field that describes the compressed
- * representation of delta-set indices. */
- MapCountT mapCount; /* The number of mapping entries. */
- UnsizedArrayOf<HBUINT8>
- mapDataZ; /* The delta-set index mapping data. */
-
- public:
- DEFINE_SIZE_ARRAY (2+MapCountT::static_size, mapDataZ);
-};
-
-struct DeltaSetIndexMap
-{
- template <typename T>
- bool serialize (hb_serialize_context_t *c, const T &plan)
- {
- TRACE_SERIALIZE (this);
- unsigned length = plan.get_output_map ().length;
- u.format = length <= 0xFFFF ? 0 : 1;
- switch (u.format) {
- case 0: return_trace (u.format0.serialize (c, plan));
- case 1: return_trace (u.format1.serialize (c, plan));
- default:return_trace (false);
- }
- }
-
- uint32_t map (unsigned v) const
- {
- switch (u.format) {
- case 0: return (u.format0.map (v));
- case 1: return (u.format1.map (v));
- default:return v;
- }
- }
-
- unsigned get_map_count () const
- {
- switch (u.format) {
- case 0: return u.format0.get_map_count ();
- case 1: return u.format1.get_map_count ();
- default:return 0;
- }
- }
-
- unsigned get_width () const
- {
- switch (u.format) {
- case 0: return u.format0.get_width ();
- case 1: return u.format1.get_width ();
- default:return 0;
- }
- }
-
- unsigned get_inner_bit_count () const
- {
- switch (u.format) {
- case 0: return u.format0.get_inner_bit_count ();
- case 1: return u.format1.get_inner_bit_count ();
- default:return 0;
- }
- }
-
- bool sanitize (hb_sanitize_context_t *c) const
- {
- TRACE_SANITIZE (this);
- if (!u.format.sanitize (c)) return_trace (false);
- hb_barrier ();
- switch (u.format) {
- case 0: return_trace (u.format0.sanitize (c));
- case 1: return_trace (u.format1.sanitize (c));
- default:return_trace (true);
- }
- }
-
- DeltaSetIndexMap* copy (hb_serialize_context_t *c) const
- {
- TRACE_SERIALIZE (this);
- switch (u.format) {
- case 0: return_trace (reinterpret_cast<DeltaSetIndexMap *> (u.format0.copy (c)));
- case 1: return_trace (reinterpret_cast<DeltaSetIndexMap *> (u.format1.copy (c)));
- default:return_trace (nullptr);
- }
- }
-
- protected:
- union {
- HBUINT8 format; /* Format identifier */
- DeltaSetIndexMapFormat01<HBUINT16> format0;
- DeltaSetIndexMapFormat01<HBUINT32> format1;
- } u;
- public:
- DEFINE_SIZE_UNION (1, format);
-};
-
-
-struct ItemVarStoreInstancer
-{
- ItemVarStoreInstancer (const ItemVariationStore *varStore,
- const DeltaSetIndexMap *varIdxMap,
- hb_array_t<int> coords) :
- varStore (varStore), varIdxMap (varIdxMap), coords (coords) {}
-
- operator bool () const { return varStore && bool (coords); }
-
- /* according to the spec, if colr table has varStore but does not have
- * varIdxMap, then an implicit identity mapping is used */
- float operator() (uint32_t varIdx, unsigned short offset = 0) const
- { return coords ? varStore->get_delta (varIdxMap ? varIdxMap->map (VarIdx::add (varIdx, offset)) : varIdx + offset, coords) : 0; }
-
- const ItemVariationStore *varStore;
- const DeltaSetIndexMap *varIdxMap;
- hb_array_t<int> coords;
-};
/* https://docs.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#tuplevariationheader */
struct TupleVariationHeader
@@ -305,9 +98,9 @@ struct TupleVariationHeader
return true;
}
- double calculate_scalar (hb_array_t<int> coords, unsigned int coord_count,
- const hb_array_t<const F2DOT14> shared_tuples,
- const hb_vector_t<hb_pair_t<int,int>> *shared_tuple_active_idx = nullptr) const
+ double calculate_scalar (hb_array_t<const int> coords, unsigned int coord_count,
+ const hb_array_t<const F2DOT14> shared_tuples,
+ const hb_vector_t<hb_pair_t<int,int>> *shared_tuple_active_idx = nullptr) const
{
const F2DOT14 *peak_tuple;
@@ -428,13 +221,6 @@ struct TupleVariationHeader
DEFINE_SIZE_MIN (4);
};
-enum packed_delta_flag_t
-{
- DELTAS_ARE_ZERO = 0x80,
- DELTAS_ARE_WORDS = 0x40,
- DELTA_RUN_COUNT_MASK = 0x3F
-};
-
struct tuple_delta_t
{
static constexpr bool realloc_move = true; // Watch out when adding new members!
@@ -452,8 +238,8 @@ struct tuple_delta_t
/* compiled data: header and deltas
* compiled point data is saved in a hashmap within tuple_variations_t cause
* some point sets might be reused by different tuple variations */
- hb_vector_t<char> compiled_tuple_header;
- hb_vector_t<char> compiled_deltas;
+ hb_vector_t<unsigned char> compiled_tuple_header;
+ hb_vector_t<unsigned char> compiled_deltas;
/* compiled peak coords, empty for non-gvar tuples */
hb_vector_t<char> compiled_peak_coords;
@@ -728,10 +514,10 @@ struct tuple_delta_t
bool compile_deltas ()
{ return compile_deltas (indices, deltas_x, deltas_y, compiled_deltas); }
- bool compile_deltas (const hb_vector_t<bool> &point_indices,
- const hb_vector_t<double> &x_deltas,
- const hb_vector_t<double> &y_deltas,
- hb_vector_t<char> &compiled_deltas /* OUT */)
+ static bool compile_deltas (const hb_vector_t<bool> &point_indices,
+ const hb_vector_t<double> &x_deltas,
+ const hb_vector_t<double> &y_deltas,
+ hb_vector_t<unsigned char> &compiled_deltas /* OUT */)
{
hb_vector_t<int> rounded_deltas;
if (unlikely (!rounded_deltas.alloc (point_indices.length)))
@@ -745,15 +531,14 @@ struct tuple_delta_t
}
if (!rounded_deltas) return true;
- /* allocate enough memories 3 * num_deltas */
- unsigned alloc_len = 3 * rounded_deltas.length;
+ /* allocate enough memories 5 * num_deltas */
+ unsigned alloc_len = 5 * rounded_deltas.length;
if (y_deltas)
alloc_len *= 2;
if (unlikely (!compiled_deltas.resize (alloc_len))) return false;
- unsigned i = 0;
- unsigned encoded_len = encode_delta_run (i, compiled_deltas.as_array (), rounded_deltas);
+ unsigned encoded_len = compile_deltas (compiled_deltas, rounded_deltas);
if (y_deltas)
{
@@ -770,174 +555,15 @@ struct tuple_delta_t
}
if (j != rounded_deltas.length) return false;
- /* reset i because we reuse rounded_deltas for y_deltas */
- i = 0;
- encoded_len += encode_delta_run (i, compiled_deltas.as_array ().sub_array (encoded_len), rounded_deltas);
+ encoded_len += compile_deltas (compiled_deltas.as_array ().sub_array (encoded_len), rounded_deltas);
}
return compiled_deltas.resize (encoded_len);
}
- unsigned encode_delta_run (unsigned& i,
- hb_array_t<char> encoded_bytes,
- const hb_vector_t<int>& deltas) const
- {
- unsigned num_deltas = deltas.length;
- unsigned encoded_len = 0;
- while (i < num_deltas)
- {
- int val = deltas.arrayZ[i];
- if (val == 0)
- encoded_len += encode_delta_run_as_zeroes (i, encoded_bytes.sub_array (encoded_len), deltas);
- else if (val >= -128 && val <= 127)
- encoded_len += encode_delta_run_as_bytes (i, encoded_bytes.sub_array (encoded_len), deltas);
- else
- encoded_len += encode_delta_run_as_words (i, encoded_bytes.sub_array (encoded_len), deltas);
- }
- return encoded_len;
- }
-
- unsigned encode_delta_run_as_zeroes (unsigned& i,
- hb_array_t<char> encoded_bytes,
- const hb_vector_t<int>& deltas) const
- {
- unsigned num_deltas = deltas.length;
- unsigned run_length = 0;
- auto it = encoded_bytes.iter ();
- unsigned encoded_len = 0;
- while (i < num_deltas && deltas.arrayZ[i] == 0)
- {
- i++;
- run_length++;
- }
-
- while (run_length >= 64)
- {
- *it++ = char (DELTAS_ARE_ZERO | 63);
- run_length -= 64;
- encoded_len++;
- }
-
- if (run_length)
- {
- *it++ = char (DELTAS_ARE_ZERO | (run_length - 1));
- encoded_len++;
- }
- return encoded_len;
- }
-
- unsigned encode_delta_run_as_bytes (unsigned &i,
- hb_array_t<char> encoded_bytes,
- const hb_vector_t<int>& deltas) const
- {
- unsigned start = i;
- unsigned num_deltas = deltas.length;
- while (i < num_deltas)
- {
- int val = deltas.arrayZ[i];
- if (val > 127 || val < -128)
- break;
-
- /* from fonttools: if there're 2 or more zeros in a sequence,
- * it is better to start a new run to save bytes. */
- if (val == 0 && i + 1 < num_deltas && deltas.arrayZ[i+1] == 0)
- break;
-
- i++;
- }
- unsigned run_length = i - start;
-
- unsigned encoded_len = 0;
- auto it = encoded_bytes.iter ();
-
- while (run_length >= 64)
- {
- *it++ = 63;
- encoded_len++;
-
- for (unsigned j = 0; j < 64; j++)
- {
- *it++ = static_cast<char> (deltas.arrayZ[start + j]);
- encoded_len++;
- }
-
- start += 64;
- run_length -= 64;
- }
-
- if (run_length)
- {
- *it++ = run_length - 1;
- encoded_len++;
-
- while (start < i)
- {
- *it++ = static_cast<char> (deltas.arrayZ[start++]);
- encoded_len++;
- }
- }
-
- return encoded_len;
- }
-
- unsigned encode_delta_run_as_words (unsigned &i,
- hb_array_t<char> encoded_bytes,
- const hb_vector_t<int>& deltas) const
+ static unsigned compile_deltas (hb_array_t<unsigned char> encoded_bytes,
+ hb_array_t<const int> deltas)
{
- unsigned start = i;
- unsigned num_deltas = deltas.length;
- while (i < num_deltas)
- {
- int val = deltas.arrayZ[i];
-
- /* start a new run for a single zero value*/
- if (val == 0) break;
-
- /* from fonttools: continue word-encoded run if there's only one
- * single value in the range [-128, 127] because it is more compact.
- * Only start a new run when there're 2 continuous such values. */
- if (val >= -128 && val <= 127 &&
- i + 1 < num_deltas &&
- deltas.arrayZ[i+1] >= -128 && deltas.arrayZ[i+1] <= 127)
- break;
-
- i++;
- }
-
- unsigned run_length = i - start;
- auto it = encoded_bytes.iter ();
- unsigned encoded_len = 0;
- while (run_length >= 64)
- {
- *it++ = (DELTAS_ARE_WORDS | 63);
- encoded_len++;
-
- for (unsigned j = 0; j < 64; j++)
- {
- int16_t delta_val = deltas.arrayZ[start + j];
- *it++ = static_cast<char> (delta_val >> 8);
- *it++ = static_cast<char> (delta_val & 0xFF);
-
- encoded_len += 2;
- }
-
- start += 64;
- run_length -= 64;
- }
-
- if (run_length)
- {
- *it++ = (DELTAS_ARE_WORDS | (run_length - 1));
- encoded_len++;
- while (start < i)
- {
- int16_t delta_val = deltas.arrayZ[start++];
- *it++ = static_cast<char> (delta_val >> 8);
- *it++ = static_cast<char> (delta_val & 0xFF);
-
- encoded_len += 2;
- }
- }
- return encoded_len;
+ return TupleValues::compile (deltas, encoded_bytes);
}
bool calc_inferred_deltas (const contour_point_vector_t& orig_points)
@@ -1079,20 +705,20 @@ struct tuple_delta_t
opt_indices.arrayZ[i] = false;
}
- hb_vector_t<char> opt_point_data;
+ hb_vector_t<unsigned char> opt_point_data;
if (!compile_point_set (opt_indices, opt_point_data))
return false;
- hb_vector_t<char> opt_deltas_data;
+ hb_vector_t<unsigned char> opt_deltas_data;
if (!compile_deltas (opt_indices,
is_comp_glyph_wo_deltas ? opt_deltas_x : deltas_x,
is_comp_glyph_wo_deltas ? opt_deltas_y : deltas_y,
opt_deltas_data))
return false;
- hb_vector_t<char> point_data;
+ hb_vector_t<unsigned char> point_data;
if (!compile_point_set (indices, point_data))
return false;
- hb_vector_t<char> deltas_data;
+ hb_vector_t<unsigned char> deltas_data;
if (!compile_deltas (indices, deltas_x, deltas_y, deltas_data))
return false;
@@ -1114,7 +740,7 @@ struct tuple_delta_t
}
static bool compile_point_set (const hb_vector_t<bool> &point_indices,
- hb_vector_t<char>& compiled_points /* OUT */)
+ hb_vector_t<unsigned char>& compiled_points /* OUT */)
{
unsigned num_points = 0;
for (bool i : point_indices)
@@ -1316,7 +942,7 @@ struct TupleVariationData
bool has_private_points = iterator.current_tuple->has_private_points ();
const HBUINT8 *end = p + length;
if (has_private_points &&
- !TupleVariationData::unpack_points (p, private_indices, end))
+ !TupleVariationData::decompile_points (p, private_indices, end))
return false;
const hb_vector_t<unsigned> &indices = has_private_points ? private_indices : shared_indices;
@@ -1326,14 +952,14 @@ struct TupleVariationData
hb_vector_t<int> deltas_x;
if (unlikely (!deltas_x.resize (num_deltas, false) ||
- !TupleVariationData::unpack_deltas (p, deltas_x, end)))
+ !TupleVariationData::decompile_deltas (p, deltas_x, end)))
return false;
hb_vector_t<int> deltas_y;
if (is_gvar)
{
if (unlikely (!deltas_y.resize (num_deltas, false) ||
- !TupleVariationData::unpack_deltas (p, deltas_y, end)))
+ !TupleVariationData::decompile_deltas (p, deltas_y, end)))
return false;
}
@@ -1508,7 +1134,7 @@ struct TupleVariationData
continue;
}
- hb_vector_t<char> compiled_point_data;
+ hb_vector_t<unsigned char> compiled_point_data;
if (!tuple_delta_t::compile_point_set (*points_set, compiled_point_data))
return false;
@@ -1700,7 +1326,7 @@ struct TupleVariationData
{
const HBUINT8 *base = &(table_base+var_data->data);
const HBUINT8 *p = base;
- if (!unpack_points (p, shared_indices, (const HBUINT8 *) (var_data_bytes.arrayZ + var_data_bytes.length))) return false;
+ if (!decompile_points (p, shared_indices, (const HBUINT8 *) (var_data_bytes.arrayZ + var_data_bytes.length))) return false;
data_offset = p - base;
}
return true;
@@ -1750,9 +1376,9 @@ struct TupleVariationData
bool has_shared_point_numbers () const { return tupleVarCount.has_shared_point_numbers (); }
- static bool unpack_points (const HBUINT8 *&p /* IN/OUT */,
- hb_vector_t<unsigned int> &points /* OUT */,
- const HBUINT8 *end)
+ static bool decompile_points (const HBUINT8 *&p /* IN/OUT */,
+ hb_vector_t<unsigned int> &points /* OUT */,
+ const HBUINT8 *end)
{
enum packed_point_flag_t
{
@@ -1802,43 +1428,13 @@ struct TupleVariationData
return true;
}
- static bool unpack_deltas (const HBUINT8 *&p /* IN/OUT */,
- hb_vector_t<int> &deltas /* IN/OUT */,
- const HBUINT8 *end)
+ template <typename T>
+ static bool decompile_deltas (const HBUINT8 *&p /* IN/OUT */,
+ hb_vector_t<T> &deltas /* IN/OUT */,
+ const HBUINT8 *end,
+ bool consume_all = false)
{
- unsigned i = 0;
- unsigned count = deltas.length;
- while (i < count)
- {
- if (unlikely (p + 1 > end)) return false;
- unsigned control = *p++;
- unsigned run_count = (control & DELTA_RUN_COUNT_MASK) + 1;
- unsigned stop = i + run_count;
- if (unlikely (stop > count)) return false;
- if (control & DELTAS_ARE_ZERO)
- {
- for (; i < stop; i++)
- deltas.arrayZ[i] = 0;
- }
- else if (control & DELTAS_ARE_WORDS)
- {
- if (unlikely (p + run_count * HBUINT16::static_size > end)) return false;
- for (; i < stop; i++)
- {
- deltas.arrayZ[i] = * (const HBINT16 *) p;
- p += HBUINT16::static_size;
- }
- }
- else
- {
- if (unlikely (p + run_count > end)) return false;
- for (; i < stop; i++)
- {
- deltas.arrayZ[i] = * (const HBINT8 *) p++;
- }
- }
- }
- return true;
+ return TupleValues::decompile (p, deltas, end, consume_all);
}
bool has_data () const { return tupleVarCount; }
@@ -2067,7 +1663,9 @@ struct item_variations_t
}
}
- if (!all_regions || !all_unique_regions) return false;
+ /* regions are empty means no variation data, return true */
+ if (!all_regions || !all_unique_regions) return true;
+
if (!region_list.alloc (all_regions.get_population ()))
return false;
@@ -2132,7 +1730,8 @@ struct item_variations_t
bool as_item_varstore (bool optimize=true, bool use_no_variation_idx=true)
{
- if (!region_list) return false;
+ /* return true if no variation data */
+ if (!region_list) return true;
unsigned num_cols = region_list.length;
/* pre-alloc a 2D vector for all sub_table's VarData rows */
unsigned total_rows = 0;
@@ -2382,6 +1981,7 @@ struct item_variations_t
}
};
+
} /* namespace OT */
diff --git a/thirdparty/harfbuzz/src/hb-ot-var-cvar-table.hh b/thirdparty/harfbuzz/src/hb-ot-var-cvar-table.hh
index 3798ad3e3e..3931382f13 100644
--- a/thirdparty/harfbuzz/src/hb-ot-var-cvar-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-var-cvar-table.hh
@@ -107,14 +107,14 @@ struct cvar
bool has_private_points = iterator.current_tuple->has_private_points ();
if (has_private_points &&
- !TupleVariationData::unpack_points (p, private_indices, end))
+ !TupleVariationData::decompile_points (p, private_indices, end))
return false;
const hb_vector_t<unsigned int> &indices = has_private_points ? private_indices : shared_indices;
bool apply_to_all = (indices.length == 0);
unsigned num_deltas = apply_to_all ? num_cvt_item : indices.length;
if (unlikely (!unpacked_deltas.resize (num_deltas, false))) return false;
- if (unlikely (!TupleVariationData::unpack_deltas (p, unpacked_deltas, end))) return false;
+ if (unlikely (!TupleVariationData::decompile_deltas (p, unpacked_deltas, end))) return false;
for (unsigned int i = 0; i < num_deltas; i++)
{
diff --git a/thirdparty/harfbuzz/src/hb-ot-var-gvar-table.hh b/thirdparty/harfbuzz/src/hb-ot-var-gvar-table.hh
index d713084faf..b021a00f66 100644
--- a/thirdparty/harfbuzz/src/hb-ot-var-gvar-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ot-var-gvar-table.hh
@@ -359,7 +359,10 @@ struct gvar
out->glyphCountX = hb_min (0xFFFFu, num_glyphs);
unsigned glyph_var_data_size = glyph_vars.compiled_byte_size ();
- bool long_offset = glyph_var_data_size & ~0xFFFFu || force_long_offsets;
+ /* According to the spec: If the short format (Offset16) is used for offsets,
+ * the value stored is the offset divided by 2, so the maximum data size should
+ * be 2 * 0xFFFFu, which is 0x1FFFEu */
+ bool long_offset = glyph_var_data_size > 0x1FFFEu || force_long_offsets;
out->flags = long_offset ? 1 : 0;
HBUINT8 *glyph_var_data_offsets = c->allocate_size<HBUINT8> ((long_offset ? 4 : 2) * (num_glyphs + 1), false);
@@ -440,7 +443,10 @@ struct gvar
subset_data_size += get_glyph_var_data_bytes (c->source_blob, glyph_count, old_gid).length;
}
- bool long_offset = (subset_data_size & ~0xFFFFu);
+ /* According to the spec: If the short format (Offset16) is used for offsets,
+ * the value stored is the offset divided by 2, so the maximum data size should
+ * be 2 * 0xFFFFu, which is 0x1FFFEu */
+ bool long_offset = subset_data_size > 0x1FFFEu;
#ifdef HB_EXPERIMENTAL_API
long_offset = long_offset || (c->plan->flags & HB_SUBSET_FLAGS_IFTB_REQUIREMENTS);
#endif
@@ -540,7 +546,7 @@ struct gvar
unsigned get_offset (unsigned glyph_count, unsigned i) const
{
if (unlikely (i > glyph_count)) return 0;
- _hb_compiler_memory_r_barrier ();
+ hb_barrier ();
return is_long_offset () ? get_long_offset_array ()[i] : get_short_offset_array ()[i] * 2;
}
@@ -618,7 +624,7 @@ struct gvar
public:
bool apply_deltas_to_points (hb_codepoint_t glyph,
- hb_array_t<int> coords,
+ hb_array_t<const int> coords,
const hb_array_t<contour_point_t> points,
bool phantom_only = false) const
{
@@ -673,16 +679,16 @@ struct gvar
bool has_private_points = iterator.current_tuple->has_private_points ();
if (has_private_points &&
- !GlyphVariationData::unpack_points (p, private_indices, end))
+ !GlyphVariationData::decompile_points (p, private_indices, end))
return false;
const hb_array_t<unsigned int> &indices = has_private_points ? private_indices : shared_indices;
bool apply_to_all = (indices.length == 0);
unsigned int num_deltas = apply_to_all ? points.length : indices.length;
if (unlikely (!x_deltas.resize (num_deltas, false))) return false;
- if (unlikely (!GlyphVariationData::unpack_deltas (p, x_deltas, end))) return false;
+ if (unlikely (!GlyphVariationData::decompile_deltas (p, x_deltas, end))) return false;
if (unlikely (!y_deltas.resize (num_deltas, false))) return false;
- if (unlikely (!GlyphVariationData::unpack_deltas (p, y_deltas, end))) return false;
+ if (unlikely (!GlyphVariationData::decompile_deltas (p, y_deltas, end))) return false;
if (!apply_to_all)
{
diff --git a/thirdparty/harfbuzz/src/hb-ot-var-varc-table.hh b/thirdparty/harfbuzz/src/hb-ot-var-varc-table.hh
new file mode 100644
index 0000000000..603aeb258c
--- /dev/null
+++ b/thirdparty/harfbuzz/src/hb-ot-var-varc-table.hh
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2024 Google, Inc.
+ *
+ * This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#ifndef HB_OT_VAR_VARC_TABLE_HH
+#define HB_OT_VAR_VARC_TABLE_HH
+
+#include "OT/Var/VARC/VARC.hh"
+
+#endif /* HB_OT_VAR_VARC_TABLE_HH */
diff --git a/thirdparty/harfbuzz/src/hb-paint-extents.hh b/thirdparty/harfbuzz/src/hb-paint-extents.hh
index f172bd42f9..2d4491e071 100644
--- a/thirdparty/harfbuzz/src/hb-paint-extents.hh
+++ b/thirdparty/harfbuzz/src/hb-paint-extents.hh
@@ -28,169 +28,8 @@
#include "hb.hh"
#include "hb-paint.h"
+#include "hb-geometry.hh"
-typedef struct hb_extents_t
-{
- hb_extents_t () {}
- hb_extents_t (float xmin, float ymin, float xmax, float ymax) :
- xmin (xmin), ymin (ymin), xmax (xmax), ymax (ymax) {}
-
- bool is_empty () const { return xmin >= xmax || ymin >= ymax; }
- bool is_void () const { return xmin > xmax; }
-
- void union_ (const hb_extents_t &o)
- {
- xmin = hb_min (xmin, o.xmin);
- ymin = hb_min (ymin, o.ymin);
- xmax = hb_max (xmax, o.xmax);
- ymax = hb_max (ymax, o.ymax);
- }
-
- void intersect (const hb_extents_t &o)
- {
- xmin = hb_max (xmin, o.xmin);
- ymin = hb_max (ymin, o.ymin);
- xmax = hb_min (xmax, o.xmax);
- ymax = hb_min (ymax, o.ymax);
- }
-
- void
- add_point (float x, float y)
- {
- if (unlikely (is_void ()))
- {
- xmin = xmax = x;
- ymin = ymax = y;
- }
- else
- {
- xmin = hb_min (xmin, x);
- ymin = hb_min (ymin, y);
- xmax = hb_max (xmax, x);
- ymax = hb_max (ymax, y);
- }
- }
-
- float xmin = 0.f;
- float ymin = 0.f;
- float xmax = -1.f;
- float ymax = -1.f;
-} hb_extents_t;
-
-typedef struct hb_transform_t
-{
- hb_transform_t () {}
- hb_transform_t (float xx, float yx,
- float xy, float yy,
- float x0, float y0) :
- xx (xx), yx (yx), xy (xy), yy (yy), x0 (x0), y0 (y0) {}
-
- void multiply (const hb_transform_t &o)
- {
- /* Copied from cairo, with "o" being "a" there and "this" being "b" there. */
- hb_transform_t r;
-
- r.xx = o.xx * xx + o.yx * xy;
- r.yx = o.xx * yx + o.yx * yy;
-
- r.xy = o.xy * xx + o.yy * xy;
- r.yy = o.xy * yx + o.yy * yy;
-
- r.x0 = o.x0 * xx + o.y0 * xy + x0;
- r.y0 = o.x0 * yx + o.y0 * yy + y0;
-
- *this = r;
- }
-
- void transform_distance (float &dx, float &dy) const
- {
- float new_x = xx * dx + xy * dy;
- float new_y = yx * dx + yy * dy;
- dx = new_x;
- dy = new_y;
- }
-
- void transform_point (float &x, float &y) const
- {
- transform_distance (x, y);
- x += x0;
- y += y0;
- }
-
- void transform_extents (hb_extents_t &extents) const
- {
- float quad_x[4], quad_y[4];
-
- quad_x[0] = extents.xmin;
- quad_y[0] = extents.ymin;
- quad_x[1] = extents.xmin;
- quad_y[1] = extents.ymax;
- quad_x[2] = extents.xmax;
- quad_y[2] = extents.ymin;
- quad_x[3] = extents.xmax;
- quad_y[3] = extents.ymax;
-
- extents = hb_extents_t {};
- for (unsigned i = 0; i < 4; i++)
- {
- transform_point (quad_x[i], quad_y[i]);
- extents.add_point (quad_x[i], quad_y[i]);
- }
- }
-
- float xx = 1.f;
- float yx = 0.f;
- float xy = 0.f;
- float yy = 1.f;
- float x0 = 0.f;
- float y0 = 0.f;
-} hb_transform_t;
-
-typedef struct hb_bounds_t
-{
- enum status_t {
- UNBOUNDED,
- BOUNDED,
- EMPTY,
- };
-
- hb_bounds_t (status_t status) : status (status) {}
- hb_bounds_t (const hb_extents_t &extents) :
- status (extents.is_empty () ? EMPTY : BOUNDED), extents (extents) {}
-
- void union_ (const hb_bounds_t &o)
- {
- if (o.status == UNBOUNDED)
- status = UNBOUNDED;
- else if (o.status == BOUNDED)
- {
- if (status == EMPTY)
- *this = o;
- else if (status == BOUNDED)
- extents.union_ (o.extents);
- }
- }
-
- void intersect (const hb_bounds_t &o)
- {
- if (o.status == EMPTY)
- status = EMPTY;
- else if (o.status == BOUNDED)
- {
- if (status == UNBOUNDED)
- *this = o;
- else if (status == BOUNDED)
- {
- extents.intersect (o.extents);
- if (extents.is_empty ())
- status = EMPTY;
- }
- }
- }
-
- status_t status;
- hb_extents_t extents;
-} hb_bounds_t;
typedef struct hb_paint_extents_context_t hb_paint_extents_context_t;
@@ -231,7 +70,10 @@ struct hb_paint_extents_context_t
const hb_transform_t &t = transforms.tail ();
t.transform_extents (extents);
- clips.push (hb_bounds_t {extents});
+ auto bounds = hb_bounds_t {extents};
+ bounds.intersect (clips.tail ());
+
+ clips.push (bounds);
}
void pop_clip ()
diff --git a/thirdparty/harfbuzz/src/hb-style.cc b/thirdparty/harfbuzz/src/hb-style.cc
index bd5cb5c6be..fbab091e8c 100644
--- a/thirdparty/harfbuzz/src/hb-style.cc
+++ b/thirdparty/harfbuzz/src/hb-style.cc
@@ -61,8 +61,8 @@ _hb_ratio_to_angle (float r)
* @style_tag: a style tag.
*
* Searches variation axes of a #hb_font_t object for a specific axis first,
- * if not set, then tries to get default style values from different
- * tables of the font.
+ * if not set, first tries to get default style values in `STAT` table
+ * then tries to polyfill from different tables of the font.
*
* Returns: Corresponding axis or default value to a style tag.
*
diff --git a/thirdparty/harfbuzz/src/hb-subset-cff2.cc b/thirdparty/harfbuzz/src/hb-subset-cff2.cc
index 9c9117d52f..eb5cb0c625 100644
--- a/thirdparty/harfbuzz/src/hb-subset-cff2.cc
+++ b/thirdparty/harfbuzz/src/hb-subset-cff2.cc
@@ -666,9 +666,6 @@ OT::cff2::accelerator_subset_t::serialize (hb_serialize_context_t *c,
bool
OT::cff2::accelerator_subset_t::subset (hb_subset_context_t *c) const
{
- if (c->plan->normalized_coords && !c->plan->all_axes_pinned)
- fprintf (stdout, "warning: CFF partial instancing is not supported.\n");
-
cff2_subset_plan cff2_plan;
if (unlikely (!cff2_plan.create (*this, c->plan))) return false;
diff --git a/thirdparty/harfbuzz/src/hb-subset-input.cc b/thirdparty/harfbuzz/src/hb-subset-input.cc
index 8974755a75..b874949df0 100644
--- a/thirdparty/harfbuzz/src/hb-subset-input.cc
+++ b/thirdparty/harfbuzz/src/hb-subset-input.cc
@@ -412,6 +412,7 @@ hb_subset_input_keep_everything (hb_subset_input_t *input)
hb_subset_input_set_flags (input,
HB_SUBSET_FLAGS_NOTDEF_OUTLINE |
HB_SUBSET_FLAGS_GLYPH_NAMES |
+ HB_SUBSET_FLAGS_NAME_LEGACY |
HB_SUBSET_FLAGS_NO_PRUNE_UNICODE_RANGES |
HB_SUBSET_FLAGS_PASSTHROUGH_UNRECOGNIZED);
}
@@ -730,7 +731,7 @@ hb_subset_input_override_name_table (hb_subset_input_t *input,
src = hb_utf8_t::next (src, src_end, &unicode, replacement);
if (unicode >= 0x0080u)
{
- printf ("Non-ascii character detected, ignored...This API supports acsii characters only for mac platform\n");
+ printf ("Non-ascii character detected, ignored...This API supports ascii characters only for mac platform\n");
return false;
}
}
diff --git a/thirdparty/harfbuzz/src/hb-subset-instancer-solver.cc b/thirdparty/harfbuzz/src/hb-subset-instancer-solver.cc
index ca903e2707..ec01575990 100644
--- a/thirdparty/harfbuzz/src/hb-subset-instancer-solver.cc
+++ b/thirdparty/harfbuzz/src/hb-subset-instancer-solver.cc
@@ -376,7 +376,7 @@ double renormalizeValue (double v, const Triple &triple,
assert (lower <= def && def <= upper);
if (!extrapolate)
- v = hb_max (hb_min (v, upper), lower);
+ v = hb_clamp (v, lower, upper);
if (v == def)
return 0.0;
diff --git a/thirdparty/harfbuzz/src/hb-subset-plan.cc b/thirdparty/harfbuzz/src/hb-subset-plan.cc
index d657790d54..59020dbe87 100644
--- a/thirdparty/harfbuzz/src/hb-subset-plan.cc
+++ b/thirdparty/harfbuzz/src/hb-subset-plan.cc
@@ -491,7 +491,7 @@ _collect_base_variation_indices (hb_subset_plan_t* plan)
base->collect_variation_indices (plan, varidx_set);
const OT::ItemVariationStore &var_store = base->get_var_store ();
unsigned subtable_count = var_store.get_sub_table_count ();
-
+
_remap_variation_indices (var_store, varidx_set,
plan->normalized_coords,
@@ -515,6 +515,7 @@ _cmap_closure (hb_face_t *face,
cmap.table->closure_glyphs (unicodes, glyphset);
}
+#ifndef HB_NO_VAR
static void
_remap_colrv1_delta_set_index_indices (const OT::DeltaSetIndexMap &index_map,
const hb_set_t &delta_set_idxes,
@@ -531,7 +532,7 @@ _remap_colrv1_delta_set_index_indices (const OT::DeltaSetIndexMap &index_map,
unsigned var_idx = index_map.map (delta_set_idx);
unsigned new_varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
int delta = 0;
-
+
if (var_idx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
{
hb_pair_t<unsigned, int> *new_varidx_delta;
@@ -547,6 +548,7 @@ _remap_colrv1_delta_set_index_indices (const OT::DeltaSetIndexMap &index_map,
}
variation_idx_delta_map = std::move (delta_set_idx_delta_map);
}
+#endif
static void _colr_closure (hb_subset_plan_t* plan,
hb_set_t *glyphs_colred)
@@ -570,6 +572,7 @@ static void _colr_closure (hb_subset_plan_t* plan,
_remap_indexes (&layer_indices, &plan->colrv1_layers);
_remap_palette_indexes (&palette_indices, &plan->colr_palettes);
+#ifndef HB_NO_VAR
if (!colr.has_var_store () || !variation_indices) return;
const OT::ItemVariationStore &var_store = colr.get_var_store ();
@@ -600,6 +603,7 @@ static void _colr_closure (hb_subset_plan_t* plan,
plan->colrv1_variation_idx_delta_map,
plan->colrv1_new_deltaset_idx_varidx_map);
}
+#endif
}
static inline void
@@ -638,6 +642,36 @@ _remove_invalid_gids (hb_set_t *glyphs,
glyphs->del_range (num_glyphs, HB_SET_VALUE_INVALID);
}
+template<bool GID_ALWAYS_EXISTS = false, typename I, typename F, typename G, hb_requires (hb_is_iterator (I))>
+static void
+_fill_unicode_and_glyph_map(hb_subset_plan_t *plan,
+ I unicode_iterator,
+ F unicode_to_gid_for_iterator,
+ G unicode_to_gid_general)
+{
+ for (hb_codepoint_t cp : unicode_iterator)
+ {
+ hb_codepoint_t gid = unicode_to_gid_for_iterator(cp);
+ if (!GID_ALWAYS_EXISTS && gid == HB_MAP_VALUE_INVALID)
+ {
+ DEBUG_MSG(SUBSET, nullptr, "Drop U+%04X; no gid", cp);
+ continue;
+ }
+
+ plan->codepoint_to_glyph->set (cp, gid);
+ plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
+ }
+}
+
+template<bool GID_ALWAYS_EXISTS = false, typename I, typename F, hb_requires (hb_is_iterator (I))>
+static void
+_fill_unicode_and_glyph_map(hb_subset_plan_t *plan,
+ I unicode_iterator,
+ F unicode_to_gid_for_iterator)
+{
+ _fill_unicode_and_glyph_map(plan, unicode_iterator, unicode_to_gid_for_iterator, unicode_to_gid_for_iterator);
+}
+
static void
_populate_unicodes_to_retain (const hb_set_t *unicodes,
const hb_set_t *glyphs,
@@ -657,35 +691,21 @@ _populate_unicodes_to_retain (const hb_set_t *unicodes,
// not excessively large (eg. an inverted set).
plan->unicode_to_new_gid_list.alloc (unicodes->get_population ());
if (!unicode_to_gid) {
- for (hb_codepoint_t cp : *unicodes)
- {
+ _fill_unicode_and_glyph_map(plan, unicodes->iter(), [&] (hb_codepoint_t cp) {
hb_codepoint_t gid;
- if (!cmap.get_nominal_glyph (cp, &gid))
- {
- DEBUG_MSG(SUBSET, nullptr, "Drop U+%04X; no gid", cp);
- continue;
+ if (!cmap.get_nominal_glyph (cp, &gid)) {
+ return HB_MAP_VALUE_INVALID;
}
-
- plan->codepoint_to_glyph->set (cp, gid);
- plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
- }
+ return gid;
+ });
} else {
// Use in memory unicode to gid map it's faster then looking up from
// the map. This code is mostly duplicated from above to avoid doing
// conditionals on the presence of the unicode_to_gid map each
// iteration.
- for (hb_codepoint_t cp : *unicodes)
- {
- hb_codepoint_t gid = unicode_to_gid->get (cp);
- if (gid == HB_MAP_VALUE_INVALID)
- {
- DEBUG_MSG(SUBSET, nullptr, "Drop U+%04X; no gid", cp);
- continue;
- }
-
- plan->codepoint_to_glyph->set (cp, gid);
- plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
- }
+ _fill_unicode_and_glyph_map(plan, unicodes->iter(), [&] (hb_codepoint_t cp) {
+ return unicode_to_gid->get (cp);
+ });
}
}
else
@@ -715,29 +735,29 @@ _populate_unicodes_to_retain (const hb_set_t *unicodes,
plan->codepoint_to_glyph->alloc (unicodes->get_population () + glyphs->get_population ());
auto &gid_to_unicodes = plan->accelerator->gid_to_unicodes;
+
for (hb_codepoint_t gid : *glyphs)
{
auto unicodes = gid_to_unicodes.get (gid);
-
- for (hb_codepoint_t cp : unicodes)
- {
- plan->codepoint_to_glyph->set (cp, gid);
- plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
- }
+ _fill_unicode_and_glyph_map<true>(plan, unicodes, [&] (hb_codepoint_t cp) {
+ return gid;
+ },
+ [&] (hb_codepoint_t cp) {
+ return unicode_glyphid_map->get(cp);
+ });
}
- for (hb_codepoint_t cp : *unicodes)
- {
- /* Don't double-add entry. */
+
+ _fill_unicode_and_glyph_map(plan, unicodes->iter(), [&] (hb_codepoint_t cp) {
+ /* Don't double-add entry. */
if (plan->codepoint_to_glyph->has (cp))
- continue;
+ return HB_MAP_VALUE_INVALID;
- hb_codepoint_t *gid;
- if (!unicode_glyphid_map->has(cp, &gid))
- continue;
+ return unicode_glyphid_map->get(cp);
+ },
+ [&] (hb_codepoint_t cp) {
+ return unicode_glyphid_map->get(cp);
+ });
- plan->codepoint_to_glyph->set (cp, *gid);
- plan->unicode_to_new_gid_list.push (hb_pair (cp, *gid));
- }
plan->unicode_to_new_gid_list.qsort ();
}
else
@@ -746,15 +766,15 @@ _populate_unicodes_to_retain (const hb_set_t *unicodes,
hb_codepoint_t first = HB_SET_VALUE_INVALID, last = HB_SET_VALUE_INVALID;
for (; cmap_unicodes->next_range (&first, &last); )
{
- for (unsigned cp = first; cp <= last; cp++)
- {
- hb_codepoint_t gid = (*unicode_glyphid_map)[cp];
+ _fill_unicode_and_glyph_map(plan, hb_range(first, last + 1), [&] (hb_codepoint_t cp) {
+ hb_codepoint_t gid = (*unicode_glyphid_map)[cp];
if (!unicodes->has (cp) && !glyphs->has (gid))
- continue;
-
- plan->codepoint_to_glyph->set (cp, gid);
- plan->unicode_to_new_gid_list.push (hb_pair (cp, gid));
- }
+ return HB_MAP_VALUE_INVALID;
+ return gid;
+ },
+ [&] (hb_codepoint_t cp) {
+ return unicode_glyphid_map->get(cp);
+ });
}
}
@@ -779,10 +799,6 @@ _populate_unicodes_to_retain (const hb_set_t *unicodes,
}
}
-#ifndef HB_COMPOSITE_OPERATIONS_PER_GLYPH
-#define HB_COMPOSITE_OPERATIONS_PER_GLYPH 64
-#endif
-
static unsigned
_glyf_add_gid_and_children (const OT::glyf_accelerator_t &glyf,
hb_codepoint_t gid,
@@ -808,18 +824,6 @@ _glyf_add_gid_and_children (const OT::glyf_accelerator_t &glyf,
operation_count,
depth);
-#ifndef HB_NO_VAR_COMPOSITES
- for (auto &item : glyph.get_var_composite_iterator ())
- {
- operation_count =
- _glyf_add_gid_and_children (glyf,
- item.get_gid (),
- gids_to_retain,
- operation_count,
- depth);
- }
-#endif
-
return operation_count;
}
@@ -916,13 +920,15 @@ _populate_gids_to_retain (hb_subset_plan_t* plan,
plan->_glyphset_colred = cur_glyphset;
+ // XXX TODO VARC closure / subset
+
_nameid_closure (plan, drop_tables);
/* Populate a full set of glyphs to retain by adding all referenced
* composite glyphs. */
if (glyf.has_data ())
for (hb_codepoint_t gid : cur_glyphset)
_glyf_add_gid_and_children (glyf, gid, &plan->_glyphset,
- cur_glyphset.get_population () * HB_COMPOSITE_OPERATIONS_PER_GLYPH);
+ cur_glyphset.get_population () * HB_MAX_COMPOSITE_OPERATIONS_PER_GLYPH);
else
plan->_glyphset.union_ (cur_glyphset);
#ifndef HB_NO_SUBSET_CFF
diff --git a/thirdparty/harfbuzz/src/hb-subset.cc b/thirdparty/harfbuzz/src/hb-subset.cc
index f10ef54dbd..7cea9f1837 100644
--- a/thirdparty/harfbuzz/src/hb-subset.cc
+++ b/thirdparty/harfbuzz/src/hb-subset.cc
@@ -594,14 +594,20 @@ static void _attach_accelerator_data (hb_subset_plan_t* plan,
* @input: input to use for the subsetting.
*
* Subsets a font according to provided input. Returns nullptr
- * if the subset operation fails.
+ * if the subset operation fails or the face has no glyphs.
*
* Since: 2.9.0
**/
hb_face_t *
hb_subset_or_fail (hb_face_t *source, const hb_subset_input_t *input)
{
- if (unlikely (!input || !source)) return hb_face_get_empty ();
+ if (unlikely (!input || !source)) return nullptr;
+
+ if (unlikely (!source->get_num_glyphs ()))
+ {
+ DEBUG_MSG (SUBSET, nullptr, "No glyphs in source font.");
+ return nullptr;
+ }
hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (source, input);
if (unlikely (!plan)) {
diff --git a/thirdparty/harfbuzz/src/hb-ucd-table.hh b/thirdparty/harfbuzz/src/hb-ucd-table.hh
index 8d3807a80f..8731a0bcf8 100644
--- a/thirdparty/harfbuzz/src/hb-ucd-table.hh
+++ b/thirdparty/harfbuzz/src/hb-ucd-table.hh
@@ -4,7 +4,7 @@
*
* ./gen-ucd-table.py ucd.nounihan.grouped.xml
*
- * on file with this description: Unicode 15.1.0
+ * on file with this description: Unicode 16.0.0
*/
#ifndef HB_UCD_TABLE_HH
@@ -13,7 +13,7 @@
#include "hb.hh"
static const hb_script_t
-_hb_ucd_sc_map[165] =
+_hb_ucd_sc_map[172] =
{
HB_SCRIPT_COMMON, HB_SCRIPT_INHERITED,
HB_SCRIPT_UNKNOWN, HB_SCRIPT_ARABIC,
@@ -97,7 +97,10 @@ _hb_ucd_sc_map[165] =
HB_SCRIPT_OLD_UYGHUR, HB_SCRIPT_TANGSA,
HB_SCRIPT_TOTO, HB_SCRIPT_VITHKUQI,
HB_SCRIPT_MATH, HB_SCRIPT_KAWI,
- HB_SCRIPT_NAG_MUNDARI,
+ HB_SCRIPT_NAG_MUNDARI, HB_SCRIPT_GARAY,
+ HB_SCRIPT_GURUNG_KHEMA, HB_SCRIPT_KIRAT_RAI,
+ HB_SCRIPT_OL_ONAL, HB_SCRIPT_SUNUWAR,
+ HB_SCRIPT_TODHRI, HB_SCRIPT_TULU_TIGALARI,
};
static const uint16_t
_hb_ucd_dm1_p0_map[825] =
@@ -868,7 +871,7 @@ _hb_ucd_dm2_u32_map[638] =
HB_CODEPOINT_ENCODE3_11_7_14 (0x04E9u, 0x0308u, 0x04EBu),
};
static const uint64_t
-_hb_ucd_dm2_u64_map[388] =
+_hb_ucd_dm2_u64_map[408] =
{
HB_CODEPOINT_ENCODE3 (0x05D0u, 0x05B7u, 0x0000u), HB_CODEPOINT_ENCODE3 (0x05D0u, 0x05B8u, 0x0000u),
HB_CODEPOINT_ENCODE3 (0x05D0u, 0x05BCu, 0x0000u), HB_CODEPOINT_ENCODE3 (0x05D1u, 0x05BCu, 0x0000u),
@@ -1051,13 +1054,23 @@ _hb_ucd_dm2_u64_map[388] =
HB_CODEPOINT_ENCODE3 (0x30F0u, 0x3099u, 0x30F8u), HB_CODEPOINT_ENCODE3 (0x30F1u, 0x3099u, 0x30F9u),
HB_CODEPOINT_ENCODE3 (0x30F2u, 0x3099u, 0x30FAu), HB_CODEPOINT_ENCODE3 (0x30FDu, 0x3099u, 0x30FEu),
HB_CODEPOINT_ENCODE3 (0xFB49u, 0x05C1u, 0x0000u), HB_CODEPOINT_ENCODE3 (0xFB49u, 0x05C2u, 0x0000u),
+ HB_CODEPOINT_ENCODE3 (0x105D2u, 0x0307u, 0x105C9u), HB_CODEPOINT_ENCODE3 (0x105DAu, 0x0307u, 0x105E4u),
HB_CODEPOINT_ENCODE3 (0x11099u, 0x110BAu, 0x1109Au),HB_CODEPOINT_ENCODE3 (0x1109Bu, 0x110BAu, 0x1109Cu),
HB_CODEPOINT_ENCODE3 (0x110A5u, 0x110BAu, 0x110ABu),HB_CODEPOINT_ENCODE3 (0x11131u, 0x11127u, 0x1112Eu),
HB_CODEPOINT_ENCODE3 (0x11132u, 0x11127u, 0x1112Fu),HB_CODEPOINT_ENCODE3 (0x11347u, 0x1133Eu, 0x1134Bu),
- HB_CODEPOINT_ENCODE3 (0x11347u, 0x11357u, 0x1134Cu),HB_CODEPOINT_ENCODE3 (0x114B9u, 0x114B0u, 0x114BCu),
- HB_CODEPOINT_ENCODE3 (0x114B9u, 0x114BAu, 0x114BBu),HB_CODEPOINT_ENCODE3 (0x114B9u, 0x114BDu, 0x114BEu),
- HB_CODEPOINT_ENCODE3 (0x115B8u, 0x115AFu, 0x115BAu),HB_CODEPOINT_ENCODE3 (0x115B9u, 0x115AFu, 0x115BBu),
- HB_CODEPOINT_ENCODE3 (0x11935u, 0x11930u, 0x11938u), HB_CODEPOINT_ENCODE3 (0x1D157u, 0x1D165u, 0x0000u),
+ HB_CODEPOINT_ENCODE3 (0x11347u, 0x11357u, 0x1134Cu),HB_CODEPOINT_ENCODE3 (0x11382u, 0x113C9u, 0x11383u),
+ HB_CODEPOINT_ENCODE3 (0x11384u, 0x113BBu, 0x11385u),HB_CODEPOINT_ENCODE3 (0x1138Bu, 0x113C2u, 0x1138Eu),
+ HB_CODEPOINT_ENCODE3 (0x11390u, 0x113C9u, 0x11391u),HB_CODEPOINT_ENCODE3 (0x113C2u, 0x113B8u, 0x113C7u),
+ HB_CODEPOINT_ENCODE3 (0x113C2u, 0x113C2u, 0x113C5u),HB_CODEPOINT_ENCODE3 (0x113C2u, 0x113C9u, 0x113C8u),
+ HB_CODEPOINT_ENCODE3 (0x114B9u, 0x114B0u, 0x114BCu),HB_CODEPOINT_ENCODE3 (0x114B9u, 0x114BAu, 0x114BBu),
+ HB_CODEPOINT_ENCODE3 (0x114B9u, 0x114BDu, 0x114BEu),HB_CODEPOINT_ENCODE3 (0x115B8u, 0x115AFu, 0x115BAu),
+ HB_CODEPOINT_ENCODE3 (0x115B9u, 0x115AFu, 0x115BBu),HB_CODEPOINT_ENCODE3 (0x11935u, 0x11930u, 0x11938u),
+ HB_CODEPOINT_ENCODE3 (0x1611Eu, 0x1611Eu, 0x16121u),HB_CODEPOINT_ENCODE3 (0x1611Eu, 0x1611Fu, 0x16123u),
+ HB_CODEPOINT_ENCODE3 (0x1611Eu, 0x16120u, 0x16125u),HB_CODEPOINT_ENCODE3 (0x1611Eu, 0x16129u, 0x16122u),
+ HB_CODEPOINT_ENCODE3 (0x16121u, 0x1611Fu, 0x16126u),HB_CODEPOINT_ENCODE3 (0x16121u, 0x16120u, 0x16128u),
+ HB_CODEPOINT_ENCODE3 (0x16122u, 0x1611Fu, 0x16127u),HB_CODEPOINT_ENCODE3 (0x16129u, 0x1611Fu, 0x16124u),
+ HB_CODEPOINT_ENCODE3 (0x16D63u, 0x16D67u, 0x16D69u),HB_CODEPOINT_ENCODE3 (0x16D67u, 0x16D67u, 0x16D68u),
+ HB_CODEPOINT_ENCODE3 (0x16D69u, 0x16D67u, 0x16D6Au), HB_CODEPOINT_ENCODE3 (0x1D157u, 0x1D165u, 0x0000u),
HB_CODEPOINT_ENCODE3 (0x1D158u, 0x1D165u, 0x0000u), HB_CODEPOINT_ENCODE3 (0x1D15Fu, 0x1D16Eu, 0x0000u),
HB_CODEPOINT_ENCODE3 (0x1D15Fu, 0x1D16Fu, 0x0000u), HB_CODEPOINT_ENCODE3 (0x1D15Fu, 0x1D170u, 0x0000u),
HB_CODEPOINT_ENCODE3 (0x1D15Fu, 0x1D171u, 0x0000u), HB_CODEPOINT_ENCODE3 (0x1D15Fu, 0x1D172u, 0x0000u),
@@ -1069,90 +1082,59 @@ _hb_ucd_dm2_u64_map[388] =
#ifndef HB_OPTIMIZE_SIZE
static const uint8_t
-_hb_ucd_u8[17884] =
+_hb_ucd_u8[17612] =
{
- 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 9, 10, 7, 7, 7, 7, 11, 12, 13, 13, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 22, 22, 22, 22, 24, 7, 7,
- 25, 26, 22, 22, 22, 27, 28, 29, 22, 30, 31, 32, 33, 34, 35, 36,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 37, 7, 38, 39, 7, 40, 7, 7, 7, 41, 22, 42,
- 7, 7, 43, 7, 44, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 45, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 46,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 47,
+ 0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 6, 5, 5, 7, 8, 9,
+ 10, 11, 12, 13, 14, 15, 16, 5, 17, 15, 18, 19, 20, 21, 22, 23,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 24, 25, 26, 5, 27, 28,
+ 5, 29, 30, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 31, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 33,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
- 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
- 32, 33, 34, 34, 35, 36, 37, 38, 39, 34, 34, 34, 40, 41, 42, 43,
- 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
- 60, 61, 62, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 69, 72, 73,
- 69, 69, 64, 74, 64, 64, 75, 76, 77, 78, 79, 80, 81, 82, 69, 83,
- 84, 85, 86, 87, 88, 89, 69, 69, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 90, 34, 34, 34, 34,
- 91, 34, 34, 34, 34, 34, 34, 34, 34, 92, 34, 34, 93, 94, 95, 96,
- 97, 98, 99,100,101,102,103,104, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,105,
- 106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,
- 107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,
- 107,107, 34, 34,108,109,110,111, 34, 34,112,113,114,115,116,117,
- 118,119,120,121,122,123,124,125,126,127,128,129, 34, 34,130,131,
- 132,133,134,135,136,137,138,139,140,141,142,122,143,144,145,146,
- 147,148,149,150,151,152,153,122,154,155,122,156,157,158,159,122,
- 160,161,162,163,164,165,166,122,167,168,169,170,122,171,172,173,
- 34, 34, 34, 34, 34, 34, 34,174,175, 34,176,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,177,
- 34, 34, 34, 34, 34, 34, 34, 34,178,122,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122, 34, 34, 34, 34,179,122,122,122,
- 34, 34, 34, 34,180,181,182,183,122,122,122,122,184,185,186,187,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,188,
- 34, 34, 34, 34, 34, 34, 34, 34, 34,189,190,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,191,
- 34, 34,192, 34, 34,193,122,122,122,122,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,194,195,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,196,197,
- 69,198,199,200,201,202,203,122,204,205,206,207,208,209,210,211,
- 69, 69, 69, 69,212,213,122,122,122,122,122,122,122,122,214,122,
- 215,216,217,122,122,218,122,122,122,219,122,122,122,122,122,220,
- 34,221,222,122,122,122,122,122,223,224,225,122,226,227,122,122,
- 228,229,230,231,232,122, 69,233, 69, 69, 69, 69, 69,234,235,236,
- 237,238, 69, 69,239,240, 69,241,122,122,122,122,122,122,122,122,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,242, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,243, 34,
- 244, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,245, 34, 34,
- 34, 34, 34, 34, 34, 34, 34,246, 34, 34, 34, 34,247,122,122,122,
- 34, 34, 34, 34,248,122,122,122,122,122,122,122,122,122,122,122,
- 34, 34, 34, 34, 34, 34,249, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34,250,122,122,122,122,122,122,122,122,
- 251,122,252,253,122,122,122,122,122,122,122,122,122,122,122,122,
- 107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,254,
- 107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,255,
+ 16, 17, 18, 19, 20, 17, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 33, 41, 42, 43, 44, 45,
+ 46, 47, 48, 39, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 49, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 50, 17, 17, 17, 51, 17, 52, 53, 54, 55, 56, 57, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 58, 59, 59, 59, 59, 59, 59, 59, 59,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 17, 61, 62, 17, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 17, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
+ 17, 17, 17, 97, 98, 99,100,100,100,100,100,100,100,100,100,101,
+ 17, 17, 17, 17,102, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17,103, 17, 17,104,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,105,100,100,100,100,100,100, 17, 17,106,107,100,108,109,110,
+ 17, 17, 17, 17, 17, 17, 17,111, 17, 17, 17, 17,112,113,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,114,
+ 17,115,116,100,100,100,100,100,100,100,100,100,117,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,118, 39,119,120,
+ 121,122,123,124,125,126,127,128, 39, 39,129,100,100,100,100,130,
+ 131,132,133,100,134,135,100,136,137,138,100,100,139,140,141,100,
+ 142,143,144,145, 39, 39,146,147,148, 39,149,150,100,100,100,100,
+ 17, 17, 17, 17, 17, 17,151, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17,152,153, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,154, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,155, 17, 17,156,100,
+ 100,100,100,100,100,100,100,100, 17, 17,157,100,100,100,100,100,
+ 17, 17, 17,158, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17,159,100,100,100,100,100,100,100,100,100,100,100,100,
+ 160,161,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,162,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,163,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 2, 4, 5, 6, 2,
7, 7, 7, 7, 7, 2, 8, 9, 10, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16,
@@ -1189,7 +1171,7 @@ _hb_ucd_u8[17884] =
43, 43, 40, 21, 2, 81, 57, 20, 36, 36, 36, 43, 43, 75, 43, 43,
43, 43, 75, 43, 75, 43, 43, 44, 2, 2, 2, 2, 2, 2, 2, 64,
36, 36, 36, 36, 70, 43, 44, 64, 36, 36, 36, 36, 36, 61, 44, 44,
- 36, 36, 36, 36, 82, 36, 36, 61, 65, 44, 44, 44, 43, 43, 43, 43,
+ 36, 36, 36, 36, 82, 36, 36, 61, 65, 44, 44, 57, 43, 43, 43, 43,
36, 36, 36, 36, 83, 43, 43, 43, 43, 84, 43, 43, 43, 43, 43, 43,
43, 85, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 85, 71, 86,
87, 43, 43, 43, 85, 86, 87, 86, 70, 43, 43, 43, 36, 36, 36, 36,
@@ -1262,13 +1244,13 @@ _hb_ucd_u8[17884] =
85, 85, 87, 43, 43, 43, 85, 86, 86, 87, 43, 43, 43, 43, 80, 57,
2, 2, 2, 88, 2, 2, 2, 44, 43, 43, 43, 43, 43, 43, 43,109,
43, 43, 43, 43, 43, 43, 43, 80, 43, 43, 98, 36, 36, 36, 36, 36,
- 36, 36, 85, 43, 43, 85, 85, 86, 86, 85, 98, 36, 36, 36, 61, 44,
- 97, 67, 67, 67, 67, 50, 43, 43, 43, 43, 67, 67, 67, 67, 21, 64,
+ 36, 36, 85, 43, 43, 85, 85, 86, 86, 85, 98, 36, 36, 36, 61, 2,
+ 97, 67, 67, 67, 67, 50, 43, 43, 43, 43, 67, 67, 67, 67, 21, 2,
43, 98, 36, 36, 36, 36, 36, 36, 94, 43, 43, 86, 43, 87, 43, 36,
36, 36, 36, 85, 43, 86, 87, 87, 43, 86, 44, 44, 44, 44, 2, 2,
36, 36, 86, 86, 86, 86, 43, 43, 43, 43, 86, 43, 44, 93, 2, 2,
7, 7, 7, 7, 7, 44, 62, 36, 36, 36, 36, 36, 40, 40, 40, 2,
- 16, 16, 16, 16,110, 44, 44, 44, 11, 11, 11, 11, 11, 47, 48, 11,
+ 16, 16, 16, 16, 34,110, 44, 44, 11, 11, 11, 11, 11, 47, 48, 11,
2, 2, 2, 2, 44, 44, 44, 44, 43, 60, 43, 43, 43, 43, 43, 43,
85, 43, 43, 43, 71, 36, 70, 36, 36, 36, 71, 94, 43, 61, 44, 44,
16, 16, 16, 16, 16, 16, 40, 40, 40, 40, 40, 40, 40, 45, 16, 16,
@@ -1296,33 +1278,33 @@ _hb_ucd_u8[17884] =
67, 67, 67, 67, 4, 4, 67, 67, 8, 67, 67, 67,145,146, 67, 67,
67, 67, 67, 67, 67, 67,144, 67, 67, 67, 67, 67, 67, 26, 8, 8,
8, 8, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 8, 8,
- 8, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 92, 44, 44, 44, 44,
- 67, 67, 67, 67, 67, 92, 44, 44, 27, 27, 27, 27, 27, 27, 67, 67,
- 67, 67, 67, 67, 67, 27, 27, 27, 67, 67, 67, 26, 67, 67, 67, 67,
- 26, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 8, 8, 8, 8,
- 67, 67, 67, 67, 67, 67, 67, 26, 67, 67, 67, 67, 4, 4, 4, 4,
- 4, 4, 4, 27, 27, 27, 27, 27, 27, 27, 67, 67, 67, 67, 67, 67,
- 8, 8,129,147, 8, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 4,
- 8,129,148,148,148,148,148,148,148,148,148,148,147, 8, 8, 8,
- 8, 8, 8, 8, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 4, 8,
- 8, 8,144, 26, 8, 8,144, 67, 67, 67, 44, 67, 67, 67, 67, 67,
- 67, 67, 67, 55, 67, 67, 67, 67, 32, 11, 32, 34, 34, 34, 34, 11,
- 32, 32, 34, 16, 16, 16, 40, 11, 32, 32,140, 67, 67,138, 34,149,
- 43, 32, 44, 44, 93, 2, 99, 2, 16, 16, 16,150, 44, 44,150, 44,
- 36, 36, 36, 36, 44, 44, 44, 52, 64, 44, 44, 44, 44, 44, 44, 57,
- 36, 36, 36, 61, 44, 44, 44, 44, 36, 36, 36, 61, 36, 36, 36, 61,
- 2,121,121, 2,125,126,121, 2, 2, 2, 2, 6, 2,108,121, 2,
- 121, 4, 4, 4, 4, 2, 2, 88, 2, 2, 2, 2, 2,120, 2, 2,
- 108,151, 2, 2, 2, 2, 2, 2, 67, 2,152,148,148,148,153, 44,
- 67, 67, 67, 67, 67, 55, 67, 67, 67, 67, 44, 44, 44, 44, 44, 44,
- 67, 67, 67, 44, 44, 44, 44, 44, 1, 2,154,155, 4, 4, 4, 4,
- 4, 67, 4, 4, 4, 4,156,157,158,105,105,105,105, 43, 43, 86,
- 159, 40, 40, 67,105,160, 63, 67, 36, 36, 36, 61, 57,161,162, 69,
- 36, 36, 36, 36, 36, 63, 40, 69, 44, 44, 62, 36, 36, 36, 36, 36,
- 67, 27, 27, 67, 67, 67, 67, 67, 67, 67, 44, 44, 44, 44, 44, 55,
- 67, 67, 67, 67, 67, 67, 67, 92, 27, 27, 27, 27, 27, 67, 67, 67,
- 67, 67, 67, 67, 27, 27, 27, 27,163, 27, 27, 27, 27, 27, 27, 27,
- 36, 36, 83, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,164, 2,
+ 8, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 92, 44, 44,
+ 27, 27, 27, 27, 27, 27, 67, 67, 67, 67, 67, 67, 67, 27, 27, 27,
+ 67, 67, 67, 26, 67, 67, 67, 67, 26, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 8, 8, 8, 8, 67, 67, 67, 67, 67, 67, 67, 26,
+ 67, 67, 67, 67, 4, 4, 4, 4, 4, 4, 4, 27, 27, 27, 27, 27,
+ 27, 27, 67, 67, 67, 67, 67, 67, 8, 8,129,147, 8, 8, 8, 8,
+ 8, 8, 8, 4, 4, 4, 4, 4, 8,129,148,148,148,148,148,148,
+ 148,148,148,148,147, 8, 8, 8, 8, 8, 8, 8, 4, 4, 8, 8,
+ 8, 8, 8, 8, 8, 8, 4, 8, 8, 8,144, 26, 8, 8,144, 67,
+ 67, 67, 44, 67, 67, 67, 67, 67, 67, 67, 67, 55, 67, 67, 67, 67,
+ 32, 11, 32, 34, 34, 34, 34, 11, 32, 32, 34, 16, 16, 16, 40, 11,
+ 32, 32,140, 67, 67,138, 34,149, 43, 32, 44, 44, 93, 2, 99, 2,
+ 16, 16, 16,150, 44, 44,150, 44, 36, 36, 36, 36, 44, 44, 44, 52,
+ 64, 44, 44, 44, 44, 44, 44, 57, 36, 36, 36, 61, 44, 44, 44, 44,
+ 36, 36, 36, 61, 36, 36, 36, 61, 2,121,121, 2,125,126,121, 2,
+ 2, 2, 2, 6, 2,108,121, 2,121, 4, 4, 4, 4, 2, 2, 88,
+ 2, 2, 2, 2, 2,120, 2, 2,108,151, 2, 2, 2, 2, 2, 2,
+ 67, 2,152,148,148,148,153, 44, 67, 67, 67, 67, 67, 55, 67, 67,
+ 67, 67, 44, 44, 44, 44, 44, 44, 67, 67, 67, 44, 44, 44, 44, 44,
+ 1, 2,154,155, 4, 4, 4, 4, 4, 67, 4, 4, 4, 4,156,157,
+ 158,105,105,105,105, 43, 43, 86,159, 40, 40, 67,105,160, 63, 67,
+ 36, 36, 36, 61, 57,161,162, 69, 36, 36, 36, 36, 36, 63, 40, 69,
+ 44, 44, 62, 36, 36, 36, 36, 36, 67, 27, 27, 67, 67, 67, 67, 67,
+ 67, 67, 67, 44, 44, 44, 44, 55, 67, 67, 67, 67, 67, 67, 67, 92,
+ 27, 27, 27, 27, 27, 67, 67, 67, 67, 67, 67, 67, 27, 27, 27, 27,
+ 163, 27, 27, 27, 27, 27, 27, 27, 36, 36, 83, 36, 36, 36, 36, 36,
+ 67, 67, 67, 92, 44, 44, 44, 44, 36, 36, 36, 36, 36, 36,164, 2,
7, 7, 7, 7, 7, 36, 44, 44, 32, 32, 32, 32, 32, 32, 32, 70,
51,165, 43, 43, 43, 43, 43, 88, 32, 32, 32, 32, 32, 32, 40, 43,
36, 36, 36,105,105,105,105,105, 43, 2, 2, 2, 44, 44, 44, 44,
@@ -1330,7 +1312,7 @@ _hb_ucd_u8[17884] =
16, 32, 32, 32, 32, 32, 32, 32, 45, 16, 16, 16, 34, 34, 34, 32,
32, 32, 32, 32, 42,166, 34, 35, 32, 32, 16, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 11, 11, 32, 11, 11, 32, 32, 32, 32, 32, 32,
- 32, 32, 11, 11, 34,110, 44, 44, 32,150,150, 32, 32, 44, 44, 44,
+ 32, 32, 11, 11, 34, 34, 32, 44, 32,150,150, 32, 32, 32, 47, 44,
44, 40,167, 35, 40, 35, 36, 36, 36, 71, 36, 71, 36, 70, 36, 36,
36, 94, 87, 85, 67, 67, 80, 44, 27, 27, 27, 67,168, 44, 44, 44,
36, 36, 2, 2, 44, 44, 44, 44, 86, 36, 36, 36, 36, 36, 36, 36,
@@ -1391,8 +1373,10 @@ _hb_ucd_u8[17884] =
36, 61, 44, 44, 27, 27, 27, 27, 36, 44, 44, 44, 93, 2, 64, 44,
44, 44, 44, 44,179, 27, 27, 27, 11, 47, 44, 44, 44, 44, 44, 44,
16,110, 44, 44, 44, 27, 27, 27, 36, 36, 43, 43, 44, 44, 44, 44,
- 27, 27, 27, 27, 27, 27, 27,100, 36, 36, 36, 36, 36, 57,184, 44,
- 36, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 57, 43,
+ 7, 7, 7, 7, 7, 36, 36, 69, 11, 11, 11, 44, 57, 43, 43,159,
+ 16, 16, 16, 44, 44, 44, 44, 8, 27, 27, 27, 27, 27, 27, 27,100,
+ 36, 36, 36, 36, 36, 57,184, 44, 36, 44, 44, 44, 44, 44, 44, 44,
+ 44, 36, 61, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 43, 43,
27, 27, 27, 95, 44, 44, 44, 44,180, 27, 30, 2, 2, 44, 44, 44,
36, 43, 43, 2, 2, 44, 44, 44, 36, 36,183, 27, 27, 27, 44, 44,
87, 98, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 43, 43, 43, 43,
@@ -1410,14 +1394,18 @@ _hb_ucd_u8[17884] =
86, 87, 43, 43, 43, 80, 44, 44, 43, 86, 62, 36, 36, 36, 61, 62,
61, 36, 62, 36, 36, 57, 71, 86, 85, 86, 90, 89, 90, 89, 86, 44,
61, 44, 44, 89, 44, 44, 62, 36, 36, 86, 44, 43, 43, 43, 80, 44,
- 43, 43, 80, 44, 44, 44, 44, 44, 36, 36, 94, 86, 43, 43, 43, 43,
- 86, 43, 85, 71, 36, 63, 2, 2, 7, 7, 7, 7, 7, 2, 93, 71,
- 86, 87, 43, 43, 85, 85, 86, 87, 85, 43, 36, 72, 44, 44, 44, 44,
- 36, 36, 36, 36, 36, 36, 36, 94, 86, 43, 43, 44, 86, 86, 43, 87,
- 60, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 36, 36, 43, 44,
- 86, 87, 43, 43, 43, 85, 87, 87, 60, 2, 61, 44, 44, 44, 44, 44,
- 2, 2, 2, 2, 2, 2, 64, 44, 36, 36, 36, 36, 36, 70, 87, 86,
- 43, 43, 43, 87, 63, 44, 44, 44, 86, 43, 43, 87, 43, 43, 44, 44,
+ 43, 43, 80, 44, 44, 44, 44, 44, 36, 36, 36, 36, 36, 62, 44, 61,
+ 36, 36, 36, 62, 86, 87, 43, 43, 80, 90, 89, 89, 86, 90, 86, 85,
+ 71, 71, 2, 93, 64, 44, 44, 44, 57, 80, 44, 44, 44, 44, 44, 44,
+ 36, 36, 94, 86, 43, 43, 43, 43, 86, 43, 85, 71, 36, 63, 2, 2,
+ 7, 7, 7, 7, 7, 2, 93, 71, 86, 87, 43, 43, 85, 85, 86, 87,
+ 85, 43, 36, 72, 44, 44, 44, 44, 36, 36, 36, 36, 36, 36, 36, 94,
+ 86, 43, 43, 44, 86, 86, 43, 87, 60, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 36, 36, 43, 44, 86, 87, 43, 43, 43, 85, 87, 87,
+ 60, 2, 61, 44, 44, 44, 44, 44, 2, 2, 2, 2, 2, 2, 64, 44,
+ 36, 36, 36, 36, 36, 70, 87, 86, 43, 43, 43, 87, 63, 44, 44, 44,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 44, 44, 44, 44, 44, 44,
+ 36, 36, 36, 36, 36, 61, 57, 87, 86, 43, 43, 87, 43, 43, 44, 44,
7, 7, 7, 7, 7, 27, 2, 97, 43, 43, 43, 43, 87, 60, 44, 44,
27,100, 44, 44, 44, 44, 44, 62, 36, 36, 36, 61, 62, 44, 36, 36,
36, 36, 62, 61, 36, 36, 36, 36, 86, 86, 86, 89, 90, 57, 85, 71,
@@ -1427,49 +1415,52 @@ _hb_ucd_u8[17884] =
2, 2, 2, 59, 44, 44, 44, 44, 70, 43, 43, 85, 87, 43, 36, 36,
36, 36, 36, 36, 36, 43, 43, 43, 43, 43, 43, 85, 43, 2, 72, 2,
2, 64, 44, 44, 44, 44, 44, 44, 2, 2, 2, 2, 2, 44, 44, 44,
- 43, 43, 43, 80, 43, 43, 43, 87, 63, 2, 2, 44, 44, 44, 44, 44,
- 2, 36, 36, 36, 36, 36, 36, 36, 44, 43, 43, 43, 43, 43, 43, 43,
- 43, 43, 43, 43, 89, 43, 43, 43, 85, 43, 87, 80, 44, 44, 44, 44,
- 36, 36, 36, 61, 36, 62, 36, 36, 70, 43, 43, 80, 44, 80, 43, 57,
- 43, 43, 43, 70, 44, 44, 44, 44, 36, 36, 36, 62, 61, 36, 36, 36,
- 36, 36, 36, 36, 36, 86, 86, 90, 43, 89, 87, 87, 61, 44, 44, 44,
- 36, 70, 85,107, 64, 44, 44, 44, 43, 94, 36, 36, 36, 36, 36, 36,
- 36, 36, 86, 43, 43, 80, 44, 86, 85, 60, 2, 2, 2, 2, 2, 2,
+ 63, 44, 44, 44, 44, 44, 44, 44, 43, 43, 43, 80, 43, 43, 43, 87,
+ 63, 2, 2, 44, 44, 44, 44, 44, 2, 36, 36, 36, 36, 36, 36, 36,
+ 44, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 89, 43, 43, 43,
+ 85, 43, 87, 80, 44, 44, 44, 44, 36, 36, 36, 61, 36, 62, 36, 36,
+ 70, 43, 43, 80, 44, 80, 43, 57, 43, 43, 43, 70, 44, 44, 44, 44,
+ 36, 36, 36, 62, 61, 36, 36, 36, 36, 36, 36, 36, 36, 86, 86, 90,
+ 43, 89, 87, 87, 61, 44, 44, 44, 36, 70, 85,107, 64, 44, 44, 44,
+ 43, 94, 36, 36, 36, 36, 36, 36, 36, 36, 86, 43, 43, 80, 44, 86,
+ 85, 60, 2, 2, 2, 2, 2, 2, 7, 7, 7, 7, 7, 80, 44, 44,
27, 27, 91, 67, 67, 67, 56, 20,168, 67, 67, 67, 67, 67, 67, 67,
67, 44, 44, 44, 44, 44, 44, 93,105,105,105,105,105,105,105,181,
2, 2, 64, 44, 44, 44, 44, 44, 63, 64, 44, 44, 44, 44, 44, 44,
65, 65, 65, 65, 65, 65, 65, 65, 71, 36, 36, 70, 43, 43, 43, 43,
- 43, 43, 43, 44, 44, 44, 44, 44, 43, 43, 60, 44, 44, 44, 44, 44,
+ 43, 43, 43, 44, 44, 44, 44, 44, 36, 36, 36, 36, 36, 36, 36, 43,
+ 43, 43, 43, 43, 43, 86, 87, 43, 43, 43, 60, 44, 44, 44, 44, 44,
43, 43, 43, 60, 2, 2, 67, 67, 40, 40, 97, 44, 44, 44, 44, 44,
7, 7, 7, 7, 7,179, 27, 27, 27, 62, 36, 36, 36, 36, 36, 36,
- 36, 36, 36, 36, 44, 44, 62, 36, 27, 27, 27, 30, 2, 64, 44, 44,
+ 36, 36, 36, 36, 44, 44, 62, 36, 40, 69, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 83,164, 2, 27, 27, 27, 30, 2, 64, 44, 44,
36, 36, 36, 36, 36, 61, 44, 57, 94, 86, 86, 86, 86, 86, 86, 86,
86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 44, 44, 44, 57,
43, 74, 40, 40, 40, 40, 40, 40, 40, 88, 80, 44, 44, 44, 44, 44,
- 86, 44, 44, 44, 44, 44, 44, 44, 40, 40, 52, 40, 40, 40, 52, 81,
- 36, 61, 44, 44, 44, 44, 44, 44, 44, 61, 44, 44, 44, 44, 44, 44,
- 36, 61, 62, 44, 44, 44, 44, 44, 44, 44, 36, 36, 44, 44, 44, 44,
- 36, 36, 36, 36, 36, 44, 50, 60, 65, 65, 44, 44, 44, 44, 44, 44,
- 43, 43, 43, 43, 43, 43, 43, 44, 43, 43, 43, 80, 44, 44, 44, 44,
- 67, 67, 67, 92, 55, 67, 67, 67, 67, 67,186, 87, 43, 67,186, 86,
- 86,187, 65, 65, 65, 84, 43, 43, 43, 76, 50, 43, 43, 43, 67, 67,
- 67, 67, 67, 67, 67, 43, 43, 67, 67, 43, 76, 44, 44, 44, 44, 44,
- 27, 27, 44, 44, 44, 44, 44, 44, 11, 11, 11, 11, 11, 16, 16, 16,
- 16, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 16,
- 16, 16,110, 16, 16, 16, 16, 16, 11, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 47, 11, 44, 47, 48, 47, 48, 11, 47, 11,
- 11, 11, 11, 16, 16,150,150, 16, 16, 16,150, 16, 16, 16, 16, 16,
- 16, 16, 11, 48, 11, 47, 48, 11, 11, 11, 47, 11, 11, 11, 47, 16,
- 16, 16, 16, 16, 11, 48, 11, 47, 11, 11, 47, 47, 44, 11, 11, 11,
- 47, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 11, 11,
- 11, 11, 11, 16, 16, 16, 16, 16, 16, 16, 16, 44, 11, 11, 11, 11,
- 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 33, 16, 16,
- 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 31, 16, 16,
- 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 31, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 11,
- 11, 11, 11, 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16,
- 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16, 32, 44, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 43, 43, 43, 76, 67, 50, 43, 43,
+ 86, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 62,
+ 40, 40, 52, 40, 40, 40, 52, 81, 36, 61, 44, 44, 44, 44, 44, 44,
+ 44, 61, 44, 44, 44, 44, 44, 44, 36, 61, 62, 44, 44, 44, 44, 44,
+ 44, 44, 36, 36, 44, 44, 44, 44, 36, 36, 36, 36, 36, 44, 50, 60,
+ 65, 65, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 43, 43, 43, 44,
+ 43, 43, 43, 80, 44, 44, 44, 44, 67, 67, 67, 92, 55, 67, 67, 67,
+ 67, 67,186, 87, 43, 67,186, 86, 86,187, 65, 65, 65, 84, 43, 43,
+ 43, 76, 50, 43, 43, 43, 67, 67, 67, 67, 67, 67, 67, 43, 43, 67,
+ 67, 43, 76, 44, 44, 44, 44, 44, 27, 27, 44, 44, 44, 44, 44, 44,
+ 11, 11, 11, 11, 11, 16, 16, 16, 16, 16, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 16, 16, 16,110, 16, 16, 16, 16, 16,
+ 11, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 47, 11,
+ 44, 47, 48, 47, 48, 11, 47, 11, 11, 11, 11, 16, 16,150,150, 16,
+ 16, 16,150, 16, 16, 16, 16, 16, 16, 16, 11, 48, 11, 47, 48, 11,
+ 11, 11, 47, 11, 11, 11, 47, 16, 16, 16, 16, 16, 11, 48, 11, 47,
+ 11, 11, 47, 47, 44, 11, 11, 11, 47, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 11, 11, 11, 11, 11, 16, 16, 16, 16, 16,
+ 16, 16, 16, 44, 11, 11, 11, 11, 31, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11,
+ 11, 11, 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 33,
+ 16, 16, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 31,
+ 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 31, 16, 16, 16,
+ 16, 33, 16, 16, 16, 32, 44, 7, 43, 43, 43, 76, 67, 50, 43, 43,
43, 43, 43, 43, 43, 43, 76, 67, 67, 67, 50, 67, 67, 67, 67, 67,
67, 67, 76, 21, 2, 2, 44, 44, 44, 44, 44, 44, 44, 57, 43, 43,
16, 16, 16, 16, 16, 39, 16, 16, 16, 16, 16, 16, 16, 16, 16,110,
@@ -1479,22 +1470,23 @@ _hb_ucd_u8[17884] =
43, 43, 43, 74, 40, 40, 40, 44, 7, 7, 7, 7, 7, 44, 44, 77,
36, 36, 36, 36, 36, 36, 36, 80, 36, 36, 36, 36, 36, 36, 43, 43,
7, 7, 7, 7, 7, 44, 44, 96, 36, 36, 36, 36, 36, 83, 43, 43,
- 36, 36, 36, 61, 36, 36, 62, 61, 36, 36, 61,179, 27, 27, 27, 27,
- 16, 16, 43, 43, 43, 74, 44, 44, 27, 27, 27, 27, 27, 27,163, 27,
- 188, 27,100, 44, 44, 44, 44, 44, 27, 27, 27, 27, 27, 27, 27,163,
- 27, 27, 27, 27, 27, 27, 27, 44, 36, 36, 62, 36, 36, 36, 36, 36,
- 62, 61, 61, 62, 62, 36, 36, 36, 36, 61, 36, 36, 62, 62, 44, 44,
- 44, 61, 44, 62, 62, 62, 62, 36, 62, 61, 61, 62, 62, 62, 62, 62,
- 62, 61, 61, 62, 36, 61, 36, 36, 36, 61, 36, 36, 62, 36, 61, 61,
- 36, 36, 36, 36, 36, 62, 36, 36, 62, 36, 62, 36, 36, 62, 36, 36,
- 8, 44, 44, 44, 44, 44, 44, 44, 67, 67, 67, 67, 67, 67, 44, 44,
- 55, 67, 67, 67, 67, 67, 67, 67, 27, 27, 27, 27, 27, 27, 91, 67,
- 67, 67, 67, 67, 67, 67, 67, 44, 44, 44, 44, 67, 67, 67, 67, 67,
- 67, 92, 44, 44, 44, 44, 44, 44, 67, 67, 67, 67, 92, 44, 44, 44,
- 67, 44, 44, 44, 44, 44, 44, 44, 67, 67, 67, 67, 67, 25, 41, 41,
- 67, 67, 67, 67, 44, 44, 67, 67, 67, 67, 67, 92, 44, 55, 67, 67,
- 67, 67, 67, 67, 44, 44, 44, 44, 67, 67, 67, 67, 67, 67, 67, 55,
- 67, 67, 67, 44, 44, 44, 44, 67, 67, 92, 67, 67, 67, 67, 67, 67,
+ 188, 7, 7, 7, 7,189, 44, 93, 36, 36, 36, 61, 36, 36, 62, 61,
+ 36, 36, 61,179, 27, 27, 27, 27, 16, 16, 43, 43, 43, 74, 44, 44,
+ 27, 27, 27, 27, 27, 27,163, 27,190, 27,100, 44, 44, 44, 44, 44,
+ 27, 27, 27, 27, 27, 27, 27,163, 27, 27, 27, 27, 27, 27, 27, 44,
+ 36, 36, 62, 36, 36, 36, 36, 36, 62, 61, 61, 62, 62, 36, 36, 36,
+ 36, 61, 36, 36, 62, 62, 44, 44, 44, 61, 44, 62, 62, 62, 62, 36,
+ 62, 61, 61, 62, 62, 62, 62, 62, 62, 61, 61, 62, 36, 61, 36, 36,
+ 36, 61, 36, 36, 62, 36, 61, 61, 36, 36, 36, 36, 36, 62, 36, 36,
+ 62, 36, 62, 36, 36, 62, 36, 36, 8, 44, 44, 44, 44, 44, 44, 44,
+ 67, 67, 67, 67, 67, 67, 44, 44, 55, 67, 67, 67, 67, 67, 67, 67,
+ 27, 27, 27, 27, 27, 27, 91, 67, 67, 67, 67, 67, 67, 67, 67, 44,
+ 44, 44, 44, 67, 67, 67, 67, 67, 67, 92, 44, 44, 44, 44, 44, 44,
+ 67, 67, 67, 67, 92, 44, 44, 44, 67, 44, 44, 44, 44, 44, 44, 44,
+ 67, 67, 67, 67, 67, 25, 41, 41, 67, 67, 67, 67, 44, 44, 67, 67,
+ 67, 67, 67, 92, 44, 55, 67, 67, 67, 67, 67, 67, 44, 44, 44, 44,
+ 67, 67, 67, 67, 67, 44, 44, 55, 67, 67, 67, 92, 44, 44, 44, 67,
+ 67, 67, 67, 67, 67, 67, 92, 55, 67, 92, 67, 67, 67, 67, 67, 67,
79, 44, 44, 44, 44, 44, 44, 44,171,171,171,171,171,171,171, 44,
171,171,171,171,171,171,171, 0, 0, 0, 29, 21, 21, 21, 23, 21,
22, 18, 21, 25, 21, 17, 13, 13, 25, 25, 25, 21, 21, 9, 9, 9,
@@ -1520,366 +1512,350 @@ _hb_ucd_u8[17884] =
6, 21, 11, 21, 24, 9, 6, 9, 23, 26, 6, 10, 4, 4, 3, 3,
7, 25, 17, 16, 16, 22, 16, 16, 25, 17, 25, 2, 25, 24, 2, 15,
12, 15, 14, 2, 21, 14, 7, 15, 12, 17, 21, 1, 26, 10, 10, 1,
- 23, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12,
- 13, 0, 14, 0, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0,
+ 7, 13, 13, 2, 23, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 0, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 0, 15, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20,
- 0, 21, 22, 23, 0, 0, 0, 24, 25, 26, 27, 28, 29, 30, 31, 32,
- 33, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 24, 25, 26, 27, 28,
+ 29, 30, 31, 32, 33, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 35, 0, 36, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 36, 0, 37, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 39, 0, 0, 0, 0,
- 0, 0, 40, 41, 42, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0, 0, 0, 4, 5,
- 6, 7, 0, 8, 9, 10, 0, 11, 12, 13, 14, 15, 16, 17, 16, 18,
- 16, 19, 16, 19, 16, 19, 0, 19, 16, 20, 16, 19, 21, 19, 0, 22,
- 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 32, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, 35,
- 0, 0, 36, 0, 37, 0, 0, 0, 38, 39, 40, 41, 42, 43, 44, 45,
- 46, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0, 0, 0,
- 0, 0, 0, 50, 0, 51, 0, 52, 53, 0, 54, 0, 0, 0, 0, 0,
- 0, 55, 56, 57, 0, 0, 0, 0, 58, 0, 0, 59, 60, 61, 62, 63,
- 0, 0, 64, 65, 0, 0, 0, 66, 0, 0, 0, 0, 67, 0, 0, 0,
- 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69,
- 0, 0, 0, 70, 0, 71, 0, 0, 72, 0, 0, 73, 0, 0, 0, 0,
- 0, 0, 0, 0, 74, 0, 0, 0, 0, 0, 75, 76, 0, 77, 78, 0,
- 0, 79, 80, 0, 81, 62, 0, 82, 83, 0, 0, 84, 85, 86, 0, 0,
- 0, 87, 0, 88, 0, 0, 51, 89, 51, 0, 90, 0, 91, 0, 0, 0,
- 80, 0, 0, 0, 92, 93, 0, 94, 95, 96, 97, 0, 0, 0, 0, 0,
- 51, 0, 0, 0, 0, 98, 99, 0, 0, 0, 0, 0, 0,100, 0, 0,
- 0, 0, 0,101,102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,103,
- 0, 0,104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,105,106, 0,
- 0,107, 0, 0, 0, 0, 0, 0,108, 0,109, 0,102, 0, 0, 0,
- 0, 0,110,111, 0, 0, 0, 0, 0, 0, 0,112, 0, 0, 0, 0,
- 0, 0, 0,113, 0,114, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
- 5, 6, 7, 0, 8, 0, 0, 0, 0, 9, 10, 11, 12, 0, 0, 0,
- 0, 13, 0, 0, 14, 15, 0, 16, 0, 17, 18, 0, 0, 19, 0, 20,
- 21, 0, 0, 0, 0, 0, 22, 23, 0, 24, 25, 0, 0, 26, 0, 0,
- 0, 27, 0, 0, 28, 29, 30, 31, 0, 0, 0, 32, 33, 34, 0, 0,
- 33, 0, 0, 35, 33, 0, 0, 0, 33, 36, 0, 0, 0, 0, 0, 37,
- 38, 0, 0, 0, 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 41,
- 42, 0, 0, 0, 0, 43, 0, 44, 0, 0, 0, 45, 46, 0, 0, 0,
- 47, 0, 0, 0, 0, 0, 0, 48, 49, 0, 0, 0, 0, 50, 0, 0,
- 0, 51, 0, 52, 0, 53, 0, 0, 0, 0, 54, 0, 0, 0, 0, 55,
- 0, 56, 0, 0, 0, 0, 57, 58, 0, 0, 0, 59, 60, 0, 0, 0,
- 0, 0, 0, 61, 52, 0, 62, 63, 0, 0, 64, 0, 0, 0, 65, 66,
- 0, 0, 0, 67, 0, 68, 69, 70, 71, 72, 1, 73, 0, 74, 75, 76,
- 0, 0, 77, 78, 0, 0, 0, 79, 0, 0, 1, 1, 0, 0, 80, 0,
- 0, 81, 0, 0, 0, 0, 77, 82, 0, 83, 0, 0, 0, 0, 0, 78,
- 84, 0, 85, 0, 52, 0, 1, 78, 0, 0, 86, 0, 0, 87, 0, 0,
- 0, 0, 0, 88, 57, 0, 0, 0, 0, 0, 0, 89, 90, 0, 0, 84,
- 0, 0, 33, 0, 0, 91, 0, 0, 0, 0, 92, 0, 0, 0, 0, 49,
- 0, 0, 93, 0, 0, 0, 0, 94, 95, 0, 0, 96, 0, 0, 97, 0,
- 0, 0, 98, 0, 0, 0, 99, 0, 0, 0, 0,100,101, 93, 0, 0,
- 102, 0, 0, 0, 84, 0, 0,103, 0, 0, 0,104,105, 0, 0,106,
- 107, 0, 0, 0, 0, 0, 0,108, 0, 0,109, 0, 0, 0, 0,110,
- 33, 0,111,112,113, 35, 0, 0,114, 0, 0, 0,115, 0, 0, 0,
- 0, 0, 0,116, 0, 0,117, 0, 0, 0, 0,118, 88, 0, 0, 0,
- 0, 0, 57, 0, 0, 0, 0, 52,119, 0, 0, 0, 0,120, 0, 0,
- 121, 0, 0, 0, 0,119, 0, 0,122, 0, 0, 0, 0, 0, 0,123,
- 0, 0, 0,124, 0, 0, 0,125, 0,126, 0, 0, 0, 0,127,128,
- 129, 0,130, 0,131, 0, 0, 0,132,133,134, 0, 77, 0, 0, 0,
- 0, 0, 35, 0, 0, 0,135, 0, 0, 0,136, 0, 0,137, 0, 0,
- 138, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 4,
- 5, 6, 7, 4, 4, 8, 9, 10, 1, 11, 12, 13, 14, 15, 16, 17,
- 18, 1, 1, 1, 19, 1, 0, 0, 20, 21, 22, 1, 23, 4, 21, 24,
- 25, 26, 27, 28, 29, 30, 0, 0, 1, 1, 31, 0, 0, 0, 32, 33,
- 34, 35, 1, 36, 37, 0, 0, 0, 0, 38, 1, 39, 14, 39, 40, 41,
- 42, 0, 0, 0, 43, 36, 44, 45, 21, 45, 46, 0, 0, 0, 19, 1,
- 21, 0, 0, 47, 0, 38, 48, 1, 1, 49, 49, 50, 0, 0, 51, 0,
- 0, 0, 52, 1, 0, 0, 38, 14, 4, 1, 1, 1, 53, 21, 43, 52,
- 54, 21, 35, 1, 0, 0, 0, 55, 0, 0, 0, 56, 57, 58, 0, 0,
- 0, 0, 0, 59, 0, 60, 0, 0, 0, 0, 61, 62, 0, 0, 63, 0,
- 0, 0, 64, 0, 0, 0, 65, 0, 0, 0, 66, 0, 0, 0, 67, 0,
- 0, 0, 68, 0, 0, 69, 70, 0, 71, 72, 73, 74, 75, 76, 0, 0,
- 0, 77, 0, 0, 0, 78, 79, 0, 0, 0, 0, 47, 0, 0, 0, 49,
- 0, 80, 0, 0, 0, 62, 0, 0, 63, 0, 0, 81, 0, 0, 82, 0,
- 0, 0, 83, 0, 0, 19, 84, 0, 62, 0, 0, 0, 0, 49, 1, 85,
- 1, 52, 15, 86, 36, 10, 21, 87, 0, 55, 0, 0, 0, 0, 19, 10,
- 1, 0, 0, 0, 0, 0, 88, 0, 0, 89, 0, 0, 88, 0, 0, 0,
- 0, 78, 0, 0, 87, 9, 12, 4, 90, 8, 91, 47, 0, 58, 50, 0,
- 21, 1, 21, 92, 93, 1, 1, 1, 1, 94, 95, 96, 97, 1, 98, 58,
- 81, 99,100, 4, 58, 0, 0, 0, 0, 0, 0, 19, 50, 0, 0, 0,
- 0, 0, 0, 61, 0, 0,101,102, 0, 0,103, 0, 0, 1, 1, 50,
- 0, 0, 0, 38, 0, 63, 0, 0, 0, 0, 0, 62, 0, 0,104, 68,
- 61, 0, 0, 0, 78, 0, 0, 0,105,106, 58, 38, 81, 0, 0, 0,
- 0, 0, 0,107, 1, 14, 4, 12, 84, 0, 0, 0, 0, 38, 87, 0,
- 0, 0, 0,108, 0, 0,109, 61, 0,110, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 40,
+ 0, 0, 0, 0, 0, 0, 41, 42, 43, 0, 44, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0,
+ 0, 0, 4, 5, 6, 7, 0, 8, 9, 10, 0, 11, 12, 13, 14, 15,
+ 16, 17, 16, 18, 16, 19, 16, 19, 16, 19, 0, 19, 16, 20, 16, 19,
+ 21, 19, 0, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0,
+ 34, 0, 0, 35, 0, 0, 36, 0, 37, 0, 0, 0, 38, 39, 40, 41,
+ 42, 43, 44, 45, 46, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 49,
+ 0, 0, 0, 0, 0, 0, 0, 50, 0, 51, 0, 52, 53, 0, 54, 0,
+ 0, 0, 0, 0, 0, 55, 56, 57, 0, 0, 0, 0, 58, 0, 0, 59,
+ 60, 61, 62, 63, 0, 0, 64, 65, 0, 0, 0, 66, 0, 0, 0, 0,
+ 67, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 69, 0, 0, 0, 70, 0, 71, 0, 0, 72, 0, 0, 73,
+ 0, 0, 0, 0, 0, 0, 0, 0, 74, 75, 0, 0, 0, 0, 76, 77,
+ 0, 78, 79, 0, 0, 80, 81, 0, 82, 62, 0, 83, 84, 0, 0, 85,
+ 86, 87, 0, 88, 0, 89, 0, 90, 0, 0, 51, 91, 51, 0, 92, 0,
+ 93, 0, 0, 0, 81, 0, 0, 0, 94, 95, 0, 96, 97, 98, 99, 0,
+ 0, 0, 0, 0, 51, 0, 0, 0, 0,100,101, 0, 0, 0, 0, 0,
+ 0,102, 0, 0, 0, 0, 0, 0,103, 0, 0, 0, 0, 0, 0,104,
+ 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,106, 0, 0,107, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,108,109, 0, 0,110, 0, 0,
+ 0, 0, 0, 0,111, 0,112, 0,105, 0, 0, 0, 0, 0,113,114,
+ 0, 0, 0, 0, 0, 0, 0,115, 0, 0, 0,116, 0, 0, 0,117,
+ 0,118, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 0,
+ 8, 0, 0, 0, 0, 9, 10, 11, 12, 0, 0, 0, 0, 13, 0, 0,
+ 14, 15, 0, 16, 0, 17, 18, 0, 0, 19, 0, 20, 21, 0, 0, 0,
+ 0, 0, 22, 23, 0, 24, 25, 0, 0, 26, 0, 0, 0, 27, 0, 0,
+ 28, 29, 30, 31, 0, 0, 0, 32, 33, 34, 0, 0, 33, 0, 0, 35,
+ 33, 0, 0, 0, 33, 36, 0, 0, 0, 0, 0, 37, 38, 0, 0, 0,
+ 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0,
+ 0, 43, 0, 44, 0, 0, 0, 45, 46, 0, 0, 0, 47, 0, 0, 0,
+ 0, 0, 0, 48, 49, 0, 0, 0, 0, 50, 0, 0, 0, 51, 0, 52,
+ 0, 53, 0, 0, 0, 0, 54, 0, 0, 0, 0, 55, 0, 56, 0, 0,
+ 0, 0, 57, 58, 0, 0, 0, 59, 60, 0, 0, 0, 0, 0, 0, 61,
+ 52, 0, 62, 63, 0, 0, 64, 0, 0, 0, 65, 66, 0, 0, 0, 67,
+ 0, 68, 69, 70, 71, 72, 1, 73, 0, 74, 75, 76, 0, 0, 77, 78,
+ 0, 0, 0, 79, 0, 0, 1, 1, 0, 0, 80, 0, 0, 81, 0, 0,
+ 0, 0, 77, 82, 0, 83, 0, 0, 0, 0, 0, 78, 84, 0, 85, 0,
+ 52, 0, 1, 78, 0, 0, 86, 0, 0, 87, 0, 0, 0, 0, 0, 88,
+ 57, 0, 0, 0, 0, 0, 0, 89, 90, 0, 0, 84, 0, 0, 33, 0,
+ 0, 91, 0, 0, 0, 0, 92, 0, 0, 0, 0, 49, 0, 0, 93, 0,
+ 0, 0, 0, 94, 95, 0, 0, 96, 0, 0, 97, 0, 0, 0, 98, 0,
+ 0, 0, 99, 0, 0, 0,100, 0, 0, 0, 0,101,102, 93, 0, 0,
+ 103, 0, 0, 0, 84, 0, 0,104, 0, 0, 0,105,106, 0, 0,107,
+ 108, 0, 0, 0, 0, 0, 0,109, 0, 0,110, 0, 0, 0, 0,111,
+ 33, 0,112,113,114, 57, 0, 0,115, 35, 0, 0,116, 0, 0, 0,
+ 117, 0, 0, 0, 0, 0, 0,118, 0, 0,119, 0, 0, 0, 0,120,
+ 88, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 52,121, 0, 0, 0,
+ 0,122, 0, 0,123, 0, 0, 0, 0,121, 0, 0,124, 0, 0, 0,
+ 0, 0, 79, 0, 0, 0, 0,125, 0, 0, 0,126, 0, 0, 0,127,
+ 0,128, 0, 0, 0, 0,129,130,131, 0,132, 0,133, 0, 0, 0,
+ 134,135,136, 0, 77, 0, 0, 0, 0, 0, 35, 0, 0, 0,137, 0,
+ 0, 0,138, 0, 0, 0,139, 0, 0,140, 0, 0,141, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 4,
+ 4, 8, 9, 10, 1, 11, 12, 13, 14, 15, 16, 17, 18, 1, 1, 1,
+ 19, 1, 0, 0, 20, 21, 22, 1, 23, 4, 21, 24, 25, 26, 27, 28,
+ 29, 30, 0, 0, 1, 1, 31, 0, 0, 0, 32, 33, 34, 35, 1, 36,
+ 37, 0, 0, 0, 0, 38, 1, 39, 14, 39, 40, 41, 42, 0, 0, 0,
+ 43, 36, 44, 45, 21, 45, 46, 0, 0, 0, 19, 1, 21, 0, 0, 47,
+ 0, 38, 48, 1, 1, 49, 49, 50, 0, 0, 51, 0, 0, 19, 52, 1,
+ 0, 0, 38, 14, 4, 1, 1, 1, 53, 21, 43, 52, 54, 21, 35, 1,
+ 0, 0, 0, 55, 0, 0, 0, 56, 57, 58, 0, 0, 0, 0, 0, 59,
+ 0, 60, 0, 0, 0, 0, 61, 62, 0, 0, 63, 0, 0, 0, 64, 0,
+ 0, 0, 65, 0, 0, 0, 66, 0, 0, 0, 67, 0, 0, 0, 68, 0,
+ 0, 69, 70, 0, 71, 72, 73, 74, 75, 76, 0, 0, 0, 77, 0, 0,
+ 0, 78, 79, 0, 0, 0, 0, 47, 0, 0, 0, 49, 0, 80, 0, 0,
+ 0, 62, 0, 0, 63, 0, 0, 81, 0, 0, 82, 0, 0, 0, 83, 0,
+ 0, 19, 84, 0, 62, 0, 0, 0, 0, 49, 1, 85, 1, 52, 15, 86,
+ 36, 10, 21, 87, 0, 55, 0, 0, 0, 0, 19, 10, 1, 0, 0, 0,
+ 0, 0, 88, 0, 0, 89, 0, 0, 88, 0, 0, 0, 0, 78, 0, 0,
+ 87, 9, 12, 4, 90, 8, 91, 47, 0, 58, 50, 0, 21, 1, 21, 92,
+ 93, 1, 1, 1, 1, 94, 95, 96, 97, 1, 98, 58, 81, 99,100, 4,
+ 58, 0, 0, 0, 0, 0, 0, 19, 50, 0, 0, 0, 0, 0, 0, 61,
+ 0, 0,101,102, 0, 0,103, 0, 0, 1, 1, 50, 0, 0, 0, 38,
+ 0, 63, 0, 0, 0, 0, 0, 62, 0, 0,104, 68, 61, 0, 0, 0,
+ 78, 0, 0, 0,105,106, 58, 38, 81, 0, 0, 0, 0, 0, 0,107,
+ 1, 14, 4, 12, 84, 0, 0, 0, 0, 38, 87, 0, 0, 0, 0,108,
+ 0, 0,109, 61, 0,110, 0, 0, 0, 1, 0, 0, 0, 0, 49, 50,
0, 0, 19, 58, 0, 0, 0, 51, 0,111, 14, 52,112, 41, 0, 0,
62, 0, 0, 61, 0, 0,113, 0, 87, 0, 0, 0, 61, 62, 0, 0,
62, 0, 89, 0, 0,113, 0, 0, 0, 0,114, 0, 0, 0, 78, 55,
- 0, 38, 1, 58, 1, 58, 0, 0, 63, 89, 0, 0,115, 0, 0, 0,
- 55, 0, 0, 0, 0,115, 0, 0, 0, 0, 61, 0, 0, 0, 0, 79,
- 0, 61, 0, 0, 0, 0, 56, 0, 89, 80, 0, 0, 79, 0, 0, 0,
- 8, 91, 0, 0, 1, 87, 0, 0,116, 0, 0, 0, 0, 0, 0,117,
- 0,118,119,120,121, 0,104, 4,122, 49, 23, 0, 0, 0, 38, 50,
- 38, 58, 0, 0, 1, 87, 1, 1, 1, 1, 39, 1, 48,105, 87, 0,
- 0, 0, 0, 1, 0, 0, 0,123, 4,122, 0, 0, 0, 1,124, 0,
- 0, 0, 0, 0,230,230,230,230,230,232,220,220,220,220,232,216,
- 220,220,220,220,220,202,202,220,220,220,220,202,202,220,220,220,
- 1, 1, 1, 1, 1,220,220,220,220,230,230,230,230,240,230,220,
- 220,220,230,230,230,220,220, 0,230,230,230,220,220,220,220,230,
- 232,220,220,230,233,234,234,233,234,234,233,230, 0, 0, 0,230,
- 0,220,230,230,230,230,220,230,230,230,222,220,230,230,220,220,
- 230,222,228,230, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20,
- 21, 22, 0, 23, 0, 24, 25, 0,230,220, 0, 18, 30, 31, 32, 0,
- 0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34,230,230,220,220,230,
- 220,230,230,220, 35, 0, 0, 0, 0, 0,230,230,230, 0, 0,230,
- 230, 0,220,230,230,220, 0, 0, 0, 36, 0, 0,230,220,230,230,
- 220,220,230,220,220,230,220,230,220,230,230, 0, 0,220, 0, 0,
- 230,230, 0,230, 0,230,230,230,230,230, 0, 0, 0,220,220,220,
- 230,220,220,220,230,230, 0,220, 27, 28, 29,230, 7, 0, 0, 0,
- 0, 9, 0, 0, 0,230,220,230,230, 0, 0, 0, 0, 0,230, 0,
- 0, 84, 91, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 9, 0,
- 103,103, 9, 0,107,107,107,107,118,118, 9, 0,122,122,122,122,
- 220,220, 0, 0, 0,220, 0,220, 0,216, 0, 0, 0,129,130, 0,
- 132, 0, 0, 0, 0, 0,130,130,130,130, 0, 0,130, 0,230,230,
- 9, 0,230,230, 0, 0,220, 0, 0, 0, 0, 7, 0, 9, 9, 0,
- 9, 9, 0, 0, 0,230, 0, 0, 0,228, 0, 0, 0,222,230,220,
- 220, 0, 0, 0,230, 0, 0,220,230,220, 0,220,230,230,230, 0,
- 0, 0, 9, 9, 0, 0, 7, 0,230, 0, 1, 1, 1, 0, 0, 0,
- 230,234,214,220,202,230,230,230,230,230,232,228,228,220,218,230,
- 233,220,230,220,230,230, 1, 1, 1, 1, 1,230, 0, 1, 1,230,
- 220,230, 1, 1, 0, 0,218,228,232,222,224,224, 0, 8, 8, 0,
- 0, 0, 0,220,230, 0,230,230,220, 0, 0,230, 0, 0, 26, 0,
- 0,220, 0,230,230, 1,220, 0, 0,230,220, 0, 0, 0,220,220,
- 0, 0,230,220, 0, 9, 7, 0, 0, 7, 9, 0, 0, 0, 9, 7,
- 6, 6, 0, 0, 0, 0, 1, 0, 0,216,216, 1, 1, 1, 0, 0,
- 0,226,216,216,216,216,216, 0,220,220,220, 0,232,232,220,230,
- 230,230, 7, 0, 16, 17, 17, 17, 17, 17, 17, 33, 17, 17, 17, 19,
- 17, 17, 17, 17, 20,101, 17,113,129,169, 17, 27, 28, 17, 17, 17,
+ 0, 38, 1, 58, 1, 58, 0, 0, 0, 0, 0, 88, 63, 89, 0, 0,
+ 115, 0, 0, 0, 55, 0, 0, 0, 0,115, 0, 0, 0, 0, 61, 0,
+ 0, 0, 0, 79, 0, 61, 0, 0, 0, 0, 56, 0, 89, 80, 0, 0,
+ 79, 0, 0, 0, 8, 91, 0, 0, 1, 87, 0, 0,116, 0, 0, 0,
+ 0, 0, 0,117, 0,118,119,120,121, 0,104, 4,122, 49, 23, 0,
+ 0, 0, 38, 50, 38, 58, 0, 0, 1, 87, 1, 1, 1, 1, 39, 1,
+ 48,105, 87, 0, 0, 0, 0, 1, 0, 0, 0,123, 0, 0, 0,112,
+ 4,122, 0, 0, 0, 1,124, 0, 0, 0, 0, 0,230,230,230,230,
+ 230,232,220,220,220,220,232,216,220,220,220,220,220,202,202,220,
+ 220,220,220,202,202,220,220,220, 1, 1, 1, 1, 1,220,220,220,
+ 220,230,230,230,230,240,230,220,220,220,230,230,230,220,220, 0,
+ 230,230,230,220,220,220,220,230,232,220,220,230,233,234,234,233,
+ 234,234,233,230, 0, 0, 0,230, 0,220,230,230,230,230,220,230,
+ 230,230,222,220,230,230,220,220,230,222,228,230, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 0, 23, 0, 24, 25, 0,
+ 230,220, 0, 18, 30, 31, 32, 0, 0, 0, 0, 27, 28, 29, 30, 31,
+ 32, 33, 34,230,230,220,220,230,220,230,230,220, 35, 0, 0, 0,
+ 0, 0,230,230,230, 0, 0,230,230, 0,220,230,230,220, 0, 0,
+ 0, 36, 0, 0,230,220,230,230,220,220,230,220,220,230,220,230,
+ 220,230,230, 0, 0,220, 0, 0,230,230, 0,230, 0,230,230,230,
+ 230,230, 0, 0, 0,220,220,220,230,220,220,220,230,230, 0,220,
+ 27, 28, 29,230, 7, 0, 0, 0, 0, 9, 0, 0, 0,230,220,230,
+ 230, 0, 0, 0, 0, 0,230, 0, 0, 84, 91, 0, 0, 0, 0, 9,
+ 9, 0, 0, 0, 0, 0, 9, 0,103,103, 9, 0,107,107,107,107,
+ 118,118, 9, 0,122,122,122,122,220,220, 0, 0, 0,220, 0,220,
+ 0,216, 0, 0, 0,129,130, 0,132, 0, 0, 0, 0, 0,130,130,
+ 130,130, 0, 0,130, 0,230,230, 9, 0,230,230, 0, 0,220, 0,
+ 0, 0, 0, 7, 0, 9, 9, 0, 9, 9, 0, 0, 0,230, 0, 0,
+ 0,228, 0, 0, 0,222,230,220,220, 0, 0, 0,230, 0, 0,220,
+ 230,220, 0,220,230,230,230, 0, 0, 0, 9, 9, 0, 0, 7, 0,
+ 230, 0, 1, 1, 1, 0, 0, 0,230,234,214,220,202,230,230,230,
+ 230,230,232,228,228,220,218,230,233,220,230,220,230,230, 1, 1,
+ 1, 1, 1,230, 0, 1, 1,230,220,230, 1, 1, 0, 0,218,228,
+ 232,222,224,224, 0, 8, 8, 0, 0, 0, 0,220,230, 0,230,230,
+ 220, 0, 0,230, 0, 0, 26, 0, 0,220, 0,230,230, 1,220, 0,
+ 0,230,220, 0, 0, 0,220,220, 0, 0,230,220, 0, 9, 7, 0,
+ 0, 7, 9, 0, 0, 0, 9, 7, 6, 6, 0, 0, 0, 0, 1, 0,
+ 0,216,216, 1, 1, 1, 0, 0, 0,226,216,216,216,216,216, 0,
+ 220,220,220, 0,232,232,220,230,230,230, 7, 0, 16, 17, 17, 17,
+ 17, 17, 17, 33, 17, 17, 17, 19, 17, 17, 17, 17, 20,101, 17,113,
+ 129,169, 17, 27, 28, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17, 17, 17,237, 0, 1, 2, 2, 0, 3, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 5, 0, 0, 0, 0, 6, 7, 8, 9, 0, 0, 0, 10, 11, 12, 13,
- 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20,
- 0, 0, 21, 22, 0, 0, 0, 0, 23, 24, 25, 26, 0, 27, 0, 28,
- 29, 30, 31, 32, 0, 0, 0, 0, 0, 0, 0, 33, 34, 35, 36, 0,
- 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 39,
- 0, 0, 0, 0, 1, 2, 40, 41, 0, 1, 2, 2, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0,
- 0, 0, 3, 4, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 0, 0,
- 0, 0, 7, 1, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 0,
- 0, 0, 10, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 11, 12,
- 0, 13, 0, 14, 15, 16, 0, 0, 0, 0, 0, 1, 17, 18, 0, 19,
- 7, 1, 0, 0, 0, 20, 20, 7, 20, 20, 20, 20, 20, 20, 20, 8,
- 21, 0, 22, 0, 7, 23, 24, 0, 20, 20, 25, 0, 0, 0, 26, 27,
- 1, 7, 20, 20, 20, 20, 20, 1, 28, 29, 30, 31, 0, 0, 20, 0,
- 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 20, 20,
- 20, 1, 0, 0, 8, 21, 32, 4, 0, 10, 0, 33, 7, 20, 20, 20,
- 0, 0, 0, 0, 8, 34, 34, 35, 36, 34, 37, 0, 38, 1, 20, 20,
- 0, 0, 39, 0, 1, 1, 0, 8, 21, 1, 20, 0, 0, 0, 1, 0,
- 0, 40, 1, 1, 0, 0, 8, 21, 0, 1, 0, 1, 0, 1, 0, 0,
- 0, 0, 26, 34, 34, 34, 34, 34, 34, 34, 34, 34, 21, 7, 20, 41,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 21, 0, 42, 43, 44, 0, 45,
- 0, 8, 21, 0, 0, 0, 0, 0, 0, 0, 0, 46, 7, 1, 10, 1,
- 0, 0, 0, 1, 20, 20, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 26, 34, 9, 0, 0, 20, 20, 1, 20, 20, 0, 0, 0, 0, 0,
- 0, 0, 26, 21, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 3, 47, 48, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
- 4, 5, 6, 7, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 9, 10, 11, 11, 11, 11, 12, 13, 13, 13, 13, 14, 15, 16, 17, 18,
- 19, 20, 21, 13, 22, 13, 13, 13, 13, 23, 24, 24, 25, 26, 13, 13,
- 13, 27, 28, 29, 13, 30, 31, 32, 33, 34, 35, 36, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 37, 7, 38, 39, 7, 40, 7, 7, 7, 41, 13, 42, 7, 7, 43, 7,
- 44, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,237, 0, 1, 2, 2,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 6, 7, 8,
+ 9, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 21, 22, 0, 0, 0, 0,
+ 23, 24, 25, 26, 0, 27, 0, 28, 29, 30, 31, 32, 0, 0, 0, 0,
+ 0, 0, 0, 33, 34, 35, 36, 0, 0, 0, 0, 0, 37, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 38, 39, 0, 0, 0, 0, 1, 2, 40, 41,
+ 0, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 5, 0,
+ 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0,
+ 0, 0, 8, 9, 0, 0, 0, 0, 0, 0, 10, 0, 0, 10, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10,
+ 0, 0, 0, 0, 0, 0, 11, 12, 0, 13, 0, 14, 15, 16, 0, 0,
+ 0, 0, 0, 1, 17, 18, 0, 19, 7, 1, 0, 0, 0, 20, 20, 7,
+ 20, 20, 20, 20, 20, 20, 20, 8, 21, 0, 22, 0, 7, 23, 24, 0,
+ 20, 20, 25, 0, 0, 0, 26, 27, 1, 7, 20, 20, 20, 20, 20, 1,
+ 28, 29, 30, 31, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 10, 0,
+ 0, 0, 0, 0, 0, 0, 20, 20, 20, 1, 0, 0, 8, 21, 32, 4,
+ 0, 10, 0, 33, 7, 20, 20, 20, 0, 0, 0, 0, 8, 34, 34, 35,
+ 36, 34, 37, 0, 38, 1, 20, 20, 0, 0, 39, 0, 1, 1, 0, 8,
+ 21, 1, 20, 0, 0, 0, 1, 0, 0, 40, 1, 1, 0, 0, 8, 21,
+ 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 26, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 21, 7, 20, 41, 34, 34, 34, 34, 34, 34, 34, 34,
+ 34, 21, 0, 42, 43, 44, 0, 45, 0, 8, 21, 0, 0, 0, 0, 0,
+ 0, 0, 0, 46, 7, 1, 10, 1, 0, 0, 0, 1, 20, 20, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 34, 9, 0, 0, 20, 20,
+ 1, 20, 20, 0, 0, 0, 0, 0, 0, 0, 26, 21, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 47, 48, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 13, 13, 13, 13, 13, 14, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 15, 16, 17, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 20, 20, 20, 20, 20, 20,
+ 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 20, 33,
+ 34, 35, 34, 34, 36, 37, 20, 20, 20, 20, 20, 20, 38, 20, 39, 40,
+ 41, 41, 41, 41, 41, 42, 43, 44, 20, 20, 20, 20, 20, 20, 20, 45,
+ 46, 20, 20, 47, 20, 20, 20, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 20, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 45, 0, 0, 1,
- 2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
- 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
- 32, 32, 33, 34, 35, 36, 37, 37, 37, 37, 37, 38, 39, 40, 41, 42,
- 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 2, 2, 53, 54, 55, 56,
- 57, 58, 59, 59, 59, 59, 60, 59, 59, 59, 59, 59, 59, 59, 61, 61,
- 59, 59, 59, 59, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
- 74, 75, 76, 77, 78, 59, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 79, 70, 70, 70, 70, 80, 80,
- 80, 80, 80, 80, 80, 80, 80, 81, 82, 82, 83, 84, 85, 86, 87, 88,
- 89, 90, 91, 92, 93, 94, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
- 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
- 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 70, 70, 97, 98, 99,100,101,101,102,103,104,105,106,107,108,109,
- 110,111, 96,112,113,114,115,116,117,118,119,119,120,121,122,123,
- 124,125,126,127,128,129,130,131,132, 96,133,134,135,136,137,138,
- 139,140,141,142,143, 96,144,145, 96,146,147,148,149, 96,150,151,
- 152,153,154,155,156, 96,157,158,159,160, 96,161,162,163,164,164,
- 164,164,164,164,164,165,166,164,167, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,168,169,169,
- 169,169,169,169,169,169,170, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96,171,171,171,171,172, 96, 96, 96,173,173,
- 173,173,174,175,176,177, 96, 96, 96, 96,178,179,180,181,182,182,
- 182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,
- 182,182,182,182,182,182,182,182,182,182,182,182,182,183,182,182,
- 182,182,182,182,184,184,184,185,186, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,187,188,189,
- 190,191,191,192, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96,193,194, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,195,196, 59,197,
- 198,199,200,201,202, 96,203,204,205, 59, 59,206, 59,207,208,208,
- 208,208,208,209, 96, 96, 96, 96, 96, 96, 96, 96,210, 96,211,212,
- 213, 96, 96,214, 96, 96, 96,215, 96, 96, 96, 96, 96,216,217,218,
- 219, 96, 96, 96, 96, 96,220,221,222, 96,223,224, 96, 96,225,226,
- 59,227,228, 96, 59, 59, 59, 59, 59, 59, 59,229,230,231,232,233,
- 59, 59,234,235, 59,236, 96, 96, 96, 96, 96, 96, 96, 96, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,237, 70, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,238, 70,239, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,240, 70, 70, 70, 70,
- 70, 70, 70, 70, 70,241, 70, 70, 70, 70,242, 96, 96, 96, 70, 70,
- 70, 70,243, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 70, 70,
- 70, 70, 70, 70,244, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70, 70,245, 96, 96, 96, 96, 96, 96, 96, 96,246, 96,
- 247,248, 0, 1, 2, 2, 0, 1, 2, 2, 2, 3, 4, 5, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 19, 19, 19, 19, 19, 19, 19, 19, 19, 0, 0, 0, 0, 0, 0, 0,
- 19, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 19, 19, 0, 19, 0,
- 0, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 0, 0, 0, 0, 0,
- 26, 26, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9,
- 9, 9, 0, 9, 9, 9, 2, 2, 9, 9, 9, 9, 0, 9, 2, 2,
- 2, 2, 9, 0, 9, 0, 9, 9, 9, 2, 9, 2, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9,
- 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 6, 6,
- 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 1, 6, 2, 4,
- 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
- 4, 4, 4, 4, 4, 2, 4, 4, 4, 2, 2, 4, 4, 4, 2, 14,
- 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 2, 2,
- 2, 2, 2, 2, 2, 2, 14, 14, 14, 2, 2, 2, 2, 14, 14, 14,
- 14, 14, 14, 2, 2, 2, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3,
- 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 3,
- 3, 3, 3, 3, 3, 3, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
- 37, 37, 37, 37, 2, 37, 37, 37, 37, 2, 2, 37, 37, 37, 38, 38,
- 38, 38, 38, 38, 38, 38, 38, 38, 2, 2, 2, 2, 2, 2, 64, 64,
- 64, 64, 64, 64, 64, 64, 64, 64, 64, 2, 2, 64, 64, 64, 90, 90,
- 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 2, 2, 90, 90,
- 90, 90, 90, 90, 90, 2, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
- 95, 95, 2, 2, 95, 2, 37, 37, 37, 2, 2, 2, 2, 2, 3, 3,
- 3, 3, 3, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 3, 3,
- 0, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1,
- 1, 1, 1, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 5, 5,
- 5, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 2, 2, 5, 5, 2,
- 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 2,
- 5, 5, 5, 5, 5, 5, 5, 2, 5, 2, 2, 2, 5, 5, 5, 5,
- 2, 2, 5, 5, 5, 5, 5, 2, 2, 5, 5, 5, 5, 2, 2, 2,
- 2, 2, 2, 2, 2, 5, 2, 2, 2, 2, 5, 5, 2, 5, 5, 5,
- 5, 5, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 2, 2, 11,
- 11, 11, 2, 11, 11, 11, 11, 11, 11, 2, 2, 2, 2, 11, 11, 2,
- 2, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 2,
- 11, 11, 11, 11, 11, 11, 11, 2, 11, 11, 2, 11, 11, 2, 11, 11,
- 2, 2, 11, 2, 11, 11, 11, 2, 2, 11, 11, 11, 2, 2, 2, 11,
- 2, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11, 2, 11, 2, 2, 2,
- 2, 2, 2, 2, 11, 11, 11, 11, 11, 11, 11, 11, 11, 2, 2, 10,
- 10, 10, 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 10, 10, 10,
- 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2,
- 10, 10, 10, 10, 10, 10, 10, 2, 10, 10, 2, 10, 10, 10, 10, 10,
- 2, 2, 10, 10, 10, 10, 10, 10, 2, 10, 10, 10, 2, 2, 10, 2,
- 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 2, 2, 10, 10, 10, 10,
- 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 2, 21,
- 21, 21, 2, 21, 21, 21, 21, 21, 21, 21, 21, 2, 2, 21, 21, 2,
- 2, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 2,
- 21, 21, 21, 21, 21, 21, 21, 2, 21, 21, 2, 21, 21, 21, 21, 21,
- 2, 2, 21, 21, 21, 21, 21, 2, 2, 21, 21, 21, 2, 2, 2, 2,
- 2, 2, 2, 21, 21, 21, 2, 2, 2, 2, 21, 21, 2, 21, 21, 21,
- 21, 21, 2, 2, 21, 21, 2, 2, 22, 22, 2, 22, 22, 22, 22, 22,
- 22, 2, 2, 2, 22, 22, 22, 2, 22, 22, 22, 22, 2, 2, 2, 22,
- 22, 2, 22, 2, 22, 22, 2, 2, 2, 22, 22, 2, 2, 2, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 2, 2, 2, 2, 22, 22, 22, 2,
- 2, 2, 2, 2, 2, 22, 2, 2, 2, 2, 2, 2, 22, 22, 22, 22,
- 22, 2, 2, 2, 2, 2, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
- 23, 23, 23, 2, 23, 23, 23, 2, 23, 23, 23, 23, 23, 23, 23, 23,
- 2, 2, 23, 23, 23, 23, 23, 2, 23, 23, 23, 23, 2, 2, 2, 2,
- 2, 2, 2, 23, 23, 2, 23, 23, 23, 2, 2, 23, 2, 2, 23, 23,
- 23, 23, 2, 2, 23, 23, 2, 2, 2, 2, 2, 2, 2, 23, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 16, 16, 16, 2,
- 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 16, 16, 16, 16, 16,
- 2, 2, 16, 16, 16, 16, 16, 2, 16, 16, 16, 16, 2, 2, 2, 2,
- 2, 2, 2, 16, 16, 2, 16, 16, 16, 16, 2, 2, 16, 16, 2, 16,
- 16, 16, 2, 2, 2, 2, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
- 20, 20, 20, 2, 20, 20, 20, 2, 20, 20, 20, 20, 20, 20, 2, 2,
- 2, 2, 20, 20, 20, 20, 20, 20, 20, 20, 2, 2, 20, 20, 2, 36,
- 36, 36, 2, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
- 36, 36, 36, 36, 36, 2, 2, 2, 36, 36, 36, 36, 36, 36, 36, 36,
- 2, 36, 36, 36, 36, 36, 36, 36, 36, 36, 2, 36, 2, 2, 2, 2,
- 36, 2, 2, 2, 2, 36, 36, 36, 36, 36, 36, 2, 36, 2, 2, 2,
- 2, 2, 2, 2, 36, 36, 2, 2, 36, 36, 36, 2, 2, 2, 2, 24,
- 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 2, 2, 2, 2, 0, 24, 24, 24, 24, 2, 2, 2, 2, 2, 18,
- 18, 2, 18, 2, 18, 18, 18, 18, 18, 2, 18, 18, 18, 18, 18, 18,
- 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 2, 18, 2, 18, 18, 18,
- 18, 18, 18, 18, 2, 2, 18, 18, 18, 18, 18, 2, 18, 2, 18, 18,
- 18, 18, 18, 18, 18, 2, 18, 18, 2, 2, 18, 18, 18, 18, 25, 25,
- 25, 25, 25, 25, 25, 25, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25,
- 25, 25, 25, 2, 2, 2, 25, 25, 25, 25, 25, 2, 25, 25, 25, 25,
- 25, 25, 25, 0, 0, 0, 0, 25, 25, 2, 2, 2, 2, 2, 33, 33,
- 33, 33, 33, 33, 33, 33, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 2, 8, 2, 2, 2, 2, 2, 8, 2, 2, 8, 8,
- 8, 0, 8, 8, 8, 8, 12, 12, 12, 12, 12, 12, 12, 12, 30, 30,
- 30, 30, 30, 30, 30, 30, 30, 2, 30, 30, 30, 30, 2, 2, 30, 30,
- 30, 30, 30, 30, 30, 2, 30, 30, 30, 2, 2, 30, 30, 30, 30, 30,
- 30, 30, 30, 2, 2, 2, 30, 30, 2, 2, 2, 2, 2, 2, 29, 29,
- 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 2, 2, 28, 28,
- 28, 28, 28, 28, 28, 28, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 2, 2, 2, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35,
- 35, 0, 0, 0, 35, 35, 35, 2, 2, 2, 2, 2, 2, 2, 45, 45,
- 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 45, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
- 44, 44, 44, 0, 0, 2, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
- 43, 43, 2, 2, 2, 2, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
- 46, 46, 46, 2, 46, 46, 46, 2, 46, 46, 2, 2, 2, 2, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 2, 2, 31, 31,
- 2, 2, 2, 2, 2, 2, 32, 32, 0, 0, 32, 0, 32, 32, 32, 32,
- 32, 32, 32, 32, 32, 32, 32, 32, 2, 2, 2, 2, 2, 2, 32, 2,
- 2, 2, 2, 2, 2, 2, 32, 32, 32, 2, 2, 2, 2, 2, 28, 28,
- 28, 28, 28, 28, 2, 2, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
- 48, 48, 48, 48, 48, 2, 48, 48, 48, 48, 2, 2, 2, 2, 48, 2,
- 2, 2, 48, 48, 48, 48, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
- 52, 52, 52, 52, 2, 2, 52, 52, 52, 52, 52, 2, 2, 2, 58, 58,
- 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 2, 2, 2, 2, 58, 58,
- 2, 2, 2, 2, 2, 2, 58, 58, 58, 2, 2, 2, 58, 58, 54, 54,
- 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 2, 2, 54, 54, 91, 91,
- 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 2, 91, 91,
- 91, 91, 91, 2, 2, 91, 91, 91, 2, 2, 2, 2, 2, 2, 91, 91,
- 91, 91, 91, 91, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 62, 62,
- 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 2, 2, 2, 62, 62,
- 62, 62, 62, 62, 62, 2, 76, 76, 76, 76, 76, 76, 76, 76, 93, 93,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 60, 13, 13,
+ 13, 61, 62, 13, 13, 13, 13, 63, 13, 13, 13, 13, 13, 13, 64, 65,
+ 20, 20, 66, 20, 13, 13, 13, 13, 67, 13, 13, 13, 68, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 19, 19,
+ 19, 19, 19, 19, 19, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 19,
+ 19, 19, 19, 0, 0, 0, 0, 0, 26, 26, 0, 0, 0, 0, 1, 1,
+ 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 0, 9, 9, 9, 2, 2,
+ 9, 9, 9, 9, 0, 9, 2, 2, 2, 2, 9, 0, 9, 0, 9, 9,
+ 9, 2, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 2, 9, 9, 9, 9, 9, 9, 9, 55, 55, 55, 55, 55, 55, 55, 55,
+ 55, 55, 55, 55, 55, 55, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 1, 1, 6, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4,
+ 4, 2, 2, 4, 4, 4, 2, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 2, 2, 2, 2, 2, 2, 2, 2, 14, 14,
+ 14, 2, 2, 2, 2, 14, 14, 14, 14, 14, 14, 2, 2, 2, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 0, 0, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 37, 37,
+ 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 2, 37, 37, 37,
+ 37, 2, 2, 37, 37, 37, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 2, 2, 2, 2, 2, 2, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 2, 2, 64, 64, 64, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 2, 2, 90, 90, 90, 90, 90, 90, 90, 2, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 2, 2, 95, 2, 37, 37,
+ 37, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3,
+ 2, 2, 2, 2, 2, 3, 3, 3, 0, 3, 3, 3, 3, 3, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 7, 7, 7, 7, 7,
+ 7, 7, 0, 0, 7, 7, 5, 5, 5, 5, 2, 5, 5, 5, 5, 5,
+ 5, 5, 5, 2, 2, 5, 5, 2, 2, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 2, 5, 5, 5, 5, 5, 5, 5, 2,
+ 5, 2, 2, 2, 5, 5, 5, 5, 2, 2, 5, 5, 5, 5, 5, 2,
+ 2, 5, 5, 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 5, 2, 2,
+ 2, 2, 5, 5, 2, 5, 5, 5, 5, 5, 2, 2, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 2, 2, 11, 11, 11, 2, 11, 11, 11, 11, 11,
+ 11, 2, 2, 2, 2, 11, 11, 2, 2, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 2, 11, 11, 11, 11, 11, 11, 11, 2,
+ 11, 11, 2, 11, 11, 2, 11, 11, 2, 2, 11, 2, 11, 11, 11, 2,
+ 2, 11, 11, 11, 2, 2, 2, 11, 2, 2, 2, 2, 2, 2, 2, 11,
+ 11, 11, 11, 2, 11, 2, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 2, 2, 10, 10, 10, 2, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 2, 10, 10, 10, 2, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 2, 10, 10, 10, 10, 10, 10, 10, 2,
+ 10, 10, 2, 10, 10, 10, 10, 10, 2, 2, 10, 10, 10, 10, 10, 10,
+ 2, 10, 10, 10, 2, 2, 10, 2, 2, 2, 2, 2, 2, 2, 10, 10,
+ 10, 10, 2, 2, 10, 10, 10, 10, 2, 2, 2, 2, 2, 2, 2, 10,
+ 10, 10, 10, 10, 10, 10, 2, 21, 21, 21, 2, 21, 21, 21, 21, 21,
+ 21, 21, 21, 2, 2, 21, 21, 2, 2, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 2, 21, 21, 21, 21, 21, 21, 21, 2,
+ 21, 21, 2, 21, 21, 21, 21, 21, 2, 2, 21, 21, 21, 21, 21, 2,
+ 2, 21, 21, 21, 2, 2, 2, 2, 2, 2, 2, 21, 21, 21, 2, 2,
+ 2, 2, 21, 21, 2, 21, 21, 21, 21, 21, 2, 2, 21, 21, 2, 2,
+ 22, 22, 2, 22, 22, 22, 22, 22, 22, 2, 2, 2, 22, 22, 22, 2,
+ 22, 22, 22, 22, 2, 2, 2, 22, 22, 2, 22, 2, 22, 22, 2, 2,
+ 2, 22, 22, 2, 2, 2, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 2, 2, 2, 2, 22, 22, 22, 2, 2, 2, 2, 2, 2, 22, 2, 2,
+ 2, 2, 2, 2, 22, 22, 22, 22, 22, 2, 2, 2, 2, 2, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 2, 23, 23, 23, 2,
+ 23, 23, 23, 23, 23, 23, 23, 23, 2, 2, 23, 23, 23, 23, 23, 2,
+ 23, 23, 23, 23, 2, 2, 2, 2, 2, 2, 2, 23, 23, 2, 23, 23,
+ 23, 2, 2, 23, 2, 2, 23, 23, 23, 23, 2, 2, 23, 23, 2, 2,
+ 2, 2, 2, 2, 2, 23, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 2, 16, 16, 16, 2, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 2, 16, 16, 16, 16, 16, 2, 2, 16, 16, 16, 16, 16, 2,
+ 16, 16, 16, 16, 2, 2, 2, 2, 2, 2, 2, 16, 16, 2, 16, 16,
+ 16, 16, 2, 2, 16, 16, 2, 16, 16, 16, 2, 2, 2, 2, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 2, 20, 20, 20, 2,
+ 20, 20, 20, 20, 20, 20, 2, 2, 2, 2, 20, 20, 20, 20, 20, 20,
+ 20, 20, 2, 2, 20, 20, 2, 36, 36, 36, 2, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 2, 2, 2,
+ 36, 36, 36, 36, 36, 36, 36, 36, 2, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 2, 36, 2, 2, 2, 2, 36, 2, 2, 2, 2, 36, 36, 36,
+ 36, 36, 36, 2, 36, 2, 2, 2, 2, 2, 2, 2, 36, 36, 2, 2,
+ 36, 36, 36, 2, 2, 2, 2, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 2, 2, 2, 2, 0, 24, 24,
+ 24, 24, 2, 2, 2, 2, 2, 18, 18, 2, 18, 2, 18, 18, 18, 18,
+ 18, 2, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 2, 18, 2, 18, 18, 18, 18, 18, 18, 18, 2, 2, 18, 18,
+ 18, 18, 18, 2, 18, 2, 18, 18, 18, 18, 18, 18, 18, 2, 18, 18,
+ 2, 2, 18, 18, 18, 18, 25, 25, 25, 25, 25, 25, 25, 25, 2, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 2, 2, 2, 25, 25,
+ 25, 25, 25, 2, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 25,
+ 25, 2, 2, 2, 2, 2, 33, 33, 33, 33, 33, 33, 33, 33, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 8, 2, 2,
+ 2, 2, 2, 8, 2, 2, 8, 8, 8, 0, 8, 8, 8, 8, 12, 12,
+ 12, 12, 12, 12, 12, 12, 30, 30, 30, 30, 30, 30, 30, 30, 30, 2,
+ 30, 30, 30, 30, 2, 2, 30, 30, 30, 30, 30, 30, 30, 2, 30, 30,
+ 30, 2, 2, 30, 30, 30, 30, 30, 30, 30, 30, 2, 2, 2, 30, 30,
+ 2, 2, 2, 2, 2, 2, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 2, 2, 28, 28, 28, 28, 28, 28, 28, 28, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 2, 2, 2, 35, 35,
+ 35, 35, 35, 35, 35, 35, 35, 35, 35, 0, 0, 0, 35, 35, 35, 2,
+ 2, 2, 2, 2, 2, 2, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 2, 2, 2, 2, 2, 2, 2, 2, 2, 45, 44, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 0, 0, 2, 43, 43,
+ 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 2, 2, 2, 2, 46, 46,
+ 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 2, 46, 46, 46, 2,
+ 46, 46, 2, 2, 2, 2, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 2, 2, 31, 31, 2, 2, 2, 2, 2, 2, 32, 32,
+ 0, 0, 32, 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+ 2, 2, 2, 2, 2, 2, 32, 2, 2, 2, 2, 2, 2, 2, 32, 32,
+ 32, 2, 2, 2, 2, 2, 28, 28, 28, 28, 28, 28, 2, 2, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 2, 48, 48,
+ 48, 48, 2, 2, 2, 2, 48, 2, 2, 2, 48, 48, 48, 48, 52, 52,
+ 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 2, 2, 52, 52,
+ 52, 52, 52, 2, 2, 2, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+ 58, 58, 2, 2, 2, 2, 58, 58, 2, 2, 2, 2, 2, 2, 58, 58,
+ 58, 2, 2, 2, 58, 58, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 2, 2, 54, 54, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 2, 91, 91, 91, 91, 91, 2, 2, 91, 91, 91,
+ 2, 2, 2, 2, 2, 2, 91, 91, 91, 91, 91, 91, 2, 2, 1, 1,
+ 1, 1, 1, 1, 1, 2, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 2, 62, 62, 76, 76, 76, 76, 76, 76, 76, 76, 93, 93,
93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 2, 2, 2, 2, 2, 2,
2, 2, 93, 93, 93, 93, 70, 70, 70, 70, 70, 70, 70, 70, 2, 2,
2, 70, 70, 70, 70, 70, 70, 70, 2, 2, 2, 70, 70, 70, 73, 73,
- 73, 73, 73, 73, 73, 73, 6, 2, 2, 2, 2, 2, 2, 2, 8, 8,
+ 73, 73, 73, 73, 73, 73, 6, 6, 6, 2, 2, 2, 2, 2, 8, 8,
8, 2, 2, 8, 8, 8, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 1, 1, 0, 2, 2, 2, 2, 2, 19, 19,
@@ -1896,31 +1872,30 @@ _hb_ucd_u8[17884] =
2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 0, 0,
0, 0, 0, 0, 9, 0, 0, 0, 19, 19, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 19, 0, 19, 0, 0, 0, 2, 2, 2, 2, 0, 0,
- 0, 2, 2, 2, 2, 2, 27, 27, 27, 27, 27, 27, 27, 27, 0, 0,
- 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 56, 56,
- 56, 56, 56, 56, 56, 56, 55, 55, 55, 55, 2, 2, 2, 2, 2, 55,
- 55, 55, 55, 55, 55, 55, 61, 61, 61, 61, 61, 61, 61, 61, 2, 2,
- 2, 2, 2, 2, 2, 61, 61, 2, 2, 2, 2, 2, 2, 2, 0, 0,
- 0, 0, 0, 0, 2, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 2, 2, 2, 2, 13, 13,
- 13, 13, 13, 13, 2, 2, 0, 0, 0, 0, 0, 13, 0, 13, 0, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1, 1, 12, 12, 13, 13,
- 13, 13, 0, 0, 0, 0, 2, 15, 15, 15, 15, 15, 15, 15, 15, 15,
- 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 2, 1,
- 1, 0, 0, 15, 15, 15, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 17, 17, 17, 2, 2,
- 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 12,
- 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 2, 0, 0,
- 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 12, 12,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 27, 27,
+ 27, 27, 27, 27, 27, 27, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 56, 56, 56, 56, 56, 56, 56, 56, 55, 55,
+ 55, 55, 2, 2, 2, 2, 2, 55, 55, 55, 55, 55, 55, 55, 61, 61,
+ 61, 61, 61, 61, 61, 61, 2, 2, 2, 2, 2, 2, 2, 61, 61, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 2, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 2, 2, 2, 2, 13, 13, 13, 13, 13, 13, 2, 2, 0, 0,
+ 0, 0, 0, 13, 0, 13, 0, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 1, 1, 1, 1, 12, 12, 13, 13, 13, 13, 0, 0, 0, 0, 2, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 2, 2, 1, 1, 0, 0, 15, 15, 15, 0, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 0, 0, 17, 17, 17, 2, 2, 2, 2, 2, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 2, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 2, 2, 2, 2, 2, 2, 2, 2, 0, 12, 12,
12, 12, 12, 12, 12, 0, 17, 17, 17, 17, 17, 17, 17, 0, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 2, 2, 2, 39, 39,
39, 39, 39, 39, 39, 2, 86, 86, 86, 86, 86, 86, 86, 86, 77, 77,
77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 2, 2, 2, 2, 79, 79,
79, 79, 79, 79, 79, 79, 0, 0, 19, 19, 19, 19, 19, 19, 0, 0,
- 0, 19, 19, 19, 19, 19, 19, 19, 19, 2, 2, 2, 2, 2, 19, 19,
- 2, 19, 2, 19, 19, 19, 19, 19, 2, 2, 2, 2, 2, 2, 2, 2,
- 19, 19, 19, 19, 19, 19, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
- 60, 60, 60, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 65, 65,
+ 0, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 2, 2, 19, 19,
+ 2, 19, 2, 19, 19, 19, 2, 2, 19, 19, 19, 19, 19, 19, 60, 60,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 2, 2, 2, 65, 65,
65, 65, 65, 65, 65, 65, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75,
75, 75, 75, 75, 2, 2, 2, 2, 2, 2, 2, 2, 75, 75, 75, 75,
2, 2, 2, 2, 2, 2, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
@@ -1943,36 +1918,38 @@ _hb_ucd_u8[17884] =
2, 14, 14, 2, 14, 14, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 2, 2,
3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 1, 1,
- 1, 1, 1, 1, 6, 6, 0, 0, 0, 2, 0, 0, 0, 0, 3, 3,
- 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 0, 2, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17,
- 17, 17, 17, 17, 0, 0, 2, 2, 12, 12, 12, 12, 12, 12, 2, 2,
- 12, 12, 12, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 49, 49,
- 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 2, 49, 49, 49, 49, 49,
- 49, 49, 49, 49, 49, 2, 49, 49, 49, 2, 49, 49, 2, 49, 49, 49,
- 49, 49, 49, 49, 2, 2, 49, 49, 49, 2, 2, 2, 2, 2, 0, 0,
- 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0,
- 0, 0, 0, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 0, 0,
- 0, 0, 0, 1, 2, 2, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
- 71, 71, 71, 2, 2, 2, 67, 67, 67, 67, 67, 67, 67, 67, 67, 2,
- 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 42, 42,
- 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 42, 42, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
- 41, 2, 2, 2, 2, 2,118,118,118,118,118,118,118,118,118,118,
- 118, 2, 2, 2, 2, 2, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
- 53, 53, 53, 53, 2, 53, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
- 59, 59, 2, 2, 2, 2, 59, 59, 59, 59, 59, 59, 2, 2, 40, 40,
- 40, 40, 40, 40, 40, 40, 51, 51, 51, 51, 51, 51, 51, 51, 50, 50,
- 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 2, 2, 50, 50,
- 2, 2, 2, 2, 2, 2,135,135,135,135,135,135,135,135,135,135,
- 135,135, 2, 2, 2, 2,106,106,106,106,106,106,106,106,104,104,
- 104,104,104,104,104,104,104,104,104,104, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2,104,161,161,161,161,161,161,161,161,161,161,
- 161, 2,161,161,161,161,161,161,161, 2,161,161, 2,161,161,161,
- 2,161,161,161,161,161,161,161, 2,161,161, 2, 2, 2,110,110,
- 110,110,110,110,110,110,110,110,110,110,110,110,110, 2,110,110,
- 110,110,110,110, 2, 2, 19, 19, 19, 19, 19, 19, 2, 19, 19, 2,
- 19, 19, 19, 19, 19, 19, 47, 47, 47, 47, 47, 47, 2, 2, 47, 2,
+ 1, 1, 1, 1, 6, 6, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 2, 2, 2, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3,
+ 3, 3, 3, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 2, 2,
+ 12, 12, 12, 12, 12, 12, 2, 2, 12, 12, 12, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 2, 2, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 2, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 2, 49, 49,
+ 49, 2, 49, 49, 2, 49, 49, 49, 49, 49, 49, 49, 2, 2, 49, 49,
+ 49, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0,
+ 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 9, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 2, 2, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 2, 2, 2, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 2, 2, 2, 2, 2, 2, 2, 1, 0,
+ 0, 0, 0, 0, 0, 0, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+ 42, 42, 2, 2, 2, 2, 2, 2, 2, 2, 2, 42, 42, 42, 41, 41,
+ 41, 41, 41, 41, 41, 41, 41, 41, 41, 2, 2, 2, 2, 2,118,118,
+ 118,118,118,118,118,118,118,118,118, 2, 2, 2, 2, 2, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 2, 53, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 2, 2, 2, 2, 59, 59,
+ 59, 59, 59, 59, 2, 2, 40, 40, 40, 40, 40, 40, 40, 40, 51, 51,
+ 51, 51, 51, 51, 51, 51, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 2, 2, 50, 50, 2, 2, 2, 2, 2, 2,135,135,
+ 135,135,135,135,135,135,135,135,135,135, 2, 2, 2, 2,106,106,
+ 106,106,106,106,106,106,104,104,104,104,104,104,104,104,104,104,
+ 104,104, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,104,161,161,
+ 161,161,161,161,161,161,161,161,161, 2,161,161,161,161,161,161,
+ 161, 2,161,161, 2,161,161,161, 2,161,161,161,161,161,161,161,
+ 2,161,161, 2, 2, 2,170,170,170,170,170,170,170,170,170,170,
+ 170,170, 2, 2, 2, 2,110,110,110,110,110,110,110,110,110,110,
+ 110,110,110,110,110, 2,110,110,110,110,110,110, 2, 2, 19, 19,
+ 19, 19, 19, 19, 2, 19, 19, 2, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 2, 2, 2, 2, 2, 47, 47, 47, 47, 47, 47, 2, 2, 47, 2,
47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
47, 47, 47, 47, 2, 47, 47, 2, 2, 2, 47, 2, 2, 47, 81, 81,
81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 2, 81,120,120,
@@ -1998,122 +1975,135 @@ _hb_ucd_u8[17884] =
122,122,122,122,122,122, 89, 89, 89, 89, 89, 89, 89, 89, 89, 2,
2, 2, 2, 2, 2, 2,130,130,130,130,130,130,130,130,130,130,
130, 2, 2, 2, 2, 2, 2, 2,130,130,130,130,130,130,144,144,
- 144,144,144,144,144,144,144,144, 2, 2, 2, 2, 2, 2,156,156,
+ 144,144,144,144,144,144,144,144, 2, 2, 2, 2, 2, 2,165,165,
+ 165,165,165,165,165,165,165,165,165,165,165,165, 2, 2, 2,165,
+ 165,165,165,165,165,165, 2, 2, 2, 2, 2, 2,165,165,156,156,
156,156,156,156,156,156,156,156, 2,156,156,156, 2, 2,156,156,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,147,147,
- 147,147,147,147,147,147,148,148,148,148,148,148,148,148,148,148,
- 2, 2, 2, 2, 2, 2,158,158,158,158,158,158,158,158,158,158,
- 2, 2, 2, 2, 2, 2,153,153,153,153,153,153,153,153,153,153,
- 153,153, 2, 2, 2, 2,149,149,149,149,149,149,149,149,149,149,
- 149,149,149,149,149, 2, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94,
- 94, 94, 94, 94, 2, 2, 2, 2, 94, 94, 94, 94, 94, 94, 2, 2,
- 2, 2, 2, 2, 2, 94, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
- 85, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 85, 2, 2,101,101,
- 101,101,101,101,101,101,101, 2, 2, 2, 2, 2, 2, 2,101,101,
- 2, 2, 2, 2, 2, 2, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 2, 96, 96,111,111,111,111,111,111,111,111,111,111,
- 111,111,111,111,111, 2,100,100,100,100,100,100,100,100, 2, 36,
- 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 2, 2, 2,108,108,
- 108,108,108,108,108,108,108,108, 2,108,108,108,108,108,108,108,
- 2, 2, 2, 2, 2, 2,129,129,129,129,129,129,129, 2,129, 2,
- 129,129,129,129, 2,129,129,129,129,129,129,129,129,129,129,129,
- 129,129,129,129, 2,129,129,129, 2, 2, 2, 2, 2, 2,109,109,
- 109,109,109,109,109,109,109,109,109, 2, 2, 2, 2, 2,109,109,
- 2, 2, 2, 2, 2, 2,107,107,107,107, 2,107,107,107,107,107,
- 107,107,107, 2, 2,107,107, 2, 2,107,107,107,107,107,107,107,
- 107,107,107,107,107,107,107, 2,107,107,107,107,107,107,107, 2,
- 107,107, 2,107,107,107,107,107, 2, 1,107,107,107,107,107, 2,
- 2,107,107,107, 2, 2,107, 2, 2, 2, 2, 2, 2,107, 2, 2,
- 2, 2, 2,107,107,107,107,107,107,107, 2, 2,107,107,107,107,
- 107,107,107, 2, 2, 2,137,137,137,137,137,137,137,137,137,137,
+ 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2,
+ 2, 2, 3, 3, 3, 3,147,147,147,147,147,147,147,147,148,148,
+ 148,148,148,148,148,148,148,148, 2, 2, 2, 2, 2, 2,158,158,
+ 158,158,158,158,158,158,158,158, 2, 2, 2, 2, 2, 2,153,153,
+ 153,153,153,153,153,153,153,153,153,153, 2, 2, 2, 2,149,149,
+ 149,149,149,149,149,149,149,149,149,149,149,149,149, 2, 94, 94,
+ 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 2, 2, 2, 2,
+ 94, 94, 94, 94, 94, 94, 2, 2, 2, 2, 2, 2, 2, 94, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 85, 2, 2,101,101,101,101,101,101,101,101,101, 2,
+ 2, 2, 2, 2, 2, 2,101,101, 2, 2, 2, 2, 2, 2, 96, 96,
+ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 2, 96, 96,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111, 2,100,100,
+ 100,100,100,100,100,100, 2, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 2, 2, 2,108,108,108,108,108,108,108,108,108,108,
+ 2,108,108,108,108,108,108,108, 2, 2, 2, 2, 2, 2,129,129,
+ 129,129,129,129,129, 2,129, 2,129,129,129,129, 2,129,129,129,
+ 129,129,129,129,129,129,129,129,129,129,129,129, 2,129,129,129,
+ 2, 2, 2, 2, 2, 2,109,109,109,109,109,109,109,109,109,109,
+ 109, 2, 2, 2, 2, 2,109,109, 2, 2, 2, 2, 2, 2,107,107,
+ 107,107, 2,107,107,107,107,107,107,107,107, 2, 2,107,107, 2,
+ 2,107,107,107,107,107,107,107,107,107,107,107,107,107,107, 2,
+ 107,107,107,107,107,107,107, 2,107,107, 2,107,107,107,107,107,
+ 2, 1,107,107,107,107,107, 2, 2,107,107,107, 2, 2,107, 2,
+ 2, 2, 2, 2, 2,107, 2, 2, 2, 2, 2,107,107,107,107,107,
+ 107,107, 2, 2,107,107,107,107,107,107,107, 2, 2, 2,171,171,
+ 171,171,171,171,171,171,171,171, 2,171, 2, 2,171, 2,171,171,
+ 171,171,171,171, 2,171,171, 2,171, 2, 2,171, 2,171,171,171,
+ 171, 2,171,171,171,171,171, 2, 2, 2, 2, 2, 2, 2, 2,171,
+ 171, 2, 2, 2, 2, 2,137,137,137,137,137,137,137,137,137,137,
137,137, 2,137,137,137,137,137, 2, 2, 2, 2, 2, 2,124,124,
124,124,124,124,124,124,124,124, 2, 2, 2, 2, 2, 2,123,123,
123,123,123,123,123,123,123,123,123,123,123,123, 2, 2,114,114,
114,114,114,114,114,114,114,114,114,114,114, 2, 2, 2,114,114,
2, 2, 2, 2, 2, 2, 32, 32, 32, 32, 32, 2, 2, 2,102,102,
- 102,102,102,102,102,102,102,102, 2, 2, 2, 2, 2, 2,126,126,
- 126,126,126,126,126,126,126,126,126, 2, 2,126,126,126,126,126,
- 126,126, 2, 2, 2, 2,126,126,126,126,126,126,126, 2,142,142,
- 142,142,142,142,142,142,142,142,142,142, 2, 2, 2, 2,125,125,
- 125,125,125,125,125,125,125,125,125, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2,125,154,154,154,154,154,154,154, 2, 2,154,
- 2, 2,154,154,154,154,154,154,154,154, 2,154,154, 2,154,154,
- 154,154,154,154,154,154,154,154,154,154,154,154, 2,154,154, 2,
- 2,154,154,154,154,154,154,154, 2, 2, 2, 2, 2, 2,150,150,
- 150,150,150,150,150,150, 2, 2,150,150,150,150,150,150,150,150,
- 150,150,150, 2, 2, 2,141,141,141,141,141,141,141,141,140,140,
- 140,140,140,140,140,140,140,140,140, 2, 2, 2, 2, 2,121,121,
- 121,121,121,121,121,121,121, 2, 2, 2, 2, 2, 2, 2, 7, 7,
- 2, 2, 2, 2, 2, 2,133,133,133,133,133,133,133,133,133, 2,
- 133,133,133,133,133,133,133,133,133,133,133,133,133, 2,133,133,
- 133,133,133,133, 2, 2,133,133,133,133,133, 2, 2, 2,134,134,
- 134,134,134,134,134,134, 2, 2,134,134,134,134,134,134, 2,134,
- 134,134,134,134,134,134,134,134,134,134,134,134,134, 2,138,138,
- 138,138,138,138,138, 2,138,138, 2,138,138,138,138,138,138,138,
- 138,138,138,138,138,138, 2, 2,138, 2,138,138, 2,138,138,138,
- 2, 2, 2, 2, 2, 2,143,143,143,143,143,143, 2,143,143, 2,
- 143,143,143,143,143,143,143,143,143,143,143,143,143,143,143,143,
- 143,143,143,143,143, 2,143,143, 2,143,143,143,143,143,143, 2,
- 2, 2, 2, 2, 2, 2,143,143, 2, 2, 2, 2, 2, 2,145,145,
- 145,145,145,145,145,145,145, 2, 2, 2, 2, 2, 2, 2,163,163,
- 163,163,163,163,163,163,163, 2,163,163,163,163,163,163,163,163,
- 163, 2, 2, 2,163,163,163,163, 2, 2, 2, 2, 2, 2, 86, 2,
- 2, 2, 2, 2, 2, 2, 22, 22, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 22, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
- 2, 2, 2, 2, 2, 2, 63, 63, 63, 63, 63, 63, 63, 2, 63, 63,
- 63, 63, 63, 2, 2, 2, 63, 63, 63, 63, 2, 2, 2, 2,157,157,
- 157,157,157,157,157,157,157,157,157, 2, 2, 2, 2, 2, 80, 80,
- 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 2, 2,127,127,
- 127,127,127,127,127,127,127,127,127,127,127,127,127, 2, 79, 2,
+ 102,102,102,102,102,102,102,102, 2, 2, 2, 2, 2, 2, 33, 33,
+ 33, 33, 2, 2, 2, 2,126,126,126,126,126,126,126,126,126,126,
+ 126, 2, 2,126,126,126,126,126,126,126, 2, 2, 2, 2,126,126,
+ 126,126,126,126,126, 2,142,142,142,142,142,142,142,142,142,142,
+ 142,142, 2, 2, 2, 2,125,125,125,125,125,125,125,125,125,125,
+ 125, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,125,154,154,
+ 154,154,154,154,154, 2, 2,154, 2, 2,154,154,154,154,154,154,
+ 154,154, 2,154,154, 2,154,154,154,154,154,154,154,154,154,154,
+ 154,154,154,154, 2,154,154, 2, 2,154,154,154,154,154,154,154,
+ 2, 2, 2, 2, 2, 2,150,150,150,150,150,150,150,150, 2, 2,
+ 150,150,150,150,150,150,150,150,150,150,150, 2, 2, 2,141,141,
+ 141,141,141,141,141,141,140,140,140,140,140,140,140,140,140,140,
+ 140, 2, 2, 2, 2, 2,121,121,121,121,121,121,121,121,121, 2,
+ 2, 2, 2, 2, 2, 2, 7, 7, 2, 2, 2, 2, 2, 2,169,169,
+ 169,169,169,169,169,169,169,169, 2, 2, 2, 2, 2, 2,133,133,
+ 133,133,133,133,133,133,133, 2,133,133,133,133,133,133,133,133,
+ 133,133,133,133,133, 2,133,133,133,133,133,133, 2, 2,133,133,
+ 133,133,133, 2, 2, 2,134,134,134,134,134,134,134,134, 2, 2,
+ 134,134,134,134,134,134, 2,134,134,134,134,134,134,134,134,134,
+ 134,134,134,134,134, 2,138,138,138,138,138,138,138, 2,138,138,
+ 2,138,138,138,138,138,138,138,138,138,138,138,138,138, 2, 2,
+ 138, 2,138,138, 2,138,138,138, 2, 2, 2, 2, 2, 2,143,143,
+ 143,143,143,143, 2,143,143, 2,143,143,143,143,143,143,143,143,
+ 143,143,143,143,143,143,143,143,143,143,143,143,143, 2,143,143,
+ 2,143,143,143,143,143,143, 2, 2, 2, 2, 2, 2, 2,143,143,
+ 2, 2, 2, 2, 2, 2,145,145,145,145,145,145,145,145,145, 2,
+ 2, 2, 2, 2, 2, 2,163,163,163,163,163,163,163,163,163, 2,
+ 163,163,163,163,163,163,163,163,163, 2, 2, 2,163,163,163,163,
+ 163, 2, 2, 2, 2, 2, 86, 2, 2, 2, 2, 2, 2, 2, 22, 22,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 22, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63, 2, 2, 2, 2, 2, 2, 63, 63,
+ 63, 63, 63, 63, 63, 2, 63, 63, 63, 63, 63, 2, 2, 2, 63, 63,
+ 63, 63, 2, 2, 2, 2,157,157,157,157,157,157,157,157,157,157,
+ 157, 2, 2, 2, 2, 2, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 2, 2, 80, 80, 80, 2, 2, 2, 2, 2,127,127,
+ 127,127,127,127,127,127,127,127,127,127,127,127,127, 2,166,166,
+ 166,166,166,166,166,166,166,166, 2, 2, 2, 2, 2, 2, 79, 2,
2, 2, 2, 2, 2, 2,115,115,115,115,115,115,115,115,115,115,
115,115,115,115,115, 2,115,115, 2, 2, 2, 2,115,115,159,159,
159,159,159,159,159,159,159,159,159,159,159,159,159, 2,159,159,
2, 2, 2, 2, 2, 2,103,103,103,103,103,103,103,103,103,103,
103,103,103,103, 2, 2,119,119,119,119,119,119,119,119,119,119,
119,119,119,119, 2, 2,119,119, 2,119,119,119,119,119, 2, 2,
- 2, 2, 2,119,119,119,146,146,146,146,146,146,146,146,146,146,
+ 2, 2, 2,119,119,119,167,167,167,167,167,167,167,167,167,167,
+ 2, 2, 2, 2, 2, 2,146,146,146,146,146,146,146,146,146,146,
146, 2, 2, 2, 2, 2, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 2, 2, 2, 2, 99, 2, 2, 2, 2, 2, 2, 2, 99,136,139,
13, 13,155, 2, 2, 2,136,136,136,136,136,136,136,136,155,155,
- 155,155,155,155,155,155,155,155,155,155,155,155, 2, 2,136, 2,
- 2, 2, 2, 2, 2, 2, 17, 17, 17, 17, 2, 17, 17, 17, 17, 17,
- 17, 17, 2, 17, 17, 2, 17, 15, 15, 15, 15, 15, 15, 15, 17, 17,
- 17, 2, 2, 2, 2, 2, 2, 2, 15, 2, 2, 2, 2, 2, 15, 15,
- 15, 2, 2, 17, 2, 2, 2, 2, 2, 2, 17, 17, 17, 17,139,139,
- 139,139,139,139,139,139,139,139,139,139, 2, 2, 2, 2,105,105,
- 105,105,105,105,105,105,105,105,105, 2, 2, 2, 2, 2,105,105,
- 105,105,105, 2, 2, 2,105, 2, 2, 2, 2, 2, 2, 2,105,105,
- 2, 2,105,105,105,105, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 2, 2,
- 0, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0,
- 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
- 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0,
- 0, 0, 0, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2,
- 0, 0, 0, 0, 0, 0,131,131,131,131,131,131,131,131,131,131,
- 131,131, 2, 2, 2, 2, 2, 2, 2,131,131,131,131,131, 2,131,
- 131,131,131,131,131,131, 2, 2, 2, 2, 2, 19, 19, 19, 56, 56,
- 56, 56, 56, 56, 56, 2, 56, 2, 2, 56, 56, 56, 56, 56, 56, 56,
- 2, 56, 56, 2, 56, 56, 56, 56, 56, 2, 2, 2, 2, 2, 6, 6,
- 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,151,151,
- 151,151,151,151,151,151,151,151,151,151,151, 2, 2, 2,151,151,
- 151,151,151,151, 2, 2,151,151, 2, 2, 2, 2,151,151,160,160,
- 160,160,160,160,160,160,160,160,160,160,160,160,160, 2,152,152,
- 152,152,152,152,152,152,152,152, 2, 2, 2, 2, 2,152,164,164,
- 164,164,164,164,164,164,164,164, 2, 2, 2, 2, 2, 2, 30, 30,
- 30, 30, 2, 30, 30, 2,113,113,113,113,113,113,113,113,113,113,
- 113,113,113, 2, 2,113,113,113,113,113,113,113,113, 2,132,132,
- 132,132,132,132,132,132,132,132,132,132, 2, 2, 2, 2,132,132,
- 2, 2, 2, 2,132,132, 3, 3, 3, 3, 2, 3, 3, 3, 2, 3,
- 3, 2, 3, 2, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 2, 3, 3, 3, 3, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2,
- 3, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 2, 3,
- 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 2, 3, 2, 3, 3,
- 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 3,
- 3, 3, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 0, 0, 15, 0,
+ 155,155,155,155,155,155,155,155,155,155,155,155, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2,155,136, 2, 2, 2, 2, 2, 2, 2, 17, 17,
+ 17, 17, 2, 17, 17, 17, 17, 17, 17, 17, 2, 17, 17, 2, 17, 15,
+ 15, 15, 15, 15, 15, 15, 17, 17, 17, 2, 2, 2, 2, 2, 2, 2,
+ 15, 2, 2, 2, 2, 2, 15, 15, 15, 2, 2, 17, 2, 2, 2, 2,
+ 2, 2, 17, 17, 17, 17,139,139,139,139,139,139,139,139,139,139,
+ 139,139, 2, 2, 2, 2,105,105,105,105,105,105,105,105,105,105,
+ 105, 2, 2, 2, 2, 2,105,105,105,105,105, 2, 2, 2,105, 2,
+ 2, 2, 2, 2, 2, 2,105,105, 2, 2,105,105,105,105, 1, 1,
+ 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 0, 0, 2, 2, 0, 2, 2, 0, 0, 2, 2, 0,
+ 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 0, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0,131,131,
+ 131,131,131,131,131,131,131,131,131,131, 2, 2, 2, 2, 2, 2,
+ 2,131,131,131,131,131, 2,131,131,131,131,131,131,131, 2, 2,
+ 2, 2, 2, 19, 19, 19, 56, 56, 56, 56, 56, 56, 56, 2, 56, 2,
+ 2, 56, 56, 56, 56, 56, 56, 56, 2, 56, 56, 2, 56, 56, 56, 56,
+ 56, 2, 2, 2, 2, 2, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 6,151,151,151,151,151,151,151,151,151,151,
+ 151,151,151, 2, 2, 2,151,151,151,151,151,151, 2, 2,151,151,
+ 2, 2, 2, 2,151,151,160,160,160,160,160,160,160,160,160,160,
+ 160,160,160,160,160, 2,152,152,152,152,152,152,152,152,152,152,
+ 2, 2, 2, 2, 2,152,164,164,164,164,164,164,164,164,164,164,
+ 2, 2, 2, 2, 2, 2,168,168,168,168,168,168,168,168,168,168,
+ 168, 2, 2, 2, 2,168, 30, 30, 30, 30, 2, 30, 30, 2,113,113,
+ 113,113,113,113,113,113,113,113,113,113,113, 2, 2,113,113,113,
+ 113,113,113,113,113, 2,132,132,132,132,132,132,132,132,132,132,
+ 132,132, 2, 2, 2, 2,132,132, 2, 2, 2, 2,132,132, 3, 3,
+ 3, 3, 2, 3, 3, 3, 2, 3, 3, 2, 3, 2, 2, 3, 2, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, 3,
+ 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 3,
+ 2, 3, 2, 3, 3, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
+ 3, 3, 3, 2, 3, 2, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 15, 0,
0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2,
- 2, 0, 0, 0, 0, 0, 13, 2, 2, 2, 2, 2, 2, 2, 13, 13,
+ 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0,
+ 0, 0, 0, 2, 2, 0, 13, 2, 2, 2, 2, 2, 2, 2, 13, 13,
13, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 0, 1,
2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 10, 9, 11, 12, 13,
9, 9, 9, 14, 9, 9, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9,
@@ -2123,13 +2113,13 @@ _hb_ucd_u8[17884] =
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 16, 17, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 18, 19, 20, 9, 21, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 18, 9, 9, 9, 9, 9, 19, 20, 21, 9, 22, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 23, 9,
+ 9, 9, 9, 9, 24, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 25, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
@@ -2138,7 +2128,7 @@ _hb_ucd_u8[17884] =
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 23, 24, 0, 0, 0, 0,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 26, 27, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 0, 0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -2175,23 +2165,29 @@ _hb_ucd_u8[17884] =
132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,
148,149,150,151,152,153,154,155,156,157, 0, 0, 0,158,159,160,
161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,162,163, 0, 0, 0, 0, 0, 0, 0,164, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,162, 0,163, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,164,165, 0, 0, 0, 0, 0, 0, 0,166, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 167, 0, 0, 0,168,169, 0, 0,170, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,171, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,172, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,173,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,166, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,167, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,168,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,174, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0,169,170, 0, 0, 0, 0,171,172, 0, 0, 0,173,174,175,176,
- 177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,
- 193,194,195,196,197,198,199,200,201,202,203,204,205,206, 0, 0,
+ 0, 0,175, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,176,177, 0, 0, 0, 0,178,179, 0, 0, 0,180,181,182,183,
+ 184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,
+ 200,201,202,203,204,205,206,207,208,209,210,211,212,213, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
};
static const uint16_t
-_hb_ucd_u16[9344] =
+_hb_ucd_u16[10400] =
{
0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 7, 8, 9, 10, 11, 12,
13, 13, 13, 14, 15, 13, 13, 16, 17, 18, 19, 20, 21, 22, 13, 23,
@@ -2210,9 +2206,10 @@ _hb_ucd_u16[9344] =
136, 48, 48, 137, 138, 139, 140, 140, 141, 48, 142, 143, 144, 145, 140, 140,
146, 147, 148, 149, 150, 48, 151, 152, 153, 154, 32, 155, 156, 157, 140, 140,
48, 48, 158, 159, 160, 161, 162, 163, 164, 165, 9, 9, 166, 11, 11, 167,
- 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 168, 169, 48, 48,
- 168, 48, 48, 170, 171, 172, 48, 48, 48, 171, 48, 48, 48, 173, 174, 175,
- 48, 176, 9, 9, 9, 9, 9, 177, 178, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 168, 169, 48, 48, 168, 48, 48, 170, 171, 172, 48, 48,
+ 48, 171, 48, 48, 48, 173, 174, 175, 48, 176, 9, 9, 9, 9, 9, 177,
+ 178, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 179, 48, 180, 181, 48, 48, 48, 48, 182, 183,
48, 184, 48, 185, 48, 186, 187, 188, 48, 48, 48, 189, 190, 191, 192, 193,
194, 192, 48, 48, 195, 48, 48, 196, 197, 48, 198, 48, 48, 48, 48, 199,
@@ -2225,28 +2222,34 @@ _hb_ucd_u16[9344] =
241, 242, 241, 241, 242, 243, 241, 244, 245, 245, 245, 246, 247, 248, 249, 250,
251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 261, 262, 263, 264, 265,
266, 267, 268, 269, 270, 271, 272, 272, 273, 274, 275, 209, 276, 277, 209, 278,
- 279, 279, 279, 279, 279, 279, 279, 279, 280, 209, 281, 209, 209, 209, 209, 282,
- 209, 283, 279, 284, 209, 285, 286, 209, 209, 209, 287, 140, 288, 140, 271, 271,
- 271, 289, 209, 209, 209, 209, 290, 271, 209, 209, 209, 209, 209, 209, 209, 209,
- 209, 209, 209, 291, 292, 209, 209, 293, 209, 209, 209, 209, 209, 209, 294, 209,
- 209, 209, 209, 209, 209, 209, 295, 296, 271, 297, 209, 209, 298, 279, 299, 279,
- 300, 301, 279, 279, 279, 302, 279, 303, 209, 209, 209, 279, 304, 209, 209, 305,
- 209, 306, 209, 209, 209, 209, 209, 209, 9, 9, 9, 11, 11, 11, 307, 308,
- 13, 13, 13, 13, 13, 13, 309, 310, 11, 11, 311, 48, 48, 48, 312, 313,
- 48, 314, 315, 315, 315, 315, 32, 32, 316, 317, 318, 319, 320, 321, 140, 140,
- 209, 322, 209, 209, 209, 209, 209, 323, 209, 209, 209, 209, 209, 324, 140, 209,
- 325, 326, 327, 328, 136, 48, 48, 48, 48, 329, 178, 48, 48, 48, 48, 330,
- 331, 48, 48, 136, 48, 48, 48, 48, 200, 332, 48, 48, 209, 209, 333, 48,
- 209, 334, 335, 209, 336, 337, 209, 209, 335, 209, 209, 337, 209, 209, 209, 209,
- 48, 48, 48, 48, 209, 209, 209, 209, 48, 338, 48, 48, 48, 48, 48, 48,
- 151, 209, 209, 209, 287, 48, 48, 229, 339, 48, 340, 140, 13, 13, 341, 342,
- 13, 343, 48, 48, 48, 48, 344, 345, 31, 346, 347, 348, 13, 13, 13, 349,
- 350, 351, 352, 353, 354, 355, 140, 356, 357, 48, 358, 359, 48, 48, 48, 360,
- 361, 48, 48, 362, 363, 192, 32, 364, 64, 48, 365, 48, 366, 367, 48, 151,
- 76, 48, 48, 368, 369, 370, 371, 372, 48, 48, 373, 374, 375, 376, 48, 377,
- 48, 48, 48, 378, 379, 380, 381, 382, 383, 384, 315, 11, 11, 385, 386, 11,
- 11, 11, 11, 11, 48, 48, 387, 192, 48, 48, 388, 48, 389, 48, 48, 206,
- 390, 390, 390, 390, 390, 390, 390, 390, 391, 391, 391, 391, 391, 391, 391, 391,
+ 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279,
+ 280, 209, 281, 209, 209, 209, 209, 282, 209, 283, 279, 284, 209, 285, 286, 209,
+ 209, 209, 176, 140, 287, 140, 271, 271, 271, 288, 209, 209, 209, 209, 289, 271,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 290, 291, 209, 209, 292,
+ 209, 209, 209, 209, 209, 209, 293, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 294, 295, 271, 296, 209, 209, 297, 279, 298, 279,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 279, 279, 279, 279, 279, 279, 279, 279, 299, 300, 279, 279, 279, 301, 279, 302,
+ 209, 209, 209, 279, 303, 209, 209, 304, 209, 305, 209, 209, 209, 209, 209, 209,
+ 9, 9, 9, 11, 11, 11, 306, 307, 13, 13, 13, 13, 13, 13, 308, 309,
+ 11, 11, 310, 48, 48, 48, 311, 312, 48, 313, 314, 314, 314, 314, 32, 32,
+ 315, 316, 317, 318, 319, 320, 140, 140, 209, 321, 209, 209, 209, 209, 209, 322,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 323, 140, 209,
+ 324, 325, 326, 327, 136, 48, 48, 48, 48, 328, 178, 48, 48, 48, 48, 329,
+ 330, 48, 48, 136, 48, 48, 48, 48, 200, 331, 48, 48, 209, 209, 332, 48,
+ 209, 333, 334, 209, 335, 336, 209, 209, 334, 209, 209, 336, 209, 209, 209, 209,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 209, 209, 209, 209,
+ 48, 337, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 151, 209, 209, 209, 338, 48, 48, 229,
+ 339, 48, 340, 140, 13, 13, 341, 342, 13, 343, 48, 48, 48, 48, 344, 345,
+ 31, 346, 347, 348, 13, 13, 13, 349, 350, 351, 352, 353, 354, 355, 140, 356,
+ 357, 48, 358, 359, 48, 48, 48, 360, 361, 48, 48, 362, 363, 192, 32, 364,
+ 64, 48, 365, 48, 366, 367, 48, 151, 76, 48, 48, 368, 369, 370, 371, 372,
+ 48, 48, 373, 374, 375, 376, 48, 377, 48, 48, 48, 378, 379, 380, 381, 382,
+ 383, 384, 314, 11, 11, 385, 386, 11, 11, 11, 11, 11, 48, 48, 387, 192,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 388, 48, 389, 48, 48, 206,
+ 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390,
+ 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391,
48, 48, 48, 48, 48, 48, 204, 48, 48, 48, 48, 48, 48, 207, 140, 140,
392, 393, 394, 395, 396, 48, 48, 48, 48, 48, 48, 397, 398, 399, 48, 48,
48, 48, 48, 400, 209, 48, 48, 48, 48, 401, 48, 48, 402, 140, 140, 403,
@@ -2257,108 +2260,204 @@ _hb_ucd_u16[9344] =
140, 140, 140, 140, 140, 140, 140, 140, 48, 151, 48, 48, 48, 100, 429, 430,
48, 48, 431, 48, 432, 48, 48, 433, 48, 434, 48, 48, 435, 436, 140, 140,
9, 9, 437, 11, 11, 48, 48, 48, 48, 204, 192, 9, 9, 438, 11, 439,
- 48, 48, 440, 48, 48, 48, 441, 442, 442, 443, 444, 445, 140, 140, 140, 140,
- 48, 48, 48, 314, 48, 199, 440, 140, 446, 27, 27, 447, 140, 140, 140, 140,
+ 48, 48, 440, 48, 48, 48, 441, 442, 442, 443, 444, 445, 48, 48, 48, 388,
+ 48, 48, 48, 313, 48, 199, 440, 140, 446, 27, 27, 447, 140, 140, 140, 140,
448, 48, 48, 449, 48, 450, 48, 451, 48, 200, 452, 140, 140, 140, 48, 453,
48, 454, 48, 455, 140, 140, 140, 140, 48, 48, 48, 456, 271, 457, 271, 271,
458, 459, 48, 460, 461, 462, 48, 463, 48, 464, 140, 140, 465, 48, 466, 467,
48, 48, 48, 468, 48, 469, 48, 470, 48, 471, 472, 140, 140, 140, 140, 140,
48, 48, 48, 48, 196, 140, 140, 140, 9, 9, 9, 473, 11, 11, 11, 474,
- 48, 48, 475, 192, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 271, 476,
- 48, 48, 477, 478, 140, 140, 140, 479, 48, 464, 480, 48, 62, 481, 140, 48,
- 482, 140, 140, 48, 483, 140, 48, 314, 484, 48, 48, 485, 486, 457, 487, 488,
- 222, 48, 48, 489, 490, 48, 196, 192, 491, 48, 492, 493, 494, 48, 48, 495,
- 222, 48, 48, 496, 497, 498, 499, 500, 48, 97, 501, 502, 503, 140, 140, 140,
- 504, 505, 506, 48, 48, 507, 508, 192, 509, 83, 84, 510, 511, 512, 513, 514,
- 48, 48, 48, 515, 516, 517, 478, 140, 48, 48, 48, 518, 519, 192, 140, 140,
- 48, 48, 520, 521, 522, 523, 140, 140, 48, 48, 48, 524, 525, 192, 526, 140,
- 48, 48, 527, 528, 192, 140, 140, 140, 48, 173, 529, 530, 314, 140, 140, 140,
- 48, 48, 501, 531, 140, 140, 140, 140, 140, 140, 9, 9, 11, 11, 148, 532,
- 533, 534, 48, 535, 536, 192, 140, 140, 140, 140, 537, 48, 48, 538, 539, 140,
- 540, 48, 48, 541, 542, 543, 48, 48, 544, 545, 546, 48, 48, 48, 48, 196,
- 547, 140, 140, 140, 140, 140, 140, 140, 84, 48, 520, 548, 549, 148, 175, 550,
- 48, 551, 552, 553, 140, 140, 140, 140, 554, 48, 48, 555, 556, 192, 557, 48,
- 558, 559, 192, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 560,
- 561, 115, 48, 562, 563, 192, 140, 140, 140, 140, 140, 100, 271, 564, 565, 566,
- 48, 207, 140, 140, 140, 140, 140, 140, 272, 272, 272, 272, 272, 272, 567, 568,
- 48, 48, 48, 48, 388, 140, 140, 140, 140, 48, 48, 48, 48, 48, 48, 569,
- 48, 48, 48, 570, 571, 572, 140, 140, 48, 48, 48, 48, 314, 140, 140, 140,
- 48, 48, 48, 196, 48, 200, 370, 48, 48, 48, 48, 200, 192, 48, 204, 573,
- 48, 48, 48, 574, 575, 576, 577, 578, 48, 140, 140, 140, 140, 140, 140, 140,
- 140, 140, 140, 140, 9, 9, 11, 11, 271, 579, 140, 140, 140, 140, 140, 140,
- 48, 48, 48, 48, 580, 581, 582, 582, 583, 584, 140, 140, 140, 140, 585, 586,
- 48, 48, 48, 48, 48, 48, 48, 440, 48, 48, 48, 48, 48, 199, 140, 140,
- 196, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 587,
- 48, 48, 588, 589, 140, 590, 591, 48, 48, 48, 48, 48, 48, 48, 48, 206,
- 48, 48, 48, 48, 48, 48, 71, 151, 196, 592, 593, 140, 140, 140, 140, 140,
- 32, 32, 594, 32, 595, 209, 209, 209, 209, 209, 209, 209, 323, 140, 140, 140,
- 209, 209, 209, 209, 209, 209, 209, 324, 209, 209, 596, 209, 209, 209, 597, 598,
- 599, 209, 600, 209, 209, 209, 288, 140, 209, 209, 209, 209, 601, 140, 140, 140,
- 140, 140, 140, 140, 271, 602, 271, 602, 209, 209, 209, 209, 209, 287, 271, 461,
- 9, 603, 11, 604, 605, 606, 241, 9, 607, 608, 609, 610, 611, 9, 603, 11,
- 612, 613, 11, 614, 615, 616, 617, 9, 618, 11, 9, 603, 11, 604, 605, 11,
- 241, 9, 607, 617, 9, 618, 11, 9, 603, 11, 619, 9, 620, 621, 622, 623,
- 11, 624, 9, 625, 626, 627, 628, 11, 629, 9, 630, 11, 631, 632, 632, 632,
- 32, 32, 32, 633, 32, 32, 634, 635, 636, 637, 45, 140, 140, 140, 140, 140,
- 638, 639, 640, 140, 140, 140, 140, 140, 641, 642, 643, 27, 27, 27, 644, 140,
- 645, 140, 140, 140, 140, 140, 140, 140, 48, 48, 151, 646, 647, 140, 140, 140,
- 140, 48, 648, 140, 48, 48, 649, 650, 140, 140, 140, 140, 140, 48, 651, 192,
- 140, 140, 140, 140, 140, 140, 652, 200, 48, 48, 48, 48, 653, 595, 140, 140,
- 9, 9, 607, 11, 654, 370, 140, 140, 140, 140, 140, 140, 140, 140, 140, 499,
- 271, 271, 655, 656, 140, 140, 140, 140, 499, 271, 657, 658, 140, 140, 140, 140,
- 659, 48, 660, 661, 662, 663, 664, 665, 666, 206, 667, 206, 140, 140, 140, 668,
- 209, 209, 669, 209, 209, 209, 209, 209, 209, 323, 334, 670, 670, 670, 209, 324,
- 671, 209, 209, 209, 209, 209, 209, 209, 209, 209, 672, 140, 140, 140, 673, 209,
- 674, 209, 209, 669, 675, 676, 324, 140, 209, 209, 209, 209, 209, 209, 209, 677,
- 209, 209, 209, 209, 209, 678, 426, 426, 209, 209, 209, 209, 209, 209, 209, 679,
- 209, 209, 209, 209, 209, 176, 669, 427, 669, 209, 209, 209, 680, 176, 209, 209,
- 680, 209, 672, 676, 140, 140, 140, 140, 209, 209, 209, 209, 209, 323, 672, 426,
- 675, 209, 209, 681, 682, 669, 675, 675, 209, 683, 209, 209, 288, 140, 140, 192,
- 48, 48, 48, 48, 48, 48, 140, 140, 48, 48, 48, 207, 48, 48, 48, 48,
- 48, 204, 48, 48, 48, 48, 48, 48, 48, 48, 478, 48, 48, 48, 48, 48,
- 48, 48, 48, 48, 48, 48, 100, 48, 48, 48, 48, 48, 48, 204, 140, 140,
- 48, 204, 140, 140, 140, 140, 140, 140, 48, 48, 48, 48, 71, 48, 48, 48,
- 48, 48, 48, 140, 140, 140, 140, 140, 684, 140, 570, 570, 570, 570, 570, 570,
+ 48, 48, 475, 192, 476, 9, 477, 11, 478, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 271, 479, 48, 48, 480, 481, 482, 140, 140, 483,
+ 48, 464, 484, 48, 62, 485, 140, 48, 486, 140, 140, 48, 487, 140, 48, 313,
+ 488, 48, 48, 489, 490, 457, 491, 492, 222, 48, 48, 493, 494, 48, 196, 192,
+ 495, 48, 496, 497, 498, 48, 48, 499, 222, 48, 48, 500, 501, 502, 503, 504,
+ 48, 97, 505, 506, 507, 140, 140, 140, 508, 509, 510, 48, 48, 511, 512, 192,
+ 513, 83, 84, 514, 515, 516, 517, 518, 519, 48, 48, 520, 521, 522, 523, 140,
+ 48, 48, 48, 524, 525, 526, 481, 140, 48, 48, 48, 527, 528, 192, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 48, 48, 529, 530, 531, 532, 140, 140,
+ 48, 48, 48, 533, 534, 192, 535, 140, 48, 48, 536, 537, 192, 538, 539, 140,
+ 48, 540, 541, 542, 313, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 505, 543, 140, 140, 140, 140, 140, 140, 9, 9, 11, 11, 148, 544,
+ 545, 546, 48, 547, 548, 192, 140, 140, 140, 140, 549, 48, 48, 550, 551, 140,
+ 552, 48, 48, 553, 554, 555, 48, 48, 556, 557, 558, 48, 48, 48, 48, 196,
+ 559, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 48, 560, 192,
+ 84, 48, 529, 561, 562, 148, 175, 563, 48, 564, 565, 566, 140, 140, 140, 140,
+ 567, 48, 48, 568, 569, 192, 570, 48, 571, 572, 192, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 573,
+ 574, 115, 48, 575, 576, 577, 140, 140, 140, 140, 140, 100, 271, 578, 579, 580,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 207, 140, 140, 140, 140, 140, 140,
+ 272, 272, 272, 272, 272, 272, 581, 582, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 388, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 48, 48, 48, 48, 48, 583,
+ 48, 48, 48, 584, 585, 586, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 71,
+ 48, 48, 48, 48, 313, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 587, 588, 192, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 48, 196, 48, 200, 370, 48, 48, 48, 48, 200, 192, 48, 204, 589,
+ 48, 48, 48, 590, 591, 592, 593, 594, 48, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 595, 48, 596, 192, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 9, 9, 11, 11, 271, 597, 140, 140, 140, 140, 140, 140,
+ 48, 48, 48, 48, 598, 599, 600, 600, 601, 602, 140, 140, 140, 140, 603, 604,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 440,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 199, 140, 605,
+ 196, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 606,
+ 48, 48, 607, 608, 140, 609, 610, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 206,
+ 48, 48, 48, 48, 48, 48, 71, 151, 196, 611, 612, 140, 140, 140, 140, 140,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 192,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 322, 140, 140, 140, 140,
+ 32, 32, 613, 32, 614, 209, 209, 209, 209, 209, 209, 209, 322, 140, 140, 140,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 323,
+ 209, 209, 615, 209, 209, 209, 616, 617, 618, 209, 619, 209, 209, 209, 287, 140,
+ 209, 209, 209, 209, 620, 140, 140, 140, 140, 140, 140, 140, 271, 621, 271, 621,
+ 209, 209, 209, 209, 209, 338, 271, 461, 140, 140, 140, 140, 140, 140, 140, 140,
+ 9, 622, 11, 623, 624, 625, 241, 9, 626, 627, 628, 629, 630, 9, 622, 11,
+ 631, 632, 11, 633, 634, 635, 636, 9, 637, 11, 9, 622, 11, 623, 624, 11,
+ 241, 9, 626, 636, 9, 637, 11, 9, 622, 11, 638, 9, 639, 640, 641, 642,
+ 11, 643, 9, 644, 645, 646, 647, 11, 648, 9, 649, 11, 650, 538, 538, 538,
+ 32, 32, 32, 651, 32, 32, 652, 653, 654, 655, 45, 140, 140, 140, 140, 140,
+ 656, 657, 658, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 659, 660, 661, 27, 27, 27, 662, 140, 663, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 151, 664, 665, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 666, 140, 48, 48, 667, 668,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 669, 192,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 587, 670,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 671, 200,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 672, 614, 140, 140,
+ 9, 9, 626, 11, 673, 370, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 503, 271, 271, 674, 675, 140, 140, 140, 140,
+ 503, 271, 676, 677, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 678, 48, 679, 680, 681, 682, 683, 684, 685, 206, 686, 206, 140, 140, 140, 687,
+ 209, 209, 688, 209, 209, 209, 209, 209, 209, 322, 333, 689, 689, 689, 209, 323,
+ 690, 209, 209, 209, 209, 209, 209, 209, 209, 209, 691, 140, 140, 140, 692, 209,
+ 693, 209, 209, 688, 694, 695, 323, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 696,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 697, 426, 426,
+ 209, 209, 209, 209, 209, 209, 209, 698, 209, 209, 209, 209, 209, 176, 688, 427,
+ 688, 209, 209, 209, 699, 176, 209, 209, 699, 209, 691, 688, 695, 140, 140, 140,
+ 209, 209, 209, 209, 209, 322, 691, 426, 700, 209, 209, 209, 701, 702, 176, 694,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 703, 209, 209, 209, 209, 209, 192,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 140, 140,
+ 48, 48, 48, 207, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 204, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 481, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 100, 48,
+ 48, 48, 48, 48, 48, 204, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 204, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 48, 48, 71, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 140, 140, 140, 140, 140,
+ 704, 140, 584, 584, 584, 584, 584, 584, 140, 140, 140, 140, 140, 140, 140, 140,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 140,
- 391, 391, 391, 391, 391, 391, 391, 685, 391, 391, 391, 391, 391, 391, 391, 686,
+ 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 705,
+ 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 706,
+ 0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10,
+ 11, 11, 12, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 57, 58, 59, 60, 60, 60, 60, 61, 62, 63, 64, 65, 66, 67, 68,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+ 92, 93, 94, 95, 96, 97, 98, 7, 4, 4, 4, 4, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 110, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 112, 112, 112, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 114, 0,
+ 115, 116, 117, 118, 119, 120, 121, 122, 0, 123, 124, 125, 126, 126, 126, 127,
+ 128, 129, 130, 131, 132, 60, 133, 134, 135, 136, 0, 137, 138, 139, 0, 0,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 0, 126, 126, 126, 126, 126, 126, 126, 126,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 141, 142, 143, 143, 143, 143, 144, 11, 145, 146, 147, 4, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 166, 167,
+ 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168,
+ 168, 168, 168, 168, 126, 126, 126, 126, 126, 169, 126, 170, 171, 172, 19, 173,
+ 19, 19, 19, 19, 174, 19, 175, 176, 177, 178, 19, 179, 180, 181, 182, 183,
+ 184, 185, 186, 187, 188, 189, 190, 191, 168, 168, 192, 193, 194, 195, 196, 197,
+ 198, 199, 200, 201, 202, 203, 204, 205, 206, 206, 206, 206, 207, 208, 209, 168,
+ 210, 211, 212, 213, 214, 168, 215, 216, 217, 218, 219, 220, 221, 222, 223, 168,
+ 224, 225, 226, 227, 228, 229, 230, 168, 168, 231, 232, 233, 234, 235, 236, 237,
+ 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
+ 254, 255, 256, 257, 168, 168, 258, 259, 260, 261, 262, 263, 264, 265, 168, 168,
+ 266, 168, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 168, 168, 278,
+ 279, 280, 281, 168, 282, 283, 284, 168, 168, 168, 168, 285, 286, 287, 288, 289,
+ 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 291, 168,
+ 290, 292, 290, 290, 290, 293, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168,
+ 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 294, 295,
+ 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296,
+ 296, 297, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296,
+ 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 298,
+ 299, 299, 299, 299, 299, 299, 299, 299, 299, 300, 168, 168, 168, 168, 168, 168,
+ 168, 168, 168, 168, 301, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168,
+ 302, 302, 302, 302, 302, 302, 302, 302, 303, 304, 305, 306, 307, 308, 309, 168,
+ 168, 168, 168, 168, 168, 310, 168, 168, 168, 311, 312, 168, 313, 314, 315, 316,
+ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317,
+ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 318,
+ 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 317, 319, 319, 319, 319,
+ 319, 319, 319, 320, 321, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168,
+ 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 322,
+ 323, 324, 324, 324, 325, 326, 327, 327, 327, 327, 327, 328, 168, 168, 168, 168,
+ 329, 330, 331, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168,
+ 0, 0, 0, 332, 0, 0, 0, 0, 0, 0, 333, 168, 334, 335, 0, 336,
+ 0, 0, 0, 337, 338, 339, 340, 341, 189, 342, 168, 343, 0, 344, 168, 168,
+ 0, 345, 346, 347, 348, 349, 0, 0, 0, 0, 350, 0, 0, 0, 0, 351,
+ 352, 352, 352, 352, 352, 352, 352, 352, 352, 352, 353, 168, 168, 168, 168, 168,
+ 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, 354, 168, 168, 168,
+ 355, 356, 357, 168, 358, 359, 168, 168, 168, 168, 360, 361, 168, 168, 168, 168,
+ 168, 168, 168, 362, 168, 168, 168, 363, 168, 168, 168, 168, 168, 168, 168, 364,
+ 365, 365, 365, 366, 367, 368, 168, 168, 168, 168, 168, 168, 168, 168, 168, 168,
+ 168, 369, 370, 168, 371, 168, 168, 168, 372, 373, 374, 375, 168, 168, 168, 168,
+ 376, 0, 377, 378, 0, 0, 379, 380, 381, 382, 168, 168, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 383, 0, 384, 0, 385,
+ 386, 387, 388, 389, 0, 0, 0, 0, 0, 390, 391, 392, 0, 0, 393, 332,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 394, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 395, 126, 126, 126,
+ 396, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 397, 126, 126, 126, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 398,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 399, 168, 168, 168, 168, 168, 168,
+ 126, 126, 126, 126, 126, 126, 126, 126, 399, 168, 168, 168, 168, 168, 168, 168,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 400, 126, 126,
+ 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 401, 168,
+ 402, 0, 168, 168, 7, 7, 7, 403, 0, 1, 2, 3, 4, 4, 4, 4,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 1, 2, 2, 3,
0, 0, 0, 0, 0, 4, 0, 4, 2, 2, 5, 2, 2, 2, 5, 2,
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 6, 0, 0, 0, 0, 7, 8, 0, 0,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 11,
- 12, 13, 14, 14, 15, 14, 14, 14, 14, 14, 14, 14, 16, 17, 14, 14,
- 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
- 19, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
- 18, 18, 18, 18, 18, 18, 20, 21, 21, 21, 22, 20, 21, 21, 21, 21,
- 21, 23, 24, 25, 25, 25, 25, 25, 25, 26, 25, 25, 25, 27, 28, 26,
- 29, 30, 31, 32, 31, 31, 31, 31, 33, 34, 35, 31, 31, 31, 36, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 29, 31, 31, 31, 31,
- 37, 38, 37, 37, 37, 37, 37, 37, 37, 39, 31, 31, 31, 31, 31, 31,
- 40, 40, 40, 40, 40, 40, 41, 26, 42, 42, 42, 42, 42, 42, 42, 43,
- 44, 44, 44, 44, 44, 45, 44, 46, 47, 47, 47, 48, 37, 49, 31, 31,
- 31, 50, 51, 31, 31, 31, 31, 31, 31, 31, 31, 31, 52, 31, 31, 31,
- 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 53, 55, 53, 53, 53,
- 56, 57, 58, 59, 59, 60, 61, 62, 57, 63, 64, 65, 66, 59, 59, 67,
- 68, 69, 70, 71, 71, 72, 73, 74, 69, 75, 76, 77, 78, 71, 79, 26,
- 80, 81, 82, 83, 83, 84, 85, 86, 81, 87, 88, 26, 89, 83, 90, 91,
- 92, 93, 94, 95, 95, 96, 97, 98, 93, 99, 100, 101, 102, 95, 95, 26,
- 103, 104, 105, 106, 107, 104, 108, 109, 104, 105, 110, 26, 111, 108, 108, 112,
- 113, 114, 115, 113, 113, 115, 113, 116, 114, 117, 118, 119, 120, 113, 121, 113,
- 122, 123, 124, 122, 122, 124, 125, 126, 123, 127, 128, 128, 129, 122, 130, 26,
- 131, 132, 133, 131, 131, 131, 131, 131, 132, 133, 134, 131, 135, 131, 131, 131,
- 136, 137, 138, 139, 137, 137, 140, 141, 138, 142, 143, 137, 144, 137, 145, 26,
- 146, 147, 147, 147, 147, 147, 147, 148, 147, 147, 147, 149, 26, 26, 26, 26,
- 150, 151, 152, 152, 153, 152, 152, 154, 155, 156, 152, 157, 26, 26, 26, 26,
- 158, 158, 158, 158, 158, 158, 158, 158, 158, 159, 158, 158, 158, 160, 159, 158,
- 158, 158, 158, 159, 158, 158, 158, 161, 158, 161, 162, 163, 26, 26, 26, 26,
- 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,
+ 0, 0, 0, 0, 7, 8, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 10, 11, 12, 13, 14, 14, 15, 14, 14, 14,
+ 14, 14, 14, 14, 16, 17, 14, 14, 18, 18, 18, 18, 18, 18, 18, 18,
+ 19, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 20, 21,
+ 21, 21, 22, 20, 21, 21, 21, 21, 21, 23, 24, 25, 25, 25, 25, 25,
+ 25, 26, 25, 25, 25, 27, 28, 26, 29, 30, 31, 32, 31, 31, 31, 31,
+ 33, 34, 35, 31, 31, 31, 36, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 29, 31, 31, 31, 31, 37, 38, 37, 37, 37, 37, 37, 37,
+ 37, 39, 31, 31, 31, 31, 31, 31, 40, 40, 40, 40, 40, 40, 41, 26,
+ 42, 42, 42, 42, 42, 42, 42, 43, 44, 44, 44, 44, 44, 45, 44, 46,
+ 47, 47, 47, 48, 37, 49, 31, 31, 31, 50, 51, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 52, 31, 31, 31, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 54, 53, 55, 53, 53, 53, 56, 57, 58, 59, 59, 60, 61, 62,
+ 57, 63, 64, 65, 66, 59, 59, 67, 68, 69, 70, 71, 71, 72, 73, 74,
+ 69, 75, 76, 77, 78, 71, 79, 26, 80, 81, 82, 83, 83, 84, 85, 86,
+ 81, 87, 88, 26, 89, 83, 90, 91, 92, 93, 94, 95, 95, 96, 97, 98,
+ 93, 99, 100, 101, 102, 95, 95, 26, 103, 104, 105, 106, 107, 104, 108, 109,
+ 104, 105, 110, 26, 111, 108, 108, 112, 113, 114, 115, 113, 113, 115, 113, 116,
+ 114, 117, 118, 119, 120, 113, 121, 113, 122, 123, 124, 122, 122, 124, 125, 126,
+ 123, 127, 128, 128, 129, 122, 130, 26, 131, 132, 133, 131, 131, 131, 131, 131,
+ 132, 133, 134, 131, 135, 131, 131, 131, 136, 137, 138, 139, 137, 137, 140, 141,
+ 138, 142, 143, 137, 144, 137, 145, 26, 146, 147, 147, 147, 147, 147, 147, 148,
+ 147, 147, 147, 149, 26, 26, 26, 26, 150, 151, 152, 152, 153, 152, 152, 154,
+ 155, 156, 152, 157, 26, 26, 26, 26, 158, 158, 158, 158, 158, 158, 158, 158,
+ 158, 159, 158, 158, 158, 160, 159, 158, 158, 158, 158, 159, 158, 158, 158, 161,
+ 158, 161, 162, 163, 26, 26, 26, 26, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 165, 165, 165, 165, 166, 167, 165, 165, 165, 165, 165, 168,
- 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169,
- 170, 170, 170, 170, 170, 170, 170, 170, 170, 171, 172, 171, 170, 170, 170, 170,
- 170, 171, 170, 170, 170, 170, 171, 172, 171, 170, 172, 170, 170, 170, 170, 170,
- 170, 170, 171, 170, 170, 170, 170, 170, 170, 170, 170, 173, 170, 170, 170, 174,
- 170, 170, 170, 175, 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, 177, 177,
- 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178, 178,
+ 169, 169, 169, 169, 169, 169, 169, 169, 170, 170, 170, 170, 170, 170, 170, 170,
+ 170, 171, 172, 171, 170, 170, 170, 170, 170, 171, 170, 170, 170, 170, 171, 172,
+ 171, 170, 172, 170, 170, 170, 170, 170, 170, 170, 171, 170, 170, 170, 170, 170,
+ 170, 170, 170, 173, 170, 170, 170, 174, 170, 170, 170, 175, 176, 176, 176, 176,
+ 176, 176, 176, 176, 176, 176, 177, 177, 178, 178, 178, 178, 178, 178, 178, 178,
179, 179, 179, 180, 181, 181, 181, 181, 181, 181, 181, 181, 181, 182, 181, 183,
184, 184, 185, 186, 187, 187, 188, 26, 189, 189, 190, 26, 191, 192, 193, 26,
194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 195, 194, 196, 194, 196,
@@ -2368,208 +2467,164 @@ _hb_ucd_u16[9344] =
210, 210, 210, 210, 210, 211, 210, 210, 210, 212, 210, 213, 194, 194, 194, 194,
214, 214, 214, 215, 216, 216, 216, 216, 216, 216, 216, 217, 216, 216, 216, 218,
216, 219, 216, 219, 216, 220, 9, 9, 9, 221, 26, 26, 26, 26, 26, 26,
- 222, 222, 222, 222, 222, 222, 222, 222, 222, 223, 222, 222, 222, 222, 222, 224,
- 225, 225, 225, 225, 225, 225, 225, 225, 226, 226, 226, 226, 226, 226, 227, 228,
- 229, 229, 229, 229, 229, 229, 229, 230, 229, 231, 232, 232, 232, 232, 232, 232,
- 18, 233, 165, 165, 165, 165, 165, 234, 225, 26, 235, 9, 236, 237, 238, 239,
- 2, 2, 2, 2, 240, 241, 2, 2, 2, 2, 2, 242, 243, 244, 2, 245,
- 2, 2, 2, 2, 2, 2, 2, 246, 9, 9, 9, 9, 9, 9, 9, 9,
- 14, 14, 247, 247, 14, 14, 14, 14, 247, 247, 14, 248, 14, 14, 14, 247,
- 14, 14, 14, 14, 14, 14, 249, 14, 249, 14, 250, 251, 14, 14, 252, 253,
- 0, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 256, 257,
- 0, 258, 2, 259, 0, 0, 0, 0, 260, 26, 9, 9, 9, 9, 261, 26,
- 0, 0, 0, 0, 262, 263, 4, 0, 0, 264, 0, 0, 2, 2, 2, 2,
- 2, 265, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 258, 26, 26, 26, 0, 266, 26, 26, 0, 0, 0, 0,
- 267, 267, 267, 267, 267, 267, 267, 267, 267, 267, 267, 267, 267, 267, 267, 267,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 268, 0,
- 0, 0, 269, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 270, 2, 2, 2, 2,
- 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 271, 272,
- 165, 165, 165, 165, 166, 167, 273, 273, 273, 273, 273, 273, 273, 274, 275, 274,
- 170, 170, 172, 26, 172, 172, 172, 172, 172, 172, 172, 172, 18, 18, 18, 18,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 276, 26, 26, 26, 26,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 223, 222, 222, 222, 222, 222, 222,
+ 224, 224, 224, 224, 224, 224, 224, 224, 225, 225, 225, 225, 225, 225, 226, 227,
+ 228, 228, 228, 228, 228, 228, 228, 229, 228, 230, 231, 231, 231, 231, 231, 231,
+ 18, 232, 165, 165, 165, 165, 165, 233, 224, 26, 234, 9, 235, 236, 237, 238,
+ 2, 2, 2, 2, 239, 240, 2, 2, 2, 2, 2, 241, 242, 243, 2, 244,
+ 2, 2, 2, 2, 2, 2, 2, 245, 14, 14, 246, 246, 14, 14, 14, 14,
+ 246, 246, 14, 247, 14, 14, 14, 246, 14, 14, 14, 14, 14, 14, 248, 14,
+ 248, 14, 249, 250, 14, 14, 251, 252, 0, 253, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 254, 0, 255, 256, 0, 257, 2, 258, 0, 0, 0, 0,
+ 259, 26, 9, 9, 9, 9, 260, 26, 0, 0, 0, 0, 261, 262, 4, 0,
+ 0, 263, 0, 0, 2, 2, 2, 2, 2, 264, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 265, 26, 26, 0, 266, 26, 26, 0, 0, 0, 0,
+ 267, 267, 267, 267, 267, 267, 267, 267, 0, 0, 0, 0, 0, 0, 268, 0,
+ 0, 0, 269, 0, 0, 0, 0, 0, 270, 270, 270, 270, 270, 270, 270, 270,
+ 270, 270, 270, 270, 2, 2, 2, 2, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 271, 272, 165, 165, 165, 165, 166, 167, 273, 273,
+ 273, 273, 273, 273, 273, 274, 275, 274, 170, 170, 172, 26, 172, 172, 172, 172,
+ 172, 172, 172, 172, 18, 18, 18, 18, 0, 0, 0, 276, 26, 26, 26, 26,
277, 277, 277, 278, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 279, 26,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277,
277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 280, 26, 26, 26, 0, 0,
281, 0, 0, 0, 282, 283, 0, 284, 285, 286, 286, 286, 286, 286, 286, 286,
286, 286, 287, 288, 289, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 291,
- 292, 293, 293, 293, 293, 293, 294, 169, 169, 169, 169, 169, 169, 169, 169, 169,
- 169, 295, 0, 0, 293, 293, 293, 293, 0, 0, 0, 0, 296, 297, 290, 290,
- 169, 169, 169, 295, 0, 0, 0, 0, 0, 0, 0, 0, 169, 169, 169, 298,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 290, 290, 290, 290, 290, 299,
+ 292, 293, 293, 293, 293, 293, 294, 169, 169, 295, 0, 0, 293, 293, 293, 293,
+ 0, 0, 0, 0, 276, 296, 290, 290, 169, 169, 169, 295, 0, 0, 0, 0,
+ 0, 0, 0, 0, 169, 169, 169, 297, 0, 0, 290, 290, 290, 290, 290, 298,
290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 0, 0, 0, 0, 0,
- 277, 277, 277, 277, 277, 277, 277, 277, 0, 0, 0, 0, 0, 0, 0, 0,
- 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
- 300, 301, 300, 300, 300, 300, 300, 300, 302, 26, 303, 303, 303, 303, 303, 303,
- 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304,
- 304, 304, 304, 304, 304, 305, 26, 26, 18, 18, 18, 18, 18, 18, 18, 18,
- 18, 18, 18, 18, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 26,
- 0, 0, 0, 0, 307, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 308, 2, 2, 2, 2, 2, 2, 2, 309, 310, 311, 26, 26, 312, 2,
- 313, 313, 313, 313, 313, 314, 0, 315, 316, 316, 316, 316, 316, 316, 316, 26,
- 317, 317, 317, 317, 317, 317, 317, 317, 318, 319, 317, 320, 53, 53, 53, 53,
- 321, 321, 321, 321, 321, 322, 323, 323, 323, 323, 324, 325, 169, 169, 169, 326,
- 327, 327, 327, 327, 327, 327, 327, 327, 327, 328, 327, 329, 164, 164, 164, 330,
- 331, 331, 331, 331, 331, 331, 332, 26, 331, 333, 331, 334, 164, 164, 164, 164,
- 335, 335, 335, 335, 335, 335, 335, 335, 336, 26, 26, 337, 338, 338, 339, 26,
- 340, 340, 340, 26, 172, 172, 2, 2, 2, 2, 2, 341, 342, 343, 176, 176,
- 176, 176, 176, 176, 176, 176, 176, 176, 338, 338, 338, 338, 338, 344, 338, 345,
- 169, 169, 169, 169, 346, 26, 169, 169, 295, 347, 169, 169, 169, 169, 169, 346,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 280, 277, 277,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 348, 26, 26, 26, 26,
- 349, 26, 350, 351, 25, 25, 352, 353, 354, 25, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 355, 26, 356, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 357, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 358, 31, 31, 31, 31, 31, 31, 359, 26, 26, 26, 26, 31, 31,
- 9, 9, 0, 315, 9, 360, 0, 0, 0, 0, 361, 0, 258, 296, 362, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 363,
- 364, 0, 0, 0, 1, 2, 2, 3, 1, 2, 2, 3, 365, 290, 289, 290,
- 290, 290, 290, 366, 169, 169, 169, 295, 367, 367, 367, 368, 258, 258, 26, 369,
- 370, 371, 370, 370, 372, 370, 370, 373, 370, 374, 370, 374, 26, 26, 26, 26,
- 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 370, 375,
- 376, 0, 0, 0, 0, 0, 377, 0, 14, 14, 14, 14, 14, 14, 14, 14,
- 14, 253, 0, 378, 379, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 380,
- 381, 381, 381, 382, 383, 383, 383, 383, 383, 383, 384, 26, 385, 0, 0, 296,
- 386, 386, 386, 386, 387, 388, 389, 389, 389, 390, 391, 391, 391, 391, 391, 392,
- 393, 393, 393, 394, 395, 395, 395, 395, 396, 395, 397, 26, 26, 26, 26, 26,
- 398, 398, 398, 398, 398, 398, 398, 398, 398, 398, 399, 399, 399, 399, 399, 399,
- 400, 400, 400, 401, 400, 402, 403, 403, 403, 403, 404, 403, 403, 403, 403, 404,
- 405, 405, 405, 405, 405, 26, 406, 406, 406, 406, 406, 406, 407, 408, 409, 410,
- 409, 410, 411, 409, 412, 409, 412, 413, 26, 26, 26, 26, 26, 26, 26, 26,
- 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414,
- 414, 414, 414, 414, 414, 414, 415, 26, 414, 414, 416, 26, 414, 26, 26, 26,
- 417, 2, 2, 2, 2, 2, 418, 309, 26, 26, 26, 26, 26, 26, 26, 26,
- 419, 420, 421, 421, 421, 421, 422, 423, 424, 424, 425, 424, 426, 426, 426, 426,
- 427, 427, 427, 428, 429, 427, 26, 26, 26, 26, 26, 26, 430, 430, 431, 432,
- 433, 433, 433, 434, 435, 435, 435, 436, 26, 26, 26, 26, 26, 26, 26, 26,
- 437, 437, 437, 437, 438, 438, 438, 439, 438, 438, 440, 438, 438, 438, 438, 438,
- 441, 442, 443, 444, 445, 445, 446, 447, 445, 448, 445, 448, 449, 449, 449, 449,
- 450, 450, 450, 450, 26, 26, 26, 26, 451, 451, 451, 451, 452, 453, 452, 26,
- 454, 454, 454, 454, 454, 454, 455, 456, 457, 457, 458, 457, 459, 459, 460, 459,
- 461, 461, 462, 463, 26, 464, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 465, 465, 465, 465, 465, 465, 465, 465, 465, 466, 26, 26, 26, 26, 26, 26,
- 467, 467, 467, 467, 467, 467, 468, 26, 467, 467, 467, 467, 467, 467, 468, 469,
- 470, 470, 470, 470, 470, 26, 470, 471, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 31, 31, 50,
- 472, 472, 472, 472, 472, 473, 474, 26, 26, 26, 26, 26, 26, 26, 26, 475,
- 476, 476, 476, 476, 476, 26, 477, 477, 477, 477, 477, 478, 26, 26, 479, 479,
- 479, 480, 26, 26, 26, 26, 481, 481, 481, 482, 26, 26, 483, 483, 484, 26,
- 485, 485, 485, 485, 485, 485, 485, 485, 485, 486, 487, 485, 485, 485, 486, 488,
- 489, 489, 489, 489, 489, 489, 489, 489, 490, 491, 492, 492, 492, 493, 492, 494,
- 495, 495, 495, 495, 495, 495, 496, 495, 495, 26, 497, 497, 497, 497, 498, 26,
- 499, 499, 499, 499, 499, 499, 499, 499, 499, 499, 499, 499, 500, 137, 501, 26,
- 502, 502, 503, 502, 502, 502, 502, 502, 504, 26, 26, 26, 26, 26, 26, 26,
- 505, 506, 507, 508, 507, 509, 510, 510, 510, 510, 510, 510, 510, 511, 510, 512,
- 513, 514, 515, 516, 516, 517, 518, 519, 514, 520, 521, 522, 523, 524, 524, 26,
- 525, 525, 525, 525, 525, 525, 525, 525, 525, 525, 525, 526, 527, 26, 26, 26,
- 528, 528, 528, 528, 528, 528, 528, 528, 528, 26, 528, 529, 26, 26, 26, 26,
- 530, 530, 530, 530, 530, 530, 531, 530, 530, 530, 530, 531, 26, 26, 26, 26,
- 532, 532, 532, 532, 532, 532, 532, 532, 533, 26, 532, 534, 198, 535, 26, 26,
- 536, 536, 536, 536, 536, 536, 536, 537, 536, 537, 26, 26, 26, 26, 26, 26,
- 538, 538, 538, 539, 538, 540, 538, 538, 541, 26, 26, 26, 26, 26, 26, 26,
- 542, 542, 542, 542, 542, 542, 542, 543, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 544, 544, 544, 544, 544, 544, 544, 544, 544, 544, 545, 546,
- 547, 548, 549, 550, 550, 550, 551, 552, 547, 26, 550, 553, 26, 26, 26, 26,
- 26, 26, 26, 26, 554, 555, 554, 554, 554, 554, 554, 555, 556, 26, 26, 26,
- 557, 557, 557, 557, 557, 557, 557, 557, 557, 26, 558, 558, 558, 558, 558, 558,
- 558, 558, 558, 558, 559, 26, 178, 178, 560, 560, 560, 560, 560, 560, 560, 561,
- 53, 562, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 563, 564, 563, 563, 563, 563, 565, 563, 566, 26, 563, 563, 563, 567, 568, 568,
- 568, 568, 569, 568, 568, 570, 571, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 572, 573, 574, 574, 574, 574, 572, 575, 574, 26, 574, 576, 577, 578, 579, 579,
- 579, 580, 581, 582, 579, 583, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 584, 584, 584, 585,
- 586, 586, 587, 586, 586, 586, 586, 588, 586, 586, 586, 589, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 590, 26, 108, 108, 108, 108, 108, 108, 591, 592,
- 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593,
- 593, 593, 593, 594, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 593, 595, 596, 26,
- 593, 593, 593, 593, 593, 593, 593, 593, 597, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 598, 598, 598, 598, 598, 598, 598, 598, 598, 598, 598, 598, 599, 26,
- 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
- 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 601, 26, 26, 26, 26, 26,
- 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602,
- 602, 602, 602, 602, 602, 602, 602, 602, 603, 26, 26, 26, 26, 26, 26, 26,
- 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306, 306,
- 306, 306, 306, 306, 306, 306, 306, 604, 605, 605, 605, 606, 605, 607, 608, 608,
- 608, 608, 608, 608, 608, 608, 608, 609, 608, 610, 611, 611, 611, 612, 612, 26,
- 613, 613, 613, 613, 613, 613, 613, 613, 614, 26, 613, 615, 615, 613, 613, 616,
- 613, 613, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 617, 617, 617, 617, 617, 617, 617, 617,
- 617, 617, 617, 618, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 619, 619, 619, 619, 619, 619, 619, 619, 619, 620, 619, 619, 619, 619, 619, 619,
- 619, 621, 619, 619, 26, 26, 26, 26, 26, 26, 26, 26, 622, 26, 348, 26,
- 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623,
- 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 26,
- 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 624,
- 624, 624, 624, 624, 624, 624, 624, 624, 624, 624, 625, 26, 26, 26, 26, 26,
- 623, 626, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 627, 628,
- 629, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286,
- 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286,
- 286, 286, 286, 286, 630, 26, 631, 26, 26, 26, 632, 26, 633, 26, 634, 634,
- 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634,
- 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 634, 635,
- 636, 636, 636, 636, 636, 636, 636, 636, 636, 636, 636, 636, 636, 637, 636, 638,
- 636, 639, 636, 640, 296, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 9, 9, 9, 9, 9, 641, 9, 9, 221, 26, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 296, 26, 26, 26, 26, 26, 26, 26,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 276, 26,
- 0, 0, 0, 0, 258, 364, 0, 0, 0, 0, 0, 0, 642, 643, 0, 644,
- 645, 646, 0, 0, 0, 647, 0, 0, 0, 0, 0, 0, 0, 266, 26, 26,
- 14, 14, 14, 14, 14, 14, 14, 14, 247, 26, 26, 26, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 296, 26, 0, 0, 296, 26,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 258, 26, 0, 0, 0, 260,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0,
- 0, 0, 0, 255, 648, 649, 0, 650, 651, 0, 0, 0, 0, 0, 0, 0,
- 269, 652, 255, 255, 0, 0, 0, 653, 654, 655, 656, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 276, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 268, 0, 0, 0, 0, 0, 0,
- 657, 657, 657, 657, 657, 657, 657, 657, 657, 657, 657, 657, 657, 657, 657, 657,
- 657, 658, 26, 659, 660, 657, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 2, 2, 2, 349, 661, 309, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 662, 270, 270, 663, 664, 665, 18, 18, 18, 18, 18, 18, 18, 666, 26, 26,
- 26, 667, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 668, 668, 668, 668, 668, 669, 668, 670, 668, 671, 26, 26, 26, 26, 26, 26,
- 26, 26, 672, 672, 672, 673, 26, 26, 674, 674, 674, 674, 674, 674, 674, 675,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 676, 676, 676, 676, 676, 677,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 172, 678, 170, 172,
- 679, 679, 679, 679, 679, 679, 679, 679, 679, 679, 679, 679, 679, 679, 679, 679,
- 679, 679, 679, 679, 679, 679, 679, 679, 680, 679, 681, 26, 26, 26, 26, 26,
- 682, 682, 682, 682, 682, 682, 682, 682, 682, 683, 682, 684, 26, 26, 26, 26,
- 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 364, 0,
- 0, 0, 0, 0, 0, 0, 378, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 364, 0, 0, 0, 0, 0, 0, 276, 26, 26, 26, 26, 26, 26, 26, 26,
- 685, 31, 31, 31, 686, 687, 688, 689, 690, 691, 686, 692, 686, 688, 688, 693,
- 31, 694, 31, 695, 696, 694, 31, 695, 26, 26, 26, 26, 26, 26, 51, 26,
- 0, 0, 0, 0, 0, 296, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 296, 26, 0, 258, 364, 0, 364, 0, 364, 0, 0, 0, 276, 26,
- 0, 0, 0, 0, 0, 276, 26, 26, 26, 26, 26, 26, 697, 0, 0, 0,
- 698, 26, 0, 0, 0, 0, 0, 296, 0, 260, 315, 26, 276, 26, 26, 26,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 699, 0, 378, 0, 378,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 258, 700,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 315, 0, 296, 260, 26,
- 0, 296, 0, 0, 0, 0, 0, 0, 0, 26, 0, 315, 0, 0, 0, 0,
- 0, 26, 0, 0, 0, 276, 315, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 296, 26, 0, 276, 0, 378,
- 0, 260, 0, 0, 0, 0, 0, 269, 276, 697, 0, 296, 0, 260, 0, 260,
- 0, 0, 361, 0, 0, 0, 0, 0, 0, 266, 26, 26, 26, 26, 0, 315,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 26, 26, 26, 26,
- 277, 277, 277, 277, 277, 277, 277, 348, 277, 277, 277, 277, 277, 277, 277, 277,
- 277, 277, 277, 280, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277,
- 277, 277, 277, 277, 348, 26, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 701, 26, 277, 277,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 280, 26, 26, 26, 26,
- 277, 277, 277, 280, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 277, 277, 277, 277, 277, 277, 277, 277, 277, 702, 277, 277, 277, 277, 277, 277,
- 277, 277, 277, 277, 277, 277, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
- 703, 26, 26, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0,
+ 299, 299, 299, 299, 299, 299, 299, 299, 299, 300, 299, 299, 299, 299, 299, 299,
+ 301, 26, 302, 302, 302, 302, 302, 302, 303, 303, 303, 303, 303, 303, 303, 303,
+ 303, 303, 303, 303, 303, 304, 26, 26, 18, 18, 18, 18, 305, 305, 305, 305,
+ 305, 305, 305, 305, 305, 305, 305, 26, 0, 0, 0, 0, 306, 2, 2, 2,
+ 2, 307, 2, 2, 2, 2, 2, 2, 2, 308, 309, 258, 26, 26, 310, 2,
+ 311, 311, 311, 311, 311, 312, 0, 265, 313, 313, 313, 313, 313, 313, 313, 26,
+ 314, 314, 314, 314, 314, 314, 314, 314, 315, 316, 314, 317, 53, 53, 53, 53,
+ 318, 318, 318, 318, 318, 319, 320, 320, 320, 320, 321, 322, 169, 169, 169, 323,
+ 324, 324, 324, 324, 324, 324, 324, 324, 324, 325, 324, 326, 164, 164, 164, 327,
+ 328, 328, 328, 328, 328, 328, 329, 26, 328, 330, 328, 331, 164, 164, 164, 164,
+ 332, 332, 332, 332, 332, 332, 332, 332, 333, 26, 26, 334, 335, 335, 336, 26,
+ 337, 337, 337, 26, 172, 172, 2, 2, 2, 2, 2, 338, 339, 340, 176, 176,
+ 176, 176, 176, 176, 176, 176, 176, 176, 335, 335, 335, 335, 335, 341, 335, 342,
+ 169, 169, 169, 169, 343, 26, 169, 169, 295, 344, 169, 169, 169, 169, 169, 343,
+ 26, 26, 26, 26, 26, 26, 26, 26, 277, 277, 277, 277, 277, 280, 277, 277,
+ 277, 277, 277, 345, 26, 26, 26, 26, 346, 26, 347, 348, 25, 25, 349, 350,
+ 351, 25, 31, 31, 31, 31, 31, 31, 352, 26, 353, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 354, 31, 31, 355, 31, 31, 31, 31, 31,
+ 31, 356, 26, 26, 26, 26, 31, 31, 9, 9, 0, 265, 9, 357, 0, 0,
+ 0, 0, 358, 0, 257, 359, 360, 31, 31, 31, 31, 31, 31, 31, 31, 361,
+ 362, 0, 0, 0, 1, 2, 2, 3, 1, 2, 2, 3, 363, 290, 289, 290,
+ 290, 290, 290, 364, 169, 169, 169, 295, 365, 365, 365, 366, 257, 257, 26, 367,
+ 368, 369, 368, 368, 370, 368, 368, 371, 368, 372, 368, 372, 26, 26, 26, 26,
+ 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 373,
+ 374, 0, 0, 0, 0, 0, 375, 0, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 252, 0, 376, 377, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 378,
+ 379, 379, 379, 380, 381, 381, 381, 381, 381, 381, 382, 26, 383, 0, 0, 359,
+ 384, 384, 384, 384, 385, 386, 387, 387, 387, 388, 389, 389, 389, 389, 389, 390,
+ 391, 391, 391, 392, 393, 393, 393, 393, 394, 393, 395, 26, 26, 26, 26, 26,
+ 396, 396, 396, 396, 396, 396, 396, 396, 396, 396, 397, 397, 397, 397, 397, 397,
+ 398, 398, 398, 399, 398, 400, 401, 401, 401, 401, 402, 401, 401, 401, 401, 402,
+ 403, 403, 403, 403, 403, 26, 404, 404, 404, 404, 404, 404, 405, 406, 407, 408,
+ 407, 408, 409, 407, 410, 407, 410, 411, 412, 412, 412, 412, 412, 412, 413, 26,
+ 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 414, 415, 26,
+ 414, 414, 416, 26, 414, 26, 26, 26, 417, 2, 2, 2, 2, 2, 418, 419,
+ 420, 421, 422, 422, 422, 422, 423, 424, 425, 425, 426, 425, 427, 427, 427, 427,
+ 428, 428, 428, 429, 430, 428, 26, 26, 26, 26, 26, 26, 431, 431, 432, 433,
+ 434, 434, 434, 435, 436, 436, 436, 437, 438, 438, 438, 438, 439, 439, 439, 440,
+ 439, 439, 441, 439, 439, 439, 439, 439, 442, 443, 444, 445, 446, 446, 447, 448,
+ 446, 449, 446, 449, 450, 450, 450, 450, 451, 451, 451, 451, 26, 26, 26, 26,
+ 452, 452, 452, 452, 453, 454, 453, 26, 455, 455, 455, 455, 455, 455, 456, 457,
+ 458, 458, 459, 458, 460, 460, 461, 460, 462, 462, 463, 464, 26, 465, 26, 26,
+ 466, 466, 466, 466, 466, 466, 466, 466, 466, 467, 26, 26, 26, 26, 26, 26,
+ 468, 468, 468, 468, 468, 468, 469, 26, 468, 468, 468, 468, 468, 468, 469, 470,
+ 471, 471, 471, 471, 471, 26, 471, 472, 473, 473, 473, 473, 474, 475, 473, 473,
+ 474, 476, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 31, 31, 50,
+ 477, 477, 477, 477, 477, 478, 479, 26, 480, 26, 26, 26, 26, 26, 26, 481,
+ 482, 482, 482, 482, 482, 26, 483, 483, 483, 483, 483, 484, 26, 26, 485, 485,
+ 485, 486, 26, 26, 26, 26, 487, 487, 487, 488, 26, 26, 489, 489, 490, 26,
+ 491, 491, 491, 491, 491, 491, 491, 491, 491, 492, 493, 491, 491, 491, 492, 494,
+ 495, 495, 495, 495, 495, 495, 495, 495, 496, 497, 498, 498, 498, 499, 498, 500,
+ 501, 501, 501, 501, 501, 501, 502, 501, 501, 26, 503, 503, 503, 503, 504, 26,
+ 505, 505, 505, 505, 505, 505, 505, 505, 505, 505, 505, 505, 506, 137, 507, 26,
+ 508, 508, 509, 508, 508, 508, 508, 508, 510, 26, 26, 26, 26, 26, 26, 26,
+ 511, 512, 513, 514, 513, 515, 516, 516, 516, 516, 516, 516, 516, 517, 516, 518,
+ 519, 520, 521, 522, 522, 523, 524, 525, 520, 526, 527, 528, 529, 530, 530, 26,
+ 531, 532, 531, 531, 531, 531, 533, 531, 534, 535, 533, 536, 537, 26, 26, 26,
+ 538, 538, 538, 538, 538, 538, 538, 538, 538, 538, 538, 539, 540, 26, 26, 26,
+ 541, 541, 541, 541, 541, 541, 541, 541, 541, 26, 541, 542, 26, 26, 26, 26,
+ 543, 543, 543, 543, 543, 543, 544, 543, 543, 543, 543, 544, 26, 26, 26, 26,
+ 545, 545, 545, 545, 545, 545, 545, 545, 546, 26, 545, 547, 198, 548, 26, 26,
+ 549, 549, 549, 549, 549, 549, 549, 550, 549, 550, 164, 164, 551, 26, 26, 26,
+ 552, 552, 552, 553, 552, 554, 552, 552, 555, 26, 26, 26, 26, 26, 26, 26,
+ 556, 556, 556, 556, 556, 556, 556, 557, 26, 26, 26, 26, 558, 558, 558, 558,
+ 558, 558, 558, 558, 558, 558, 559, 560, 561, 562, 563, 564, 564, 564, 565, 566,
+ 561, 26, 564, 567, 26, 26, 26, 26, 26, 26, 26, 26, 568, 569, 568, 568,
+ 568, 568, 568, 569, 570, 26, 26, 26, 571, 571, 571, 571, 571, 571, 571, 571,
+ 571, 26, 572, 572, 572, 572, 572, 572, 572, 572, 572, 572, 573, 26, 178, 178,
+ 574, 574, 574, 574, 574, 574, 574, 575, 53, 576, 26, 26, 26, 26, 26, 26,
+ 577, 577, 577, 577, 578, 26, 577, 578, 579, 580, 579, 579, 579, 579, 581, 579,
+ 582, 26, 579, 579, 579, 583, 584, 584, 584, 584, 585, 584, 584, 586, 587, 26,
+ 588, 589, 590, 590, 590, 590, 588, 591, 590, 26, 590, 592, 593, 594, 595, 595,
+ 595, 596, 597, 598, 595, 599, 26, 26, 26, 26, 26, 26, 600, 600, 600, 601,
+ 602, 602, 603, 602, 602, 602, 602, 604, 602, 602, 602, 605, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 606, 26, 108, 108, 108, 108, 108, 108, 607, 608,
+ 609, 609, 609, 609, 609, 609, 609, 609, 609, 609, 609, 610, 26, 26, 26, 26,
+ 609, 609, 609, 609, 609, 611, 612, 26, 613, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 614, 614, 614, 614, 614, 614, 614, 614, 614, 614, 614, 614, 615, 26,
+ 616, 616, 616, 616, 616, 616, 616, 616, 616, 616, 617, 26, 616, 616, 616, 616,
+ 616, 616, 616, 616, 616, 616, 616, 618, 619, 619, 619, 619, 619, 619, 619, 619,
+ 620, 26, 26, 26, 26, 26, 26, 26, 621, 621, 621, 621, 621, 621, 621, 622,
+ 305, 305, 305, 305, 305, 305, 305, 305, 305, 305, 305, 305, 305, 305, 305, 623,
+ 624, 624, 624, 625, 624, 626, 627, 627, 627, 627, 627, 627, 627, 627, 627, 628,
+ 627, 629, 630, 630, 630, 631, 631, 26, 632, 632, 632, 632, 632, 632, 632, 632,
+ 633, 26, 632, 634, 634, 632, 632, 635, 632, 632, 26, 26, 26, 26, 26, 26,
+ 636, 636, 636, 636, 636, 636, 636, 637, 638, 638, 638, 638, 638, 638, 638, 638,
+ 638, 638, 638, 639, 26, 26, 26, 26, 640, 640, 640, 640, 640, 640, 640, 640,
+ 640, 641, 640, 640, 640, 640, 640, 640, 640, 642, 640, 640, 26, 26, 26, 26,
+ 26, 26, 26, 26, 643, 26, 345, 26, 644, 644, 644, 644, 644, 644, 644, 644,
+ 644, 644, 644, 644, 644, 644, 644, 26, 645, 645, 645, 645, 645, 645, 645, 645,
+ 645, 645, 646, 26, 26, 26, 26, 647, 644, 648, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 649, 650, 651, 286, 286, 286, 286, 286, 286, 286,
+ 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 286, 652, 26, 653, 26,
+ 26, 26, 654, 26, 655, 26, 656, 656, 656, 656, 656, 656, 656, 656, 656, 656,
+ 656, 656, 656, 656, 656, 656, 656, 657, 658, 658, 658, 658, 658, 658, 658, 658,
+ 658, 658, 658, 658, 658, 659, 658, 660, 658, 661, 658, 662, 359, 26, 26, 26,
+ 0, 0, 0, 0, 0, 0, 0, 265, 0, 0, 0, 0, 0, 0, 359, 26,
+ 9, 9, 9, 9, 9, 663, 9, 9, 221, 26, 0, 0, 0, 0, 0, 0,
+ 359, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 276, 26,
+ 0, 0, 0, 0, 257, 362, 0, 0, 0, 0, 0, 0, 664, 665, 0, 666,
+ 667, 668, 0, 0, 0, 669, 0, 0, 0, 0, 0, 0, 0, 266, 26, 26,
+ 246, 26, 26, 26, 26, 26, 26, 26, 0, 0, 359, 26, 0, 0, 359, 26,
+ 0, 0, 257, 26, 0, 0, 0, 259, 0, 0, 254, 0, 0, 0, 0, 0,
+ 0, 0, 0, 254, 670, 671, 0, 672, 673, 0, 0, 0, 0, 0, 0, 0,
+ 269, 674, 254, 254, 0, 0, 0, 675, 676, 677, 678, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 276, 0, 0, 0, 0, 268, 0, 0, 0, 0, 0, 0,
+ 679, 679, 679, 679, 679, 679, 679, 679, 679, 680, 26, 681, 682, 679, 26, 26,
+ 2, 2, 2, 346, 683, 419, 26, 26, 684, 270, 270, 685, 686, 687, 18, 18,
+ 18, 18, 18, 18, 18, 688, 26, 26, 26, 689, 26, 26, 26, 26, 26, 26,
+ 690, 690, 690, 690, 690, 691, 690, 692, 690, 693, 26, 26, 26, 26, 26, 26,
+ 26, 26, 694, 694, 694, 695, 26, 26, 696, 696, 696, 696, 696, 696, 696, 697,
+ 26, 26, 698, 698, 698, 698, 698, 699, 26, 26, 700, 700, 700, 700, 700, 701,
+ 26, 26, 26, 26, 172, 702, 170, 172, 703, 703, 703, 703, 703, 703, 703, 703,
+ 704, 703, 705, 26, 26, 26, 26, 26, 706, 706, 706, 706, 706, 706, 706, 706,
+ 706, 707, 706, 708, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 362, 0,
+ 0, 0, 0, 0, 0, 0, 376, 26, 362, 0, 0, 0, 0, 0, 0, 276,
+ 709, 31, 31, 31, 710, 711, 712, 713, 714, 715, 710, 716, 710, 712, 712, 717,
+ 31, 718, 31, 719, 720, 718, 31, 719, 26, 26, 26, 26, 26, 26, 721, 26,
+ 0, 0, 0, 0, 0, 359, 0, 0, 0, 0, 359, 26, 0, 257, 362, 0,
+ 362, 0, 362, 0, 0, 0, 276, 26, 0, 0, 0, 0, 0, 276, 26, 26,
+ 26, 26, 26, 26, 722, 0, 0, 0, 723, 26, 0, 0, 0, 0, 0, 359,
+ 0, 259, 265, 26, 276, 26, 26, 26, 0, 0, 0, 724, 0, 376, 0, 376,
+ 0, 0, 0, 0, 0, 0, 257, 725, 0, 0, 0, 265, 0, 359, 259, 26,
+ 0, 359, 0, 0, 0, 0, 0, 0, 0, 26, 0, 265, 0, 0, 0, 0,
+ 0, 26, 0, 0, 0, 276, 0, 359, 265, 26, 26, 26, 26, 26, 26, 26,
+ 0, 0, 359, 26, 0, 276, 0, 376, 0, 726, 0, 0, 0, 0, 0, 0,
+ 257, 722, 0, 727, 0, 265, 0, 259, 0, 0, 358, 0, 0, 0, 0, 0,
+ 277, 277, 277, 277, 26, 26, 26, 26, 277, 277, 277, 277, 277, 277, 277, 345,
+ 277, 277, 277, 280, 277, 277, 277, 277, 277, 277, 277, 277, 345, 26, 277, 277,
+ 277, 277, 277, 277, 728, 26, 277, 277, 277, 277, 277, 280, 26, 26, 26, 26,
+ 277, 729, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 26, 26,
+ 730, 26, 26, 26, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
939, 940, 941, 942, 946, 948, 0, 962, 969, 970, 971, 976,1001,1002,1003,1008,
0,1033,1040,1041,1042,1043,1047, 0, 0,1080,1081,1082,1086,1110, 0, 0,
@@ -2732,17 +2787,24 @@ _hb_ucd_u16[9344] =
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1602,1603,1934,1935,1574,1575,
1576,1577,1579,1580,1581,1583,1584, 0,1585,1587,1588,1589,1591, 0,1592, 0,
1593,1594, 0,1595,1596, 0,1598,1599,1600,1601,1604,1582,1578,1590,1597, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1936, 0,1937, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1938, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1939,1940,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1941,1942, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1944,1943, 0,1945, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1946,1947, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1948, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1949,1950,
- 1951,1952,1953,1954,1955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1956,1957,1958,1960,1959,
- 1961, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,1936, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,1937, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1938, 0,1939, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1940, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1941,1942,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1943,1944, 0, 0, 0,
+ 0, 0, 0,1945, 0,1946, 0, 0, 0, 0, 0, 0, 0, 0,1947, 0,
+ 0,1948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,1950, 0,1949,1951, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1953,1952, 0,1954, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1955,1956, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,1957, 0, 0, 0, 0, 0, 0, 0,
+ 0,1958,1961,1959,1965,1960,1962,1964,1963, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,1967,1966,1968, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1969,1970,
+ 1971,1972,1973,1974,1975, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1976,1977,1978,1980,1979,
+ 1981, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
106, 104, 107, 826, 114, 118, 119, 121, 123, 124, 127, 125, 34, 830, 130, 131,
132, 137, 827, 35, 133, 139, 829, 142, 143, 112, 144, 145, 924, 151, 152, 37,
157, 158, 159, 160, 38, 165, 166, 169, 171, 172, 173, 174, 176, 177, 178, 179,
@@ -2799,12 +2861,12 @@ _hb_ucd_i16[196] =
static inline uint_fast8_t
_hb_ucd_gc (unsigned u)
{
- return u<1114110u?_hb_ucd_u8[6808+(((_hb_ucd_u8[1312+(((_hb_ucd_u16[((_hb_ucd_u8[544+(((_hb_ucd_u8[u>>1>>3>>3>>4])<<4)+((u>>1>>3>>3)&15u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:2;
+ return u<1114110u?_hb_ucd_u8[6472+(((_hb_ucd_u8[816+(((_hb_ucd_u16[((_hb_ucd_u8[272+(((_hb_ucd_u8[u>>1>>3>>4>>4])<<4)+((u>>1>>3>>4)&15u))])<<4)+((u>>1>>3)&15u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:2;
}
static inline uint_fast8_t
_hb_ucd_ccc (unsigned u)
{
- return u<125259u?_hb_ucd_u8[8800+(((_hb_ucd_u8[8244+(((_hb_ucd_u8[7784+(((_hb_ucd_u8[7432+(((_hb_ucd_u8[7186+(u>>2>>2>>2>>3)])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:0;
+ return u<125259u?_hb_ucd_u8[8504+(((_hb_ucd_u8[7936+(((_hb_ucd_u8[7460+(((_hb_ucd_u8[7100+(((_hb_ucd_u8[6854+(u>>2>>2>>2>>3)])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:0;
}
static inline unsigned
_hb_ucd_b4 (const uint8_t* a, unsigned i)
@@ -2814,107 +2876,76 @@ _hb_ucd_b4 (const uint8_t* a, unsigned i)
static inline int_fast16_t
_hb_ucd_bmg (unsigned u)
{
- return u<65380u?_hb_ucd_i16[((_hb_ucd_u8[9548+(((_hb_ucd_u8[9428+(((_hb_ucd_b4(9300+_hb_ucd_u8,u>>2>>3>>3))<<3)+((u>>2>>3)&7u))])<<3)+((u>>2)&7u))])<<2)+((u)&3u)]:0;
+ return u<65380u?_hb_ucd_i16[((_hb_ucd_u8[9252+(((_hb_ucd_u8[9132+(((_hb_ucd_b4(9004+_hb_ucd_u8,u>>2>>3>>3))<<3)+((u>>2>>3)&7u))])<<3)+((u>>2)&7u))])<<2)+((u)&3u)]:0;
}
static inline uint_fast8_t
_hb_ucd_sc (unsigned u)
{
- return u<918000u?_hb_ucd_u8[11070+(((_hb_ucd_u16[2048+(((_hb_ucd_u8[10334+(((_hb_ucd_u8[9884+(u>>3>>4>>4)])<<4)+((u>>3>>4)&15u))])<<4)+((u>>3)&15u))])<<3)+((u)&7u))]:2;
+ return u<918000u?_hb_ucd_u8[10486+(((_hb_ucd_u16[3744+(((_hb_ucd_u16[2624+(((_hb_ucd_u8[9588+(u>>3>>3>>4)])<<4)+((u>>3>>3)&15u))])<<3)+((u>>3)&7u))])<<3)+((u)&7u))]:2;
}
static inline uint_fast16_t
_hb_ucd_dm (unsigned u)
{
- return u<195102u?_hb_ucd_u16[6032+(((_hb_ucd_u8[17084+(((_hb_ucd_u8[16702+(u>>4>>5)])<<5)+((u>>4)&31u))])<<4)+((u)&15u))]:0;
+ return u<195102u?_hb_ucd_u16[6976+(((_hb_ucd_u8[16716+(((_hb_ucd_u8[16334+(u>>4>>5)])<<5)+((u>>4)&31u))])<<4)+((u)&15u))]:0;
}
#elif !defined(HB_NO_UCD_UNASSIGNED)
static const uint8_t
-_hb_ucd_u8[14752] =
+_hb_ucd_u8[17524] =
{
- 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 9, 10, 7, 7, 7, 7, 11, 12, 13, 13, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 22, 22, 22, 22, 24, 7, 7,
- 25, 26, 22, 22, 22, 27, 28, 29, 22, 30, 31, 32, 33, 34, 35, 36,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 37, 7, 38, 39, 7, 40, 7, 7, 7, 41, 22, 42,
- 7, 7, 43, 7, 44, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 45, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 46,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 47,
+ 0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 6, 5, 5, 7, 8, 9,
+ 10, 11, 12, 13, 14, 15, 16, 5, 17, 15, 18, 19, 20, 21, 22, 23,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 24, 25, 26, 5, 27, 28,
+ 5, 29, 30, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 31, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 33,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
- 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
- 32, 33, 34, 34, 35, 36, 37, 38, 39, 34, 34, 34, 40, 41, 42, 43,
- 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
- 60, 61, 62, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 69, 72, 73,
- 69, 69, 64, 74, 64, 64, 75, 76, 77, 78, 79, 80, 81, 82, 69, 83,
- 84, 85, 86, 87, 88, 89, 69, 69, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 90, 34, 34, 34, 34,
- 91, 34, 34, 34, 34, 34, 34, 34, 34, 92, 34, 34, 93, 94, 95, 96,
- 97, 98, 99,100,101,102,103,104, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,105,
- 106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,106,
- 107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,
- 107,107, 34, 34,108,109,110,111, 34, 34,112,113,114,115,116,117,
- 118,119,120,121,122,123,124,125,126,127,128,129, 34, 34,130,131,
- 132,133,134,135,136,137,138,139,140,141,142,122,143,144,145,146,
- 147,148,149,150,151,152,153,122,154,155,122,156,157,158,159,122,
- 160,161,162,163,164,165,166,122,167,168,169,170,122,171,172,173,
- 34, 34, 34, 34, 34, 34, 34,174,175, 34,176,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,177,
- 34, 34, 34, 34, 34, 34, 34, 34,178,122,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122, 34, 34, 34, 34,179,122,122,122,
- 34, 34, 34, 34,180,181,182,183,122,122,122,122,184,185,186,187,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,188,
- 34, 34, 34, 34, 34, 34, 34, 34, 34,189,190,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,122,191,
- 34, 34,192, 34, 34,193,122,122,122,122,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,194,195,122,122,122,122,122,122,
- 122,122,122,122,122,122,122,122,122,122,122,122,122,122,196,197,
- 69,198,199,200,201,202,203,122,204,205,206,207,208,209,210,211,
- 69, 69, 69, 69,212,213,122,122,122,122,122,122,122,122,214,122,
- 215,216,217,122,122,218,122,122,122,219,122,122,122,122,122,220,
- 34,221,222,122,122,122,122,122,223,224,225,122,226,227,122,122,
- 228,229,230,231,232,122, 69,233, 69, 69, 69, 69, 69,234,235,236,
- 237,238, 69, 69,239,240, 69,241,122,122,122,122,122,122,122,122,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,242, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,243, 34,
- 244, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,245, 34, 34,
- 34, 34, 34, 34, 34, 34, 34,246, 34, 34, 34, 34,247,122,122,122,
- 34, 34, 34, 34,248,122,122,122,122,122,122,122,122,122,122,122,
- 34, 34, 34, 34, 34, 34,249, 34, 34, 34, 34, 34, 34, 34, 34, 34,
- 34, 34, 34, 34, 34, 34, 34,250,122,122,122,122,122,122,122,122,
- 251,122,252,253,122,122,122,122,122,122,122,122,122,122,122,122,
- 107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,254,
- 107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,255,
+ 16, 17, 18, 19, 20, 17, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 33, 41, 42, 43, 44, 45,
+ 46, 47, 48, 39, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 49, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 50, 17, 17, 17, 51, 17, 52, 53, 54, 55, 56, 57, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 58, 59, 59, 59, 59, 59, 59, 59, 59,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 17, 61, 62, 17, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 17, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
+ 17, 17, 17, 97, 98, 99,100,100,100,100,100,100,100,100,100,101,
+ 17, 17, 17, 17,102, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17,103, 17, 17,104,100,100,100,100,100,100,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,105,100,100,100,100,100,100, 17, 17,106,107,100,108,109,110,
+ 17, 17, 17, 17, 17, 17, 17,111, 17, 17, 17, 17,112,113,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,114,
+ 17,115,116,100,100,100,100,100,100,100,100,100,117,100,100,100,
+ 100,100,100,100,100,100,100,100,100,100,100,100,118, 39,119,120,
+ 121,122,123,124,125,126,127,128, 39, 39,129,100,100,100,100,130,
+ 131,132,133,100,134,135,100,136,137,138,100,100,139,140,141,100,
+ 142,143,144,145, 39, 39,146,147,148, 39,149,150,100,100,100,100,
+ 17, 17, 17, 17, 17, 17,151, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17,152,153, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,154, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,155, 17, 17,156,100,
+ 100,100,100,100,100,100,100,100, 17, 17,157,100,100,100,100,100,
+ 17, 17, 17,158, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17,159,100,100,100,100,100,100,100,100,100,100,100,100,
+ 160,161,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,162,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,163,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 2, 4, 5, 6, 2,
7, 7, 7, 7, 7, 2, 8, 9, 10, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16,
@@ -2951,7 +2982,7 @@ _hb_ucd_u8[14752] =
43, 43, 40, 21, 2, 81, 57, 20, 36, 36, 36, 43, 43, 75, 43, 43,
43, 43, 75, 43, 75, 43, 43, 44, 2, 2, 2, 2, 2, 2, 2, 64,
36, 36, 36, 36, 70, 43, 44, 64, 36, 36, 36, 36, 36, 61, 44, 44,
- 36, 36, 36, 36, 82, 36, 36, 61, 65, 44, 44, 44, 43, 43, 43, 43,
+ 36, 36, 36, 36, 82, 36, 36, 61, 65, 44, 44, 57, 43, 43, 43, 43,
36, 36, 36, 36, 83, 43, 43, 43, 43, 84, 43, 43, 43, 43, 43, 43,
43, 85, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 85, 71, 86,
87, 43, 43, 43, 85, 86, 87, 86, 70, 43, 43, 43, 36, 36, 36, 36,
@@ -3024,13 +3055,13 @@ _hb_ucd_u8[14752] =
85, 85, 87, 43, 43, 43, 85, 86, 86, 87, 43, 43, 43, 43, 80, 57,
2, 2, 2, 88, 2, 2, 2, 44, 43, 43, 43, 43, 43, 43, 43,109,
43, 43, 43, 43, 43, 43, 43, 80, 43, 43, 98, 36, 36, 36, 36, 36,
- 36, 36, 85, 43, 43, 85, 85, 86, 86, 85, 98, 36, 36, 36, 61, 44,
- 97, 67, 67, 67, 67, 50, 43, 43, 43, 43, 67, 67, 67, 67, 21, 64,
+ 36, 36, 85, 43, 43, 85, 85, 86, 86, 85, 98, 36, 36, 36, 61, 2,
+ 97, 67, 67, 67, 67, 50, 43, 43, 43, 43, 67, 67, 67, 67, 21, 2,
43, 98, 36, 36, 36, 36, 36, 36, 94, 43, 43, 86, 43, 87, 43, 36,
36, 36, 36, 85, 43, 86, 87, 87, 43, 86, 44, 44, 44, 44, 2, 2,
36, 36, 86, 86, 86, 86, 43, 43, 43, 43, 86, 43, 44, 93, 2, 2,
7, 7, 7, 7, 7, 44, 62, 36, 36, 36, 36, 36, 40, 40, 40, 2,
- 16, 16, 16, 16,110, 44, 44, 44, 11, 11, 11, 11, 11, 47, 48, 11,
+ 16, 16, 16, 16, 34,110, 44, 44, 11, 11, 11, 11, 11, 47, 48, 11,
2, 2, 2, 2, 44, 44, 44, 44, 43, 60, 43, 43, 43, 43, 43, 43,
85, 43, 43, 43, 71, 36, 70, 36, 36, 36, 71, 94, 43, 61, 44, 44,
16, 16, 16, 16, 16, 16, 40, 40, 40, 40, 40, 40, 40, 45, 16, 16,
@@ -3058,33 +3089,33 @@ _hb_ucd_u8[14752] =
67, 67, 67, 67, 4, 4, 67, 67, 8, 67, 67, 67,145,146, 67, 67,
67, 67, 67, 67, 67, 67,144, 67, 67, 67, 67, 67, 67, 26, 8, 8,
8, 8, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 8, 8,
- 8, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 92, 44, 44, 44, 44,
- 67, 67, 67, 67, 67, 92, 44, 44, 27, 27, 27, 27, 27, 27, 67, 67,
- 67, 67, 67, 67, 67, 27, 27, 27, 67, 67, 67, 26, 67, 67, 67, 67,
- 26, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 8, 8, 8, 8,
- 67, 67, 67, 67, 67, 67, 67, 26, 67, 67, 67, 67, 4, 4, 4, 4,
- 4, 4, 4, 27, 27, 27, 27, 27, 27, 27, 67, 67, 67, 67, 67, 67,
- 8, 8,129,147, 8, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 4,
- 8,129,148,148,148,148,148,148,148,148,148,148,147, 8, 8, 8,
- 8, 8, 8, 8, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 4, 8,
- 8, 8,144, 26, 8, 8,144, 67, 67, 67, 44, 67, 67, 67, 67, 67,
- 67, 67, 67, 55, 67, 67, 67, 67, 32, 11, 32, 34, 34, 34, 34, 11,
- 32, 32, 34, 16, 16, 16, 40, 11, 32, 32,140, 67, 67,138, 34,149,
- 43, 32, 44, 44, 93, 2, 99, 2, 16, 16, 16,150, 44, 44,150, 44,
- 36, 36, 36, 36, 44, 44, 44, 52, 64, 44, 44, 44, 44, 44, 44, 57,
- 36, 36, 36, 61, 44, 44, 44, 44, 36, 36, 36, 61, 36, 36, 36, 61,
- 2,121,121, 2,125,126,121, 2, 2, 2, 2, 6, 2,108,121, 2,
- 121, 4, 4, 4, 4, 2, 2, 88, 2, 2, 2, 2, 2,120, 2, 2,
- 108,151, 2, 2, 2, 2, 2, 2, 67, 2,152,148,148,148,153, 44,
- 67, 67, 67, 67, 67, 55, 67, 67, 67, 67, 44, 44, 44, 44, 44, 44,
- 67, 67, 67, 44, 44, 44, 44, 44, 1, 2,154,155, 4, 4, 4, 4,
- 4, 67, 4, 4, 4, 4,156,157,158,105,105,105,105, 43, 43, 86,
- 159, 40, 40, 67,105,160, 63, 67, 36, 36, 36, 61, 57,161,162, 69,
- 36, 36, 36, 36, 36, 63, 40, 69, 44, 44, 62, 36, 36, 36, 36, 36,
- 67, 27, 27, 67, 67, 67, 67, 67, 67, 67, 44, 44, 44, 44, 44, 55,
- 67, 67, 67, 67, 67, 67, 67, 92, 27, 27, 27, 27, 27, 67, 67, 67,
- 67, 67, 67, 67, 27, 27, 27, 27,163, 27, 27, 27, 27, 27, 27, 27,
- 36, 36, 83, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,164, 2,
+ 8, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 92, 44, 44,
+ 27, 27, 27, 27, 27, 27, 67, 67, 67, 67, 67, 67, 67, 27, 27, 27,
+ 67, 67, 67, 26, 67, 67, 67, 67, 26, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 8, 8, 8, 8, 67, 67, 67, 67, 67, 67, 67, 26,
+ 67, 67, 67, 67, 4, 4, 4, 4, 4, 4, 4, 27, 27, 27, 27, 27,
+ 27, 27, 67, 67, 67, 67, 67, 67, 8, 8,129,147, 8, 8, 8, 8,
+ 8, 8, 8, 4, 4, 4, 4, 4, 8,129,148,148,148,148,148,148,
+ 148,148,148,148,147, 8, 8, 8, 8, 8, 8, 8, 4, 4, 8, 8,
+ 8, 8, 8, 8, 8, 8, 4, 8, 8, 8,144, 26, 8, 8,144, 67,
+ 67, 67, 44, 67, 67, 67, 67, 67, 67, 67, 67, 55, 67, 67, 67, 67,
+ 32, 11, 32, 34, 34, 34, 34, 11, 32, 32, 34, 16, 16, 16, 40, 11,
+ 32, 32,140, 67, 67,138, 34,149, 43, 32, 44, 44, 93, 2, 99, 2,
+ 16, 16, 16,150, 44, 44,150, 44, 36, 36, 36, 36, 44, 44, 44, 52,
+ 64, 44, 44, 44, 44, 44, 44, 57, 36, 36, 36, 61, 44, 44, 44, 44,
+ 36, 36, 36, 61, 36, 36, 36, 61, 2,121,121, 2,125,126,121, 2,
+ 2, 2, 2, 6, 2,108,121, 2,121, 4, 4, 4, 4, 2, 2, 88,
+ 2, 2, 2, 2, 2,120, 2, 2,108,151, 2, 2, 2, 2, 2, 2,
+ 67, 2,152,148,148,148,153, 44, 67, 67, 67, 67, 67, 55, 67, 67,
+ 67, 67, 44, 44, 44, 44, 44, 44, 67, 67, 67, 44, 44, 44, 44, 44,
+ 1, 2,154,155, 4, 4, 4, 4, 4, 67, 4, 4, 4, 4,156,157,
+ 158,105,105,105,105, 43, 43, 86,159, 40, 40, 67,105,160, 63, 67,
+ 36, 36, 36, 61, 57,161,162, 69, 36, 36, 36, 36, 36, 63, 40, 69,
+ 44, 44, 62, 36, 36, 36, 36, 36, 67, 27, 27, 67, 67, 67, 67, 67,
+ 67, 67, 67, 44, 44, 44, 44, 55, 67, 67, 67, 67, 67, 67, 67, 92,
+ 27, 27, 27, 27, 27, 67, 67, 67, 67, 67, 67, 67, 27, 27, 27, 27,
+ 163, 27, 27, 27, 27, 27, 27, 27, 36, 36, 83, 36, 36, 36, 36, 36,
+ 67, 67, 67, 92, 44, 44, 44, 44, 36, 36, 36, 36, 36, 36,164, 2,
7, 7, 7, 7, 7, 36, 44, 44, 32, 32, 32, 32, 32, 32, 32, 70,
51,165, 43, 43, 43, 43, 43, 88, 32, 32, 32, 32, 32, 32, 40, 43,
36, 36, 36,105,105,105,105,105, 43, 2, 2, 2, 44, 44, 44, 44,
@@ -3092,7 +3123,7 @@ _hb_ucd_u8[14752] =
16, 32, 32, 32, 32, 32, 32, 32, 45, 16, 16, 16, 34, 34, 34, 32,
32, 32, 32, 32, 42,166, 34, 35, 32, 32, 16, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 11, 11, 32, 11, 11, 32, 32, 32, 32, 32, 32,
- 32, 32, 11, 11, 34,110, 44, 44, 32,150,150, 32, 32, 44, 44, 44,
+ 32, 32, 11, 11, 34, 34, 32, 44, 32,150,150, 32, 32, 32, 47, 44,
44, 40,167, 35, 40, 35, 36, 36, 36, 71, 36, 71, 36, 70, 36, 36,
36, 94, 87, 85, 67, 67, 80, 44, 27, 27, 27, 67,168, 44, 44, 44,
36, 36, 2, 2, 44, 44, 44, 44, 86, 36, 36, 36, 36, 36, 36, 36,
@@ -3153,8 +3184,10 @@ _hb_ucd_u8[14752] =
36, 61, 44, 44, 27, 27, 27, 27, 36, 44, 44, 44, 93, 2, 64, 44,
44, 44, 44, 44,179, 27, 27, 27, 11, 47, 44, 44, 44, 44, 44, 44,
16,110, 44, 44, 44, 27, 27, 27, 36, 36, 43, 43, 44, 44, 44, 44,
- 27, 27, 27, 27, 27, 27, 27,100, 36, 36, 36, 36, 36, 57,184, 44,
- 36, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 57, 43,
+ 7, 7, 7, 7, 7, 36, 36, 69, 11, 11, 11, 44, 57, 43, 43,159,
+ 16, 16, 16, 44, 44, 44, 44, 8, 27, 27, 27, 27, 27, 27, 27,100,
+ 36, 36, 36, 36, 36, 57,184, 44, 36, 44, 44, 44, 44, 44, 44, 44,
+ 44, 36, 61, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 43, 43,
27, 27, 27, 95, 44, 44, 44, 44,180, 27, 30, 2, 2, 44, 44, 44,
36, 43, 43, 2, 2, 44, 44, 44, 36, 36,183, 27, 27, 27, 44, 44,
87, 98, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 43, 43, 43, 43,
@@ -3172,14 +3205,18 @@ _hb_ucd_u8[14752] =
86, 87, 43, 43, 43, 80, 44, 44, 43, 86, 62, 36, 36, 36, 61, 62,
61, 36, 62, 36, 36, 57, 71, 86, 85, 86, 90, 89, 90, 89, 86, 44,
61, 44, 44, 89, 44, 44, 62, 36, 36, 86, 44, 43, 43, 43, 80, 44,
- 43, 43, 80, 44, 44, 44, 44, 44, 36, 36, 94, 86, 43, 43, 43, 43,
- 86, 43, 85, 71, 36, 63, 2, 2, 7, 7, 7, 7, 7, 2, 93, 71,
- 86, 87, 43, 43, 85, 85, 86, 87, 85, 43, 36, 72, 44, 44, 44, 44,
- 36, 36, 36, 36, 36, 36, 36, 94, 86, 43, 43, 44, 86, 86, 43, 87,
- 60, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 36, 36, 43, 44,
- 86, 87, 43, 43, 43, 85, 87, 87, 60, 2, 61, 44, 44, 44, 44, 44,
- 2, 2, 2, 2, 2, 2, 64, 44, 36, 36, 36, 36, 36, 70, 87, 86,
- 43, 43, 43, 87, 63, 44, 44, 44, 86, 43, 43, 87, 43, 43, 44, 44,
+ 43, 43, 80, 44, 44, 44, 44, 44, 36, 36, 36, 36, 36, 62, 44, 61,
+ 36, 36, 36, 62, 86, 87, 43, 43, 80, 90, 89, 89, 86, 90, 86, 85,
+ 71, 71, 2, 93, 64, 44, 44, 44, 57, 80, 44, 44, 44, 44, 44, 44,
+ 36, 36, 94, 86, 43, 43, 43, 43, 86, 43, 85, 71, 36, 63, 2, 2,
+ 7, 7, 7, 7, 7, 2, 93, 71, 86, 87, 43, 43, 85, 85, 86, 87,
+ 85, 43, 36, 72, 44, 44, 44, 44, 36, 36, 36, 36, 36, 36, 36, 94,
+ 86, 43, 43, 44, 86, 86, 43, 87, 60, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 36, 36, 43, 44, 86, 87, 43, 43, 43, 85, 87, 87,
+ 60, 2, 61, 44, 44, 44, 44, 44, 2, 2, 2, 2, 2, 2, 64, 44,
+ 36, 36, 36, 36, 36, 70, 87, 86, 43, 43, 43, 87, 63, 44, 44, 44,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 44, 44, 44, 44, 44, 44,
+ 36, 36, 36, 36, 36, 61, 57, 87, 86, 43, 43, 87, 43, 43, 44, 44,
7, 7, 7, 7, 7, 27, 2, 97, 43, 43, 43, 43, 87, 60, 44, 44,
27,100, 44, 44, 44, 44, 44, 62, 36, 36, 36, 61, 62, 44, 36, 36,
36, 36, 62, 61, 36, 36, 36, 36, 86, 86, 86, 89, 90, 57, 85, 71,
@@ -3189,49 +3226,52 @@ _hb_ucd_u8[14752] =
2, 2, 2, 59, 44, 44, 44, 44, 70, 43, 43, 85, 87, 43, 36, 36,
36, 36, 36, 36, 36, 43, 43, 43, 43, 43, 43, 85, 43, 2, 72, 2,
2, 64, 44, 44, 44, 44, 44, 44, 2, 2, 2, 2, 2, 44, 44, 44,
- 43, 43, 43, 80, 43, 43, 43, 87, 63, 2, 2, 44, 44, 44, 44, 44,
- 2, 36, 36, 36, 36, 36, 36, 36, 44, 43, 43, 43, 43, 43, 43, 43,
- 43, 43, 43, 43, 89, 43, 43, 43, 85, 43, 87, 80, 44, 44, 44, 44,
- 36, 36, 36, 61, 36, 62, 36, 36, 70, 43, 43, 80, 44, 80, 43, 57,
- 43, 43, 43, 70, 44, 44, 44, 44, 36, 36, 36, 62, 61, 36, 36, 36,
- 36, 36, 36, 36, 36, 86, 86, 90, 43, 89, 87, 87, 61, 44, 44, 44,
- 36, 70, 85,107, 64, 44, 44, 44, 43, 94, 36, 36, 36, 36, 36, 36,
- 36, 36, 86, 43, 43, 80, 44, 86, 85, 60, 2, 2, 2, 2, 2, 2,
+ 63, 44, 44, 44, 44, 44, 44, 44, 43, 43, 43, 80, 43, 43, 43, 87,
+ 63, 2, 2, 44, 44, 44, 44, 44, 2, 36, 36, 36, 36, 36, 36, 36,
+ 44, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 89, 43, 43, 43,
+ 85, 43, 87, 80, 44, 44, 44, 44, 36, 36, 36, 61, 36, 62, 36, 36,
+ 70, 43, 43, 80, 44, 80, 43, 57, 43, 43, 43, 70, 44, 44, 44, 44,
+ 36, 36, 36, 62, 61, 36, 36, 36, 36, 36, 36, 36, 36, 86, 86, 90,
+ 43, 89, 87, 87, 61, 44, 44, 44, 36, 70, 85,107, 64, 44, 44, 44,
+ 43, 94, 36, 36, 36, 36, 36, 36, 36, 36, 86, 43, 43, 80, 44, 86,
+ 85, 60, 2, 2, 2, 2, 2, 2, 7, 7, 7, 7, 7, 80, 44, 44,
27, 27, 91, 67, 67, 67, 56, 20,168, 67, 67, 67, 67, 67, 67, 67,
67, 44, 44, 44, 44, 44, 44, 93,105,105,105,105,105,105,105,181,
2, 2, 64, 44, 44, 44, 44, 44, 63, 64, 44, 44, 44, 44, 44, 44,
65, 65, 65, 65, 65, 65, 65, 65, 71, 36, 36, 70, 43, 43, 43, 43,
- 43, 43, 43, 44, 44, 44, 44, 44, 43, 43, 60, 44, 44, 44, 44, 44,
+ 43, 43, 43, 44, 44, 44, 44, 44, 36, 36, 36, 36, 36, 36, 36, 43,
+ 43, 43, 43, 43, 43, 86, 87, 43, 43, 43, 60, 44, 44, 44, 44, 44,
43, 43, 43, 60, 2, 2, 67, 67, 40, 40, 97, 44, 44, 44, 44, 44,
7, 7, 7, 7, 7,179, 27, 27, 27, 62, 36, 36, 36, 36, 36, 36,
- 36, 36, 36, 36, 44, 44, 62, 36, 27, 27, 27, 30, 2, 64, 44, 44,
+ 36, 36, 36, 36, 44, 44, 62, 36, 40, 69, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 83,164, 2, 27, 27, 27, 30, 2, 64, 44, 44,
36, 36, 36, 36, 36, 61, 44, 57, 94, 86, 86, 86, 86, 86, 86, 86,
86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 44, 44, 44, 57,
43, 74, 40, 40, 40, 40, 40, 40, 40, 88, 80, 44, 44, 44, 44, 44,
- 86, 44, 44, 44, 44, 44, 44, 44, 40, 40, 52, 40, 40, 40, 52, 81,
- 36, 61, 44, 44, 44, 44, 44, 44, 44, 61, 44, 44, 44, 44, 44, 44,
- 36, 61, 62, 44, 44, 44, 44, 44, 44, 44, 36, 36, 44, 44, 44, 44,
- 36, 36, 36, 36, 36, 44, 50, 60, 65, 65, 44, 44, 44, 44, 44, 44,
- 43, 43, 43, 43, 43, 43, 43, 44, 43, 43, 43, 80, 44, 44, 44, 44,
- 67, 67, 67, 92, 55, 67, 67, 67, 67, 67,186, 87, 43, 67,186, 86,
- 86,187, 65, 65, 65, 84, 43, 43, 43, 76, 50, 43, 43, 43, 67, 67,
- 67, 67, 67, 67, 67, 43, 43, 67, 67, 43, 76, 44, 44, 44, 44, 44,
- 27, 27, 44, 44, 44, 44, 44, 44, 11, 11, 11, 11, 11, 16, 16, 16,
- 16, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 16,
- 16, 16,110, 16, 16, 16, 16, 16, 11, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 47, 11, 44, 47, 48, 47, 48, 11, 47, 11,
- 11, 11, 11, 16, 16,150,150, 16, 16, 16,150, 16, 16, 16, 16, 16,
- 16, 16, 11, 48, 11, 47, 48, 11, 11, 11, 47, 11, 11, 11, 47, 16,
- 16, 16, 16, 16, 11, 48, 11, 47, 11, 11, 47, 47, 44, 11, 11, 11,
- 47, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 11, 11,
- 11, 11, 11, 16, 16, 16, 16, 16, 16, 16, 16, 44, 11, 11, 11, 11,
- 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 33, 16, 16,
- 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 31, 16, 16,
- 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 31, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 11,
- 11, 11, 11, 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16,
- 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16, 32, 44, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 43, 43, 43, 76, 67, 50, 43, 43,
+ 86, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 62,
+ 40, 40, 52, 40, 40, 40, 52, 81, 36, 61, 44, 44, 44, 44, 44, 44,
+ 44, 61, 44, 44, 44, 44, 44, 44, 36, 61, 62, 44, 44, 44, 44, 44,
+ 44, 44, 36, 36, 44, 44, 44, 44, 36, 36, 36, 36, 36, 44, 50, 60,
+ 65, 65, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 43, 43, 43, 44,
+ 43, 43, 43, 80, 44, 44, 44, 44, 67, 67, 67, 92, 55, 67, 67, 67,
+ 67, 67,186, 87, 43, 67,186, 86, 86,187, 65, 65, 65, 84, 43, 43,
+ 43, 76, 50, 43, 43, 43, 67, 67, 67, 67, 67, 67, 67, 43, 43, 67,
+ 67, 43, 76, 44, 44, 44, 44, 44, 27, 27, 44, 44, 44, 44, 44, 44,
+ 11, 11, 11, 11, 11, 16, 16, 16, 16, 16, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 16, 16, 16,110, 16, 16, 16, 16, 16,
+ 11, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 47, 11,
+ 44, 47, 48, 47, 48, 11, 47, 11, 11, 11, 11, 16, 16,150,150, 16,
+ 16, 16,150, 16, 16, 16, 16, 16, 16, 16, 11, 48, 11, 47, 48, 11,
+ 11, 11, 47, 11, 11, 11, 47, 16, 16, 16, 16, 16, 11, 48, 11, 47,
+ 11, 11, 47, 47, 44, 11, 11, 11, 47, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 11, 11, 11, 11, 11, 16, 16, 16, 16, 16,
+ 16, 16, 16, 44, 11, 11, 11, 11, 31, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11,
+ 11, 11, 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 33,
+ 16, 16, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 31,
+ 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 31, 16, 16, 16,
+ 16, 33, 16, 16, 16, 32, 44, 7, 43, 43, 43, 76, 67, 50, 43, 43,
43, 43, 43, 43, 43, 43, 76, 67, 67, 67, 50, 67, 67, 67, 67, 67,
67, 67, 76, 21, 2, 2, 44, 44, 44, 44, 44, 44, 44, 57, 43, 43,
16, 16, 16, 16, 16, 39, 16, 16, 16, 16, 16, 16, 16, 16, 16,110,
@@ -3241,22 +3281,23 @@ _hb_ucd_u8[14752] =
43, 43, 43, 74, 40, 40, 40, 44, 7, 7, 7, 7, 7, 44, 44, 77,
36, 36, 36, 36, 36, 36, 36, 80, 36, 36, 36, 36, 36, 36, 43, 43,
7, 7, 7, 7, 7, 44, 44, 96, 36, 36, 36, 36, 36, 83, 43, 43,
- 36, 36, 36, 61, 36, 36, 62, 61, 36, 36, 61,179, 27, 27, 27, 27,
- 16, 16, 43, 43, 43, 74, 44, 44, 27, 27, 27, 27, 27, 27,163, 27,
- 188, 27,100, 44, 44, 44, 44, 44, 27, 27, 27, 27, 27, 27, 27,163,
- 27, 27, 27, 27, 27, 27, 27, 44, 36, 36, 62, 36, 36, 36, 36, 36,
- 62, 61, 61, 62, 62, 36, 36, 36, 36, 61, 36, 36, 62, 62, 44, 44,
- 44, 61, 44, 62, 62, 62, 62, 36, 62, 61, 61, 62, 62, 62, 62, 62,
- 62, 61, 61, 62, 36, 61, 36, 36, 36, 61, 36, 36, 62, 36, 61, 61,
- 36, 36, 36, 36, 36, 62, 36, 36, 62, 36, 62, 36, 36, 62, 36, 36,
- 8, 44, 44, 44, 44, 44, 44, 44, 67, 67, 67, 67, 67, 67, 44, 44,
- 55, 67, 67, 67, 67, 67, 67, 67, 27, 27, 27, 27, 27, 27, 91, 67,
- 67, 67, 67, 67, 67, 67, 67, 44, 44, 44, 44, 67, 67, 67, 67, 67,
- 67, 92, 44, 44, 44, 44, 44, 44, 67, 67, 67, 67, 92, 44, 44, 44,
- 67, 44, 44, 44, 44, 44, 44, 44, 67, 67, 67, 67, 67, 25, 41, 41,
- 67, 67, 67, 67, 44, 44, 67, 67, 67, 67, 67, 92, 44, 55, 67, 67,
- 67, 67, 67, 67, 44, 44, 44, 44, 67, 67, 67, 67, 67, 67, 67, 55,
- 67, 67, 67, 44, 44, 44, 44, 67, 67, 92, 67, 67, 67, 67, 67, 67,
+ 188, 7, 7, 7, 7,189, 44, 93, 36, 36, 36, 61, 36, 36, 62, 61,
+ 36, 36, 61,179, 27, 27, 27, 27, 16, 16, 43, 43, 43, 74, 44, 44,
+ 27, 27, 27, 27, 27, 27,163, 27,190, 27,100, 44, 44, 44, 44, 44,
+ 27, 27, 27, 27, 27, 27, 27,163, 27, 27, 27, 27, 27, 27, 27, 44,
+ 36, 36, 62, 36, 36, 36, 36, 36, 62, 61, 61, 62, 62, 36, 36, 36,
+ 36, 61, 36, 36, 62, 62, 44, 44, 44, 61, 44, 62, 62, 62, 62, 36,
+ 62, 61, 61, 62, 62, 62, 62, 62, 62, 61, 61, 62, 36, 61, 36, 36,
+ 36, 61, 36, 36, 62, 36, 61, 61, 36, 36, 36, 36, 36, 62, 36, 36,
+ 62, 36, 62, 36, 36, 62, 36, 36, 8, 44, 44, 44, 44, 44, 44, 44,
+ 67, 67, 67, 67, 67, 67, 44, 44, 55, 67, 67, 67, 67, 67, 67, 67,
+ 27, 27, 27, 27, 27, 27, 91, 67, 67, 67, 67, 67, 67, 67, 67, 44,
+ 44, 44, 44, 67, 67, 67, 67, 67, 67, 92, 44, 44, 44, 44, 44, 44,
+ 67, 67, 67, 67, 92, 44, 44, 44, 67, 44, 44, 44, 44, 44, 44, 44,
+ 67, 67, 67, 67, 67, 25, 41, 41, 67, 67, 67, 67, 44, 44, 67, 67,
+ 67, 67, 67, 92, 44, 55, 67, 67, 67, 67, 67, 67, 44, 44, 44, 44,
+ 67, 67, 67, 67, 67, 44, 44, 55, 67, 67, 67, 92, 44, 44, 44, 67,
+ 67, 67, 67, 67, 67, 67, 92, 55, 67, 92, 67, 67, 67, 67, 67, 67,
79, 44, 44, 44, 44, 44, 44, 44,171,171,171,171,171,171,171, 44,
171,171,171,171,171,171,171, 0, 0, 0, 29, 21, 21, 21, 23, 21,
22, 18, 21, 25, 21, 17, 13, 13, 25, 25, 25, 21, 21, 9, 9, 9,
@@ -3282,482 +3323,677 @@ _hb_ucd_u8[14752] =
6, 21, 11, 21, 24, 9, 6, 9, 23, 26, 6, 10, 4, 4, 3, 3,
7, 25, 17, 16, 16, 22, 16, 16, 25, 17, 25, 2, 25, 24, 2, 15,
12, 15, 14, 2, 21, 14, 7, 15, 12, 17, 21, 1, 26, 10, 10, 1,
- 23, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12,
- 13, 0, 14, 0, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0,
+ 7, 13, 13, 2, 23, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 0, 10, 11, 12, 13, 0, 14, 0, 0, 0, 0, 0, 15, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20,
- 0, 21, 22, 23, 0, 0, 0, 24, 25, 26, 27, 28, 29, 30, 31, 32,
- 33, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 20, 0, 21, 22, 23, 0, 0, 0, 24, 25, 26, 27, 28,
+ 29, 30, 31, 32, 33, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 35, 0, 36, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 36, 0, 37, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 39, 0, 0, 0, 0,
- 0, 0, 40, 41, 42, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0, 0, 0, 4, 5,
- 6, 7, 0, 8, 9, 10, 0, 11, 12, 13, 14, 15, 16, 17, 16, 18,
- 16, 19, 16, 19, 16, 19, 0, 19, 16, 20, 16, 19, 21, 19, 0, 22,
- 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 32, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, 35,
- 0, 0, 36, 0, 37, 0, 0, 0, 38, 39, 40, 41, 42, 43, 44, 45,
- 46, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0, 0, 0,
- 0, 0, 0, 50, 0, 51, 0, 52, 53, 0, 54, 0, 0, 0, 0, 0,
- 0, 55, 56, 57, 0, 0, 0, 0, 58, 0, 0, 59, 60, 61, 62, 63,
- 0, 0, 64, 65, 0, 0, 0, 66, 0, 0, 0, 0, 67, 0, 0, 0,
- 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69,
- 0, 0, 0, 70, 0, 71, 0, 0, 72, 0, 0, 73, 0, 0, 0, 0,
- 0, 0, 0, 0, 74, 0, 0, 0, 0, 0, 75, 76, 0, 77, 78, 0,
- 0, 79, 80, 0, 81, 62, 0, 82, 83, 0, 0, 84, 85, 86, 0, 0,
- 0, 87, 0, 88, 0, 0, 51, 89, 51, 0, 90, 0, 91, 0, 0, 0,
- 80, 0, 0, 0, 92, 93, 0, 94, 95, 96, 97, 0, 0, 0, 0, 0,
- 51, 0, 0, 0, 0, 98, 99, 0, 0, 0, 0, 0, 0,100, 0, 0,
- 0, 0, 0,101,102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,103,
- 0, 0,104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,105,106, 0,
- 0,107, 0, 0, 0, 0, 0, 0,108, 0,109, 0,102, 0, 0, 0,
- 0, 0,110,111, 0, 0, 0, 0, 0, 0, 0,112, 0, 0, 0, 0,
- 0, 0, 0,113, 0,114, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
- 5, 6, 7, 0, 8, 0, 0, 0, 0, 9, 10, 11, 12, 0, 0, 0,
- 0, 13, 0, 0, 14, 15, 0, 16, 0, 17, 18, 0, 0, 19, 0, 20,
- 21, 0, 0, 0, 0, 0, 22, 23, 0, 24, 25, 0, 0, 26, 0, 0,
- 0, 27, 0, 0, 28, 29, 30, 31, 0, 0, 0, 32, 33, 34, 0, 0,
- 33, 0, 0, 35, 33, 0, 0, 0, 33, 36, 0, 0, 0, 0, 0, 37,
- 38, 0, 0, 0, 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 41,
- 42, 0, 0, 0, 0, 43, 0, 44, 0, 0, 0, 45, 46, 0, 0, 0,
- 47, 0, 0, 0, 0, 0, 0, 48, 49, 0, 0, 0, 0, 50, 0, 0,
- 0, 51, 0, 52, 0, 53, 0, 0, 0, 0, 54, 0, 0, 0, 0, 55,
- 0, 56, 0, 0, 0, 0, 57, 58, 0, 0, 0, 59, 60, 0, 0, 0,
- 0, 0, 0, 61, 52, 0, 62, 63, 0, 0, 64, 0, 0, 0, 65, 66,
- 0, 0, 0, 67, 0, 68, 69, 70, 71, 72, 1, 73, 0, 74, 75, 76,
- 0, 0, 77, 78, 0, 0, 0, 79, 0, 0, 1, 1, 0, 0, 80, 0,
- 0, 81, 0, 0, 0, 0, 77, 82, 0, 83, 0, 0, 0, 0, 0, 78,
- 84, 0, 85, 0, 52, 0, 1, 78, 0, 0, 86, 0, 0, 87, 0, 0,
- 0, 0, 0, 88, 57, 0, 0, 0, 0, 0, 0, 89, 90, 0, 0, 84,
- 0, 0, 33, 0, 0, 91, 0, 0, 0, 0, 92, 0, 0, 0, 0, 49,
- 0, 0, 93, 0, 0, 0, 0, 94, 95, 0, 0, 96, 0, 0, 97, 0,
- 0, 0, 98, 0, 0, 0, 99, 0, 0, 0, 0,100,101, 93, 0, 0,
- 102, 0, 0, 0, 84, 0, 0,103, 0, 0, 0,104,105, 0, 0,106,
- 107, 0, 0, 0, 0, 0, 0,108, 0, 0,109, 0, 0, 0, 0,110,
- 33, 0,111,112,113, 35, 0, 0,114, 0, 0, 0,115, 0, 0, 0,
- 0, 0, 0,116, 0, 0,117, 0, 0, 0, 0,118, 88, 0, 0, 0,
- 0, 0, 57, 0, 0, 0, 0, 52,119, 0, 0, 0, 0,120, 0, 0,
- 121, 0, 0, 0, 0,119, 0, 0,122, 0, 0, 0, 0, 0, 0,123,
- 0, 0, 0,124, 0, 0, 0,125, 0,126, 0, 0, 0, 0,127,128,
- 129, 0,130, 0,131, 0, 0, 0,132,133,134, 0, 77, 0, 0, 0,
- 0, 0, 35, 0, 0, 0,135, 0, 0, 0,136, 0, 0,137, 0, 0,
- 138, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 4,
- 5, 6, 7, 4, 4, 8, 9, 10, 1, 11, 12, 13, 14, 15, 16, 17,
- 18, 1, 1, 1, 19, 1, 0, 0, 20, 21, 22, 1, 23, 4, 21, 24,
- 25, 26, 27, 28, 29, 30, 0, 0, 1, 1, 31, 0, 0, 0, 32, 33,
- 34, 35, 1, 36, 37, 0, 0, 0, 0, 38, 1, 39, 14, 39, 40, 41,
- 42, 0, 0, 0, 43, 36, 44, 45, 21, 45, 46, 0, 0, 0, 19, 1,
- 21, 0, 0, 47, 0, 38, 48, 1, 1, 49, 49, 50, 0, 0, 51, 0,
- 0, 0, 52, 1, 0, 0, 38, 14, 4, 1, 1, 1, 53, 21, 43, 52,
- 54, 21, 35, 1, 0, 0, 0, 55, 0, 0, 0, 56, 57, 58, 0, 0,
- 0, 0, 0, 59, 0, 60, 0, 0, 0, 0, 61, 62, 0, 0, 63, 0,
- 0, 0, 64, 0, 0, 0, 65, 0, 0, 0, 66, 0, 0, 0, 67, 0,
- 0, 0, 68, 0, 0, 69, 70, 0, 71, 72, 73, 74, 75, 76, 0, 0,
- 0, 77, 0, 0, 0, 78, 79, 0, 0, 0, 0, 47, 0, 0, 0, 49,
- 0, 80, 0, 0, 0, 62, 0, 0, 63, 0, 0, 81, 0, 0, 82, 0,
- 0, 0, 83, 0, 0, 19, 84, 0, 62, 0, 0, 0, 0, 49, 1, 85,
- 1, 52, 15, 86, 36, 10, 21, 87, 0, 55, 0, 0, 0, 0, 19, 10,
- 1, 0, 0, 0, 0, 0, 88, 0, 0, 89, 0, 0, 88, 0, 0, 0,
- 0, 78, 0, 0, 87, 9, 12, 4, 90, 8, 91, 47, 0, 58, 50, 0,
- 21, 1, 21, 92, 93, 1, 1, 1, 1, 94, 95, 96, 97, 1, 98, 58,
- 81, 99,100, 4, 58, 0, 0, 0, 0, 0, 0, 19, 50, 0, 0, 0,
- 0, 0, 0, 61, 0, 0,101,102, 0, 0,103, 0, 0, 1, 1, 50,
- 0, 0, 0, 38, 0, 63, 0, 0, 0, 0, 0, 62, 0, 0,104, 68,
- 61, 0, 0, 0, 78, 0, 0, 0,105,106, 58, 38, 81, 0, 0, 0,
- 0, 0, 0,107, 1, 14, 4, 12, 84, 0, 0, 0, 0, 38, 87, 0,
- 0, 0, 0,108, 0, 0,109, 61, 0,110, 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 40,
+ 0, 0, 0, 0, 0, 0, 41, 42, 43, 0, 44, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0,
+ 0, 0, 4, 5, 6, 7, 0, 8, 9, 10, 0, 11, 12, 13, 14, 15,
+ 16, 17, 16, 18, 16, 19, 16, 19, 16, 19, 0, 19, 16, 20, 16, 19,
+ 21, 19, 0, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0,
+ 34, 0, 0, 35, 0, 0, 36, 0, 37, 0, 0, 0, 38, 39, 40, 41,
+ 42, 43, 44, 45, 46, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 49,
+ 0, 0, 0, 0, 0, 0, 0, 50, 0, 51, 0, 52, 53, 0, 54, 0,
+ 0, 0, 0, 0, 0, 55, 56, 57, 0, 0, 0, 0, 58, 0, 0, 59,
+ 60, 61, 62, 63, 0, 0, 64, 65, 0, 0, 0, 66, 0, 0, 0, 0,
+ 67, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 69, 0, 0, 0, 70, 0, 71, 0, 0, 72, 0, 0, 73,
+ 0, 0, 0, 0, 0, 0, 0, 0, 74, 75, 0, 0, 0, 0, 76, 77,
+ 0, 78, 79, 0, 0, 80, 81, 0, 82, 62, 0, 83, 84, 0, 0, 85,
+ 86, 87, 0, 88, 0, 89, 0, 90, 0, 0, 51, 91, 51, 0, 92, 0,
+ 93, 0, 0, 0, 81, 0, 0, 0, 94, 95, 0, 96, 97, 98, 99, 0,
+ 0, 0, 0, 0, 51, 0, 0, 0, 0,100,101, 0, 0, 0, 0, 0,
+ 0,102, 0, 0, 0, 0, 0, 0,103, 0, 0, 0, 0, 0, 0,104,
+ 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,106, 0, 0,107, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,108,109, 0, 0,110, 0, 0,
+ 0, 0, 0, 0,111, 0,112, 0,105, 0, 0, 0, 0, 0,113,114,
+ 0, 0, 0, 0, 0, 0, 0,115, 0, 0, 0,116, 0, 0, 0,117,
+ 0,118, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 0,
+ 8, 0, 0, 0, 0, 9, 10, 11, 12, 0, 0, 0, 0, 13, 0, 0,
+ 14, 15, 0, 16, 0, 17, 18, 0, 0, 19, 0, 20, 21, 0, 0, 0,
+ 0, 0, 22, 23, 0, 24, 25, 0, 0, 26, 0, 0, 0, 27, 0, 0,
+ 28, 29, 30, 31, 0, 0, 0, 32, 33, 34, 0, 0, 33, 0, 0, 35,
+ 33, 0, 0, 0, 33, 36, 0, 0, 0, 0, 0, 37, 38, 0, 0, 0,
+ 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0,
+ 0, 43, 0, 44, 0, 0, 0, 45, 46, 0, 0, 0, 47, 0, 0, 0,
+ 0, 0, 0, 48, 49, 0, 0, 0, 0, 50, 0, 0, 0, 51, 0, 52,
+ 0, 53, 0, 0, 0, 0, 54, 0, 0, 0, 0, 55, 0, 56, 0, 0,
+ 0, 0, 57, 58, 0, 0, 0, 59, 60, 0, 0, 0, 0, 0, 0, 61,
+ 52, 0, 62, 63, 0, 0, 64, 0, 0, 0, 65, 66, 0, 0, 0, 67,
+ 0, 68, 69, 70, 71, 72, 1, 73, 0, 74, 75, 76, 0, 0, 77, 78,
+ 0, 0, 0, 79, 0, 0, 1, 1, 0, 0, 80, 0, 0, 81, 0, 0,
+ 0, 0, 77, 82, 0, 83, 0, 0, 0, 0, 0, 78, 84, 0, 85, 0,
+ 52, 0, 1, 78, 0, 0, 86, 0, 0, 87, 0, 0, 0, 0, 0, 88,
+ 57, 0, 0, 0, 0, 0, 0, 89, 90, 0, 0, 84, 0, 0, 33, 0,
+ 0, 91, 0, 0, 0, 0, 92, 0, 0, 0, 0, 49, 0, 0, 93, 0,
+ 0, 0, 0, 94, 95, 0, 0, 96, 0, 0, 97, 0, 0, 0, 98, 0,
+ 0, 0, 99, 0, 0, 0,100, 0, 0, 0, 0,101,102, 93, 0, 0,
+ 103, 0, 0, 0, 84, 0, 0,104, 0, 0, 0,105,106, 0, 0,107,
+ 108, 0, 0, 0, 0, 0, 0,109, 0, 0,110, 0, 0, 0, 0,111,
+ 33, 0,112,113,114, 57, 0, 0,115, 35, 0, 0,116, 0, 0, 0,
+ 117, 0, 0, 0, 0, 0, 0,118, 0, 0,119, 0, 0, 0, 0,120,
+ 88, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 52,121, 0, 0, 0,
+ 0,122, 0, 0,123, 0, 0, 0, 0,121, 0, 0,124, 0, 0, 0,
+ 0, 0, 79, 0, 0, 0, 0,125, 0, 0, 0,126, 0, 0, 0,127,
+ 0,128, 0, 0, 0, 0,129,130,131, 0,132, 0,133, 0, 0, 0,
+ 134,135,136, 0, 77, 0, 0, 0, 0, 0, 35, 0, 0, 0,137, 0,
+ 0, 0,138, 0, 0, 0,139, 0, 0,140, 0, 0,141, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 4,
+ 4, 8, 9, 10, 1, 11, 12, 13, 14, 15, 16, 17, 18, 1, 1, 1,
+ 19, 1, 0, 0, 20, 21, 22, 1, 23, 4, 21, 24, 25, 26, 27, 28,
+ 29, 30, 0, 0, 1, 1, 31, 0, 0, 0, 32, 33, 34, 35, 1, 36,
+ 37, 0, 0, 0, 0, 38, 1, 39, 14, 39, 40, 41, 42, 0, 0, 0,
+ 43, 36, 44, 45, 21, 45, 46, 0, 0, 0, 19, 1, 21, 0, 0, 47,
+ 0, 38, 48, 1, 1, 49, 49, 50, 0, 0, 51, 0, 0, 19, 52, 1,
+ 0, 0, 38, 14, 4, 1, 1, 1, 53, 21, 43, 52, 54, 21, 35, 1,
+ 0, 0, 0, 55, 0, 0, 0, 56, 57, 58, 0, 0, 0, 0, 0, 59,
+ 0, 60, 0, 0, 0, 0, 61, 62, 0, 0, 63, 0, 0, 0, 64, 0,
+ 0, 0, 65, 0, 0, 0, 66, 0, 0, 0, 67, 0, 0, 0, 68, 0,
+ 0, 69, 70, 0, 71, 72, 73, 74, 75, 76, 0, 0, 0, 77, 0, 0,
+ 0, 78, 79, 0, 0, 0, 0, 47, 0, 0, 0, 49, 0, 80, 0, 0,
+ 0, 62, 0, 0, 63, 0, 0, 81, 0, 0, 82, 0, 0, 0, 83, 0,
+ 0, 19, 84, 0, 62, 0, 0, 0, 0, 49, 1, 85, 1, 52, 15, 86,
+ 36, 10, 21, 87, 0, 55, 0, 0, 0, 0, 19, 10, 1, 0, 0, 0,
+ 0, 0, 88, 0, 0, 89, 0, 0, 88, 0, 0, 0, 0, 78, 0, 0,
+ 87, 9, 12, 4, 90, 8, 91, 47, 0, 58, 50, 0, 21, 1, 21, 92,
+ 93, 1, 1, 1, 1, 94, 95, 96, 97, 1, 98, 58, 81, 99,100, 4,
+ 58, 0, 0, 0, 0, 0, 0, 19, 50, 0, 0, 0, 0, 0, 0, 61,
+ 0, 0,101,102, 0, 0,103, 0, 0, 1, 1, 50, 0, 0, 0, 38,
+ 0, 63, 0, 0, 0, 0, 0, 62, 0, 0,104, 68, 61, 0, 0, 0,
+ 78, 0, 0, 0,105,106, 58, 38, 81, 0, 0, 0, 0, 0, 0,107,
+ 1, 14, 4, 12, 84, 0, 0, 0, 0, 38, 87, 0, 0, 0, 0,108,
+ 0, 0,109, 61, 0,110, 0, 0, 0, 1, 0, 0, 0, 0, 49, 50,
0, 0, 19, 58, 0, 0, 0, 51, 0,111, 14, 52,112, 41, 0, 0,
62, 0, 0, 61, 0, 0,113, 0, 87, 0, 0, 0, 61, 62, 0, 0,
62, 0, 89, 0, 0,113, 0, 0, 0, 0,114, 0, 0, 0, 78, 55,
- 0, 38, 1, 58, 1, 58, 0, 0, 63, 89, 0, 0,115, 0, 0, 0,
- 55, 0, 0, 0, 0,115, 0, 0, 0, 0, 61, 0, 0, 0, 0, 79,
- 0, 61, 0, 0, 0, 0, 56, 0, 89, 80, 0, 0, 79, 0, 0, 0,
- 8, 91, 0, 0, 1, 87, 0, 0,116, 0, 0, 0, 0, 0, 0,117,
- 0,118,119,120,121, 0,104, 4,122, 49, 23, 0, 0, 0, 38, 50,
- 38, 58, 0, 0, 1, 87, 1, 1, 1, 1, 39, 1, 48,105, 87, 0,
- 0, 0, 0, 1, 0, 0, 0,123, 4,122, 0, 0, 0, 1,124, 0,
- 0, 0, 0, 0,230,230,230,230,230,232,220,220,220,220,232,216,
- 220,220,220,220,220,202,202,220,220,220,220,202,202,220,220,220,
- 1, 1, 1, 1, 1,220,220,220,220,230,230,230,230,240,230,220,
- 220,220,230,230,230,220,220, 0,230,230,230,220,220,220,220,230,
- 232,220,220,230,233,234,234,233,234,234,233,230, 0, 0, 0,230,
- 0,220,230,230,230,230,220,230,230,230,222,220,230,230,220,220,
- 230,222,228,230, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20,
- 21, 22, 0, 23, 0, 24, 25, 0,230,220, 0, 18, 30, 31, 32, 0,
- 0, 0, 0, 27, 28, 29, 30, 31, 32, 33, 34,230,230,220,220,230,
- 220,230,230,220, 35, 0, 0, 0, 0, 0,230,230,230, 0, 0,230,
- 230, 0,220,230,230,220, 0, 0, 0, 36, 0, 0,230,220,230,230,
- 220,220,230,220,220,230,220,230,220,230,230, 0, 0,220, 0, 0,
- 230,230, 0,230, 0,230,230,230,230,230, 0, 0, 0,220,220,220,
- 230,220,220,220,230,230, 0,220, 27, 28, 29,230, 7, 0, 0, 0,
- 0, 9, 0, 0, 0,230,220,230,230, 0, 0, 0, 0, 0,230, 0,
- 0, 84, 91, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 9, 0,
- 103,103, 9, 0,107,107,107,107,118,118, 9, 0,122,122,122,122,
- 220,220, 0, 0, 0,220, 0,220, 0,216, 0, 0, 0,129,130, 0,
- 132, 0, 0, 0, 0, 0,130,130,130,130, 0, 0,130, 0,230,230,
- 9, 0,230,230, 0, 0,220, 0, 0, 0, 0, 7, 0, 9, 9, 0,
- 9, 9, 0, 0, 0,230, 0, 0, 0,228, 0, 0, 0,222,230,220,
- 220, 0, 0, 0,230, 0, 0,220,230,220, 0,220,230,230,230, 0,
- 0, 0, 9, 9, 0, 0, 7, 0,230, 0, 1, 1, 1, 0, 0, 0,
- 230,234,214,220,202,230,230,230,230,230,232,228,228,220,218,230,
- 233,220,230,220,230,230, 1, 1, 1, 1, 1,230, 0, 1, 1,230,
- 220,230, 1, 1, 0, 0,218,228,232,222,224,224, 0, 8, 8, 0,
- 0, 0, 0,220,230, 0,230,230,220, 0, 0,230, 0, 0, 26, 0,
- 0,220, 0,230,230, 1,220, 0, 0,230,220, 0, 0, 0,220,220,
- 0, 0,230,220, 0, 9, 7, 0, 0, 7, 9, 0, 0, 0, 9, 7,
- 6, 6, 0, 0, 0, 0, 1, 0, 0,216,216, 1, 1, 1, 0, 0,
- 0,226,216,216,216,216,216, 0,220,220,220, 0,232,232,220,230,
- 230,230, 7, 0, 16, 17, 17, 33, 17, 49, 17, 17, 84, 97,135,145,
- 26, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 0, 38, 1, 58, 1, 58, 0, 0, 0, 0, 0, 88, 63, 89, 0, 0,
+ 115, 0, 0, 0, 55, 0, 0, 0, 0,115, 0, 0, 0, 0, 61, 0,
+ 0, 0, 0, 79, 0, 61, 0, 0, 0, 0, 56, 0, 89, 80, 0, 0,
+ 79, 0, 0, 0, 8, 91, 0, 0, 1, 87, 0, 0,116, 0, 0, 0,
+ 0, 0, 0,117, 0,118,119,120,121, 0,104, 4,122, 49, 23, 0,
+ 0, 0, 38, 50, 38, 58, 0, 0, 1, 87, 1, 1, 1, 1, 39, 1,
+ 48,105, 87, 0, 0, 0, 0, 1, 0, 0, 0,123, 0, 0, 0,112,
+ 4,122, 0, 0, 0, 1,124, 0, 0, 0, 0, 0,230,230,230,230,
+ 230,232,220,220,220,220,232,216,220,220,220,220,220,202,202,220,
+ 220,220,220,202,202,220,220,220, 1, 1, 1, 1, 1,220,220,220,
+ 220,230,230,230,230,240,230,220,220,220,230,230,230,220,220, 0,
+ 230,230,230,220,220,220,220,230,232,220,220,230,233,234,234,233,
+ 234,234,233,230, 0, 0, 0,230, 0,220,230,230,230,230,220,230,
+ 230,230,222,220,230,230,220,220,230,222,228,230, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 0, 23, 0, 24, 25, 0,
+ 230,220, 0, 18, 30, 31, 32, 0, 0, 0, 0, 27, 28, 29, 30, 31,
+ 32, 33, 34,230,230,220,220,230,220,230,230,220, 35, 0, 0, 0,
+ 0, 0,230,230,230, 0, 0,230,230, 0,220,230,230,220, 0, 0,
+ 0, 36, 0, 0,230,220,230,230,220,220,230,220,220,230,220,230,
+ 220,230,230, 0, 0,220, 0, 0,230,230, 0,230, 0,230,230,230,
+ 230,230, 0, 0, 0,220,220,220,230,220,220,220,230,230, 0,220,
+ 27, 28, 29,230, 7, 0, 0, 0, 0, 9, 0, 0, 0,230,220,230,
+ 230, 0, 0, 0, 0, 0,230, 0, 0, 84, 91, 0, 0, 0, 0, 9,
+ 9, 0, 0, 0, 0, 0, 9, 0,103,103, 9, 0,107,107,107,107,
+ 118,118, 9, 0,122,122,122,122,220,220, 0, 0, 0,220, 0,220,
+ 0,216, 0, 0, 0,129,130, 0,132, 0, 0, 0, 0, 0,130,130,
+ 130,130, 0, 0,130, 0,230,230, 9, 0,230,230, 0, 0,220, 0,
+ 0, 0, 0, 7, 0, 9, 9, 0, 9, 9, 0, 0, 0,230, 0, 0,
+ 0,228, 0, 0, 0,222,230,220,220, 0, 0, 0,230, 0, 0,220,
+ 230,220, 0,220,230,230,230, 0, 0, 0, 9, 9, 0, 0, 7, 0,
+ 230, 0, 1, 1, 1, 0, 0, 0,230,234,214,220,202,230,230,230,
+ 230,230,232,228,228,220,218,230,233,220,230,220,230,230, 1, 1,
+ 1, 1, 1,230, 0, 1, 1,230,220,230, 1, 1, 0, 0,218,228,
+ 232,222,224,224, 0, 8, 8, 0, 0, 0, 0,220,230, 0,230,230,
+ 220, 0, 0,230, 0, 0, 26, 0, 0,220, 0,230,230, 1,220, 0,
+ 0,230,220, 0, 0, 0,220,220, 0, 0,230,220, 0, 9, 7, 0,
+ 0, 7, 9, 0, 0, 0, 9, 7, 6, 6, 0, 0, 0, 0, 1, 0,
+ 0,216,216, 1, 1, 1, 0, 0, 0,226,216,216,216,216,216, 0,
+ 220,220,220, 0,232,232,220,230,230,230, 7, 0, 16, 17, 17, 33,
+ 17, 49, 17, 17, 84, 97,135,145, 26, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17, 17, 17,177, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 5, 3,
- 3, 3, 3, 3, 6, 7, 8, 3, 3, 3, 3, 3, 9, 10, 11, 12,
- 13, 3, 3, 3, 3, 3, 3, 3, 3, 14, 3, 15, 3, 3, 3, 3,
- 3, 3, 16, 17, 18, 19, 20, 21, 3, 3, 3, 22, 23, 24, 3, 3,
- 3, 3, 3, 3, 25, 3, 3, 3, 3, 3, 3, 3, 3, 26, 3, 3,
- 27, 28, 0, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 3,
- 0, 0, 0, 3, 0, 0, 0, 0, 0, 4, 0, 5, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 7,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0,
- 0, 0, 0, 9, 0, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
- 13, 0, 0, 14, 15, 16, 6, 0, 17, 18, 19, 19, 19, 20, 21, 22,
- 23, 24, 19, 25, 0, 26, 27, 19, 19, 28, 29, 30, 0, 31, 0, 0,
- 0, 8, 0, 0, 0, 0, 0, 0, 0, 19, 28, 0, 32, 33, 9, 34,
- 35, 19, 0, 0, 36, 37, 38, 39, 40, 19, 0, 41, 42, 43, 44, 31,
- 0, 1, 45, 42, 0, 0, 0, 0, 0, 32, 14, 14, 0, 0, 0, 0,
- 14, 0, 0, 46, 47, 47, 47, 47, 48, 49, 47, 47, 47, 47, 50, 51,
- 52, 53, 43, 21, 0, 0, 0, 0, 0, 0, 0, 54, 6, 55, 0, 14,
- 19, 1, 0, 0, 0, 0, 56, 57, 0, 0, 0, 0, 0, 19, 58, 31,
- 0, 0, 0, 0, 0, 0, 0, 59, 14, 0, 0, 0, 0, 1, 0, 2,
- 0, 0, 0, 3, 0, 0, 0, 60, 61, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 0, 0, 2, 3, 0, 4, 5, 0, 0, 6, 0, 0,
- 0, 7, 0, 0, 0, 1, 1, 0, 0, 8, 9, 0, 8, 9, 0, 0,
- 0, 0, 8, 9, 10, 11, 12, 0, 0, 0, 13, 0, 0, 0, 0, 14,
- 15, 16, 17, 0, 0, 0, 1, 0, 0, 18, 19, 0, 0, 0, 20, 0,
- 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 8,
- 21, 9, 0, 0, 22, 0, 0, 0, 0, 1, 0, 23, 24, 25, 0, 0,
- 26, 0, 0, 0, 8, 21, 27, 0, 1, 0, 0, 1, 1, 1, 1, 0,
- 1, 28, 29, 30, 0, 31, 32, 20, 1, 1, 0, 0, 0, 8, 21, 9,
- 1, 4, 5, 0, 0, 0, 33, 9, 0, 1, 1, 1, 0, 8, 21, 21,
- 21, 21, 34, 1, 35, 21, 21, 21, 9, 36, 0, 0, 37, 38, 1, 0,
- 39, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 8, 21, 9, 1, 0,
- 0, 0, 40, 0, 8, 21, 21, 21, 21, 21, 21, 21, 21, 9, 0, 1,
- 1, 1, 1, 8, 21, 21, 21, 9, 0, 0, 0, 41, 0, 42, 43, 0,
- 0, 0, 1, 44, 0, 0, 0, 45, 8, 9, 1, 0, 0, 0, 8, 21,
- 21, 21, 9, 0, 1, 0, 1, 1, 8, 21, 21, 9, 0, 4, 5, 8,
- 9, 1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 9, 10, 11, 11, 11, 11, 12, 13,
- 13, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21, 13, 22, 13, 13, 13,
- 13, 23, 24, 24, 25, 26, 13, 13, 13, 27, 28, 29, 13, 30, 31, 32,
- 33, 34, 35, 36, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 37, 7, 38, 39, 7, 40, 7, 7,
- 7, 41, 13, 42, 7, 7, 43, 7, 44, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
- 13, 13, 13, 13, 45, 0, 0, 1, 2, 2, 2, 3, 4, 5, 6, 7,
- 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
- 24, 25, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 37,
- 37, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
- 51, 52, 2, 2, 53, 54, 55, 56, 57, 58, 59, 59, 59, 59, 60, 59,
- 59, 59, 59, 59, 59, 59, 61, 61, 59, 59, 59, 59, 62, 63, 64, 65,
- 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 59, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 79, 70, 70, 70, 70, 80, 80, 80, 80, 80, 80, 80, 80, 80, 81,
- 82, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 32, 32,
- 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
- 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
- 32, 32, 32, 32, 32, 95, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 70, 70, 97, 98, 99,100,101,101,
- 102,103,104,105,106,107,108,109,110,111, 96,112,113,114,115,116,
- 117,118,119,119,120,121,122,123,124,125,126,127,128,129,130,131,
- 132, 96,133,134,135,136,137,138,139,140,141,142,143, 96,144,145,
- 96,146,147,148,149, 96,150,151,152,153,154,155,156, 96,157,158,
- 159,160, 96,161,162,163,164,164,164,164,164,164,164,165,166,164,
- 167, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96,168,169,169,169,169,169,169,169,169,170, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,171,171,
- 171,171,172, 96, 96, 96,173,173,173,173,174,175,176,177, 96, 96,
- 96, 96,178,179,180,181,182,182,182,182,182,182,182,182,182,182,
- 182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,
- 182,182,182,182,182,183,182,182,182,182,182,182,184,184,184,185,
- 186, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96,187,188,189,190,191,191,192, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,193,194,
- 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
- 96, 96, 96, 96,195,196, 59,197,198,199,200,201,202, 96,203,204,
- 205, 59, 59,206, 59,207,208,208,208,208,208,209, 96, 96, 96, 96,
- 96, 96, 96, 96,210, 96,211,212,213, 96, 96,214, 96, 96, 96,215,
- 96, 96, 96, 96, 96,216,217,218,219, 96, 96, 96, 96, 96,220,221,
- 222, 96,223,224, 96, 96,225,226, 59,227,228, 96, 59, 59, 59, 59,
- 59, 59, 59,229,230,231,232,233, 59, 59,234,235, 59,236, 96, 96,
- 96, 96, 96, 96, 96, 96, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70,237, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70,238, 70,239, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
- 70, 70, 70,240, 70, 70, 70, 70, 70, 70, 70, 70, 70,241, 70, 70,
- 70, 70,242, 96, 96, 96, 70, 70, 70, 70,243, 96, 96, 96, 96, 96,
- 96, 96, 96, 96, 96, 96, 70, 70, 70, 70, 70, 70,244, 70, 70, 70,
- 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,245, 96, 96,
- 96, 96, 96, 96, 96, 96,246, 96,247,248, 0, 1, 2, 2, 0, 1,
- 2, 2, 2, 3, 4, 5, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19,
- 19, 19, 19, 19, 19, 0, 0, 0, 19, 0, 19, 0, 0, 0, 0, 0,
- 26, 26, 1, 1, 1, 1, 9, 9, 9, 9, 0, 9, 9, 9, 2, 2,
- 9, 9, 9, 9, 0, 9, 2, 2, 2, 2, 9, 0, 9, 0, 9, 9,
- 9, 2, 9, 2, 9, 9, 9, 9, 2, 9, 9, 9, 55, 55, 55, 55,
- 55, 55, 6, 6, 6, 6, 6, 1, 1, 6, 2, 4, 4, 4, 4, 4,
- 4, 4, 4, 4, 4, 2, 2, 14, 14, 14, 14, 14, 14, 14, 14, 14,
- 14, 2, 2, 2, 2, 14, 14, 2, 2, 2, 3, 3, 3, 3, 3, 0,
- 3, 3, 0, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 1, 1, 1,
- 3, 3, 1, 3, 3, 3, 37, 37, 37, 37, 37, 37, 2, 37, 37, 37,
- 37, 2, 2, 37, 37, 37, 38, 38, 38, 38, 38, 38, 2, 2, 64, 64,
- 64, 64, 64, 64, 64, 2, 2, 64, 64, 64, 90, 90, 90, 90, 90, 90,
- 2, 2, 90, 90, 90, 2, 95, 95, 95, 95, 2, 2, 95, 2, 3, 3,
- 3, 2, 3, 3, 2, 2, 3, 3, 0, 3, 7, 7, 7, 7, 7, 1,
- 1, 1, 1, 7, 7, 7, 0, 0, 7, 7, 5, 5, 5, 5, 2, 5,
- 5, 5, 5, 2, 2, 5, 5, 2, 5, 5, 5, 2, 5, 2, 2, 2,
- 5, 5, 5, 5, 2, 2, 5, 5, 5, 2, 2, 2, 2, 5, 5, 5,
- 2, 5, 2, 11, 11, 11, 11, 11, 11, 2, 2, 2, 2, 11, 11, 2,
- 2, 11, 11, 11, 11, 11, 11, 2, 11, 11, 2, 11, 11, 2, 11, 11,
- 2, 2, 2, 11, 2, 2, 11, 2, 11, 2, 2, 2, 11, 11, 2, 10,
- 10, 10, 10, 10, 10, 10, 10, 10, 2, 10, 10, 2, 10, 10, 10, 10,
- 2, 2, 10, 2, 2, 2, 2, 2, 10, 10, 2, 21, 21, 21, 21, 21,
- 21, 21, 21, 2, 2, 21, 21, 2, 21, 21, 21, 21, 2, 2, 21, 21,
- 2, 21, 2, 2, 21, 21, 2, 2, 22, 22, 2, 22, 22, 22, 22, 22,
- 22, 2, 22, 2, 22, 22, 22, 22, 2, 2, 2, 22, 22, 2, 2, 2,
- 2, 22, 22, 2, 2, 2, 22, 22, 22, 22, 23, 23, 23, 23, 23, 2,
- 23, 23, 23, 23, 2, 2, 2, 23, 23, 2, 23, 23, 23, 2, 2, 23,
- 2, 2, 2, 2, 23, 23, 2, 2, 2, 23, 16, 16, 16, 16, 16, 2,
- 16, 16, 2, 16, 16, 16, 16, 16, 2, 2, 2, 16, 16, 2, 2, 2,
- 16, 16, 20, 20, 20, 20, 20, 2, 20, 20, 2, 2, 20, 20, 2, 36,
- 36, 36, 36, 36, 36, 36, 36, 36, 36, 2, 2, 2, 36, 36, 36, 36,
- 2, 36, 2, 36, 2, 2, 2, 2, 36, 2, 2, 2, 2, 36, 36, 2,
- 36, 2, 36, 2, 2, 2, 2, 24, 24, 24, 24, 24, 24, 24, 24, 24,
- 24, 2, 2, 2, 2, 0, 2, 18, 18, 2, 18, 2, 18, 18, 18, 18,
- 18, 2, 18, 18, 18, 18, 2, 18, 2, 18, 18, 18, 2, 2, 18, 2,
- 18, 2, 25, 25, 25, 25, 2, 25, 25, 25, 25, 2, 2, 2, 25, 2,
- 25, 25, 25, 0, 0, 0, 0, 25, 25, 2, 33, 33, 33, 33, 8, 8,
- 8, 8, 8, 8, 2, 8, 2, 8, 2, 2, 8, 8, 8, 0, 12, 12,
- 12, 12, 30, 30, 30, 30, 30, 2, 30, 30, 30, 30, 2, 2, 30, 30,
- 30, 2, 2, 30, 30, 30, 30, 2, 2, 2, 29, 29, 29, 29, 29, 29,
- 2, 2, 28, 28, 28, 28, 34, 34, 34, 34, 34, 2, 2, 2, 35, 35,
- 35, 35, 35, 35, 35, 0, 0, 0, 35, 35, 35, 2, 2, 2, 45, 45,
- 45, 45, 45, 45, 2, 2, 2, 2, 2, 45, 44, 44, 44, 44, 44, 0,
- 0, 2, 43, 43, 43, 43, 46, 46, 46, 46, 46, 2, 46, 46, 31, 31,
- 31, 31, 31, 31, 2, 2, 32, 32, 0, 0, 32, 0, 32, 32, 32, 32,
- 32, 32, 32, 32, 2, 2, 32, 2, 2, 2, 32, 32, 32, 2, 28, 28,
- 2, 2, 48, 48, 48, 48, 48, 48, 48, 2, 48, 2, 2, 2, 52, 52,
- 52, 52, 52, 52, 2, 2, 52, 2, 2, 2, 58, 58, 58, 58, 58, 58,
- 2, 2, 58, 58, 58, 2, 2, 2, 58, 58, 54, 54, 54, 54, 2, 2,
- 54, 54, 91, 91, 91, 91, 91, 91, 91, 2, 91, 2, 2, 91, 91, 91,
- 2, 2, 1, 1, 1, 2, 62, 62, 62, 62, 62, 2, 2, 2, 62, 62,
- 62, 2, 76, 76, 76, 76, 93, 93, 93, 93, 70, 70, 70, 70, 2, 2,
- 2, 70, 70, 70, 2, 2, 2, 70, 70, 70, 73, 73, 73, 73, 6, 2,
- 2, 2, 8, 8, 8, 2, 2, 8, 8, 8, 1, 1, 1, 0, 1, 0,
- 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 2, 19, 19,
- 9, 9, 9, 9, 9, 6, 19, 9, 9, 9, 9, 9, 19, 19, 9, 9,
- 9, 19, 6, 19, 19, 19, 19, 19, 19, 9, 9, 9, 2, 2, 2, 9,
- 2, 9, 2, 9, 9, 9, 1, 1, 0, 0, 0, 2, 0, 0, 0, 19,
- 2, 2, 0, 0, 0, 19, 0, 0, 0, 2, 19, 2, 2, 2, 0, 2,
- 2, 2, 1, 2, 2, 2, 0, 0, 9, 0, 0, 0, 19, 19, 27, 27,
- 27, 27, 2, 2, 0, 0, 0, 0, 2, 0, 56, 56, 56, 56, 2, 55,
- 55, 55, 61, 61, 61, 61, 2, 2, 2, 61, 61, 2, 2, 2, 0, 0,
- 2, 2, 13, 13, 13, 13, 13, 13, 2, 13, 13, 13, 2, 2, 0, 13,
- 0, 13, 0, 13, 13, 13, 13, 13, 1, 1, 1, 1, 12, 12, 2, 15,
- 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 2, 1, 1, 0, 0, 15,
- 15, 15, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 2, 26,
- 26, 26, 26, 26, 26, 26, 2, 12, 12, 12, 12, 12, 12, 2, 12, 12,
- 12, 0, 39, 39, 39, 39, 39, 2, 2, 2, 39, 39, 39, 2, 86, 86,
- 86, 86, 77, 77, 77, 77, 79, 79, 79, 79, 19, 19, 19, 2, 19, 19,
- 2, 19, 2, 19, 19, 19, 19, 19, 2, 2, 2, 2, 19, 19, 60, 60,
- 60, 60, 60, 2, 2, 2, 65, 65, 65, 65, 75, 75, 75, 75, 75, 75,
- 2, 2, 2, 2, 75, 75, 69, 69, 69, 69, 69, 69, 0, 69, 74, 74,
- 74, 74, 2, 2, 2, 74, 12, 2, 2, 2, 84, 84, 84, 84, 84, 84,
- 2, 0, 84, 84, 2, 2, 2, 2, 84, 84, 33, 33, 33, 2, 68, 68,
- 68, 68, 68, 68, 68, 2, 68, 68, 2, 2, 92, 92, 92, 92, 92, 92,
- 92, 2, 2, 2, 2, 92, 87, 87, 87, 87, 87, 87, 87, 2, 19, 9,
- 19, 19, 19, 19, 0, 0, 87, 87, 2, 2, 2, 2, 2, 12, 2, 2,
- 2, 4, 14, 2, 14, 2, 14, 14, 2, 14, 14, 2, 14, 14, 2, 2,
- 2, 3, 3, 3, 0, 0, 2, 2, 3, 3, 1, 1, 6, 6, 3, 2,
- 3, 3, 3, 2, 2, 0, 2, 0, 0, 0, 0, 0, 17, 17, 17, 17,
- 0, 0, 2, 2, 12, 12, 49, 49, 49, 49, 2, 49, 49, 49, 49, 49,
- 49, 2, 49, 49, 2, 49, 49, 49, 2, 2, 9, 2, 2, 2, 0, 1,
- 2, 2, 71, 71, 71, 71, 71, 2, 2, 2, 67, 67, 67, 67, 67, 2,
- 2, 2, 42, 42, 42, 42, 2, 42, 42, 42, 41, 41, 41, 41, 41, 41,
- 41, 2,118,118,118,118,118,118,118, 2, 53, 53, 53, 53, 53, 53,
- 2, 53, 59, 59, 59, 59, 59, 59, 2, 2, 40, 40, 40, 40, 51, 51,
- 51, 51, 50, 50, 50, 50, 50, 50, 2, 2,135,135,135,135,106,106,
- 106,106,104,104,104,104, 2, 2, 2,104,161,161,161,161,161,161,
- 161, 2,161,161, 2,161,161, 2, 2, 2,110,110,110,110,110,110,
- 110, 2,110,110, 2, 2, 19, 2, 19, 19, 47, 47, 47, 47, 47, 47,
- 2, 2, 47, 2, 47, 47, 47, 47, 2, 47, 47, 2, 2, 2, 47, 2,
- 2, 47, 81, 81, 81, 81, 81, 81, 2, 81,120,120,120,120,116,116,
- 116,116,116,116,116, 2, 2, 2, 2,116,128,128,128,128,128,128,
- 128, 2,128,128, 2, 2, 2, 2, 2,128, 66, 66, 66, 66, 2, 2,
- 2, 66, 72, 72, 72, 72, 72, 72, 2, 2, 2, 2, 2, 72, 98, 98,
- 98, 98, 97, 97, 97, 97, 2, 2, 97, 97, 57, 57, 57, 57, 2, 57,
- 57, 2, 2, 57, 57, 57, 57, 57, 2, 2, 57, 57, 57, 2, 2, 2,
- 2, 57, 57, 2, 2, 2, 88, 88, 88, 88,117,117,117,117,112,112,
- 112,112,112,112,112, 2, 2, 2, 2,112, 78, 78, 78, 78, 78, 78,
- 2, 2, 2, 78, 78, 78, 83, 83, 83, 83, 83, 83, 2, 2, 82, 82,
- 82, 82, 82, 82, 82, 2,122,122,122,122,122,122, 2, 2, 2,122,
- 122,122,122, 2, 2, 2, 89, 89, 89, 89, 89, 2, 2, 2,130,130,
- 130,130,130,130,130, 2, 2, 2,130,130,144,144,144,144,144,144,
- 2, 2,156,156,156,156,156,156, 2,156,156,156, 2, 2, 2, 3,
- 3, 3,147,147,147,147,148,148,148,148,148,148, 2, 2,158,158,
- 158,158,158,158, 2, 2,153,153,153,153,149,149,149,149,149,149,
- 149, 2, 94, 94, 94, 94, 94, 94, 2, 2, 2, 2, 94, 94, 2, 2,
- 2, 94, 85, 85, 85, 85, 85, 85, 85, 2, 2, 85, 2, 2,101,101,
- 101,101,101, 2, 2, 2,101,101, 2, 2, 96, 96, 96, 96, 96, 2,
- 96, 96,111,111,111,111,111,111,111, 2,100,100,100,100,108,108,
- 108,108,108,108, 2,108,108,108, 2, 2,129,129,129,129,129,129,
- 129, 2,129, 2,129,129,129,129, 2,129,129,129, 2, 2,109,109,
- 109,109,109,109,109, 2,109,109, 2, 2,107,107,107,107, 2,107,
- 107,107,107, 2, 2,107,107, 2,107,107,107,107, 2, 1,107,107,
- 2, 2,107, 2, 2, 2, 2, 2, 2,107, 2, 2,107,107,137,137,
- 137,137, 2,137,137,137,137,137, 2, 2,124,124,124,124,124,124,
- 2, 2,123,123,123,123,123,123, 2, 2,114,114,114,114,114, 2,
- 2, 2,114,114, 2, 2,102,102,102,102,102,102, 2, 2,126,126,
- 126,126,126,126,126, 2, 2,126,126,126,142,142,142,142,125,125,
- 125,125,125,125,125, 2, 2, 2, 2,125,154,154,154,154,154,154,
- 154, 2, 2,154, 2, 2, 2,154,154, 2,154,154, 2,154,154, 2,
- 2,154,154,154, 2, 2,150,150,150,150, 2, 2,150,150,150, 2,
- 2, 2,141,141,141,141,140,140,140,140,140,140,140, 2,121,121,
- 121,121,121, 2, 2, 2, 7, 7, 2, 2,133,133,133,133,133, 2,
- 133,133,133,133,133, 2,133,133, 2, 2,133, 2, 2, 2,134,134,
- 134,134, 2, 2,134,134, 2,134,134,134,134,134,134, 2,138,138,
- 138,138,138,138,138, 2,138,138, 2,138, 2, 2,138, 2,138,138,
- 2, 2,143,143,143,143,143,143, 2,143,143, 2,143,143,143,143,
- 143, 2,143, 2, 2, 2,143,143, 2, 2,145,145,145,145,145, 2,
- 2, 2,163,163,163,163,163, 2,163,163,163,163,163, 2, 2, 2,
- 163,163,163,163, 2, 2, 86, 2, 2, 2, 63, 63, 63, 63, 63, 63,
- 2, 2, 63, 63, 63, 2, 63, 2, 2, 2,157,157,157,157,157,157,
- 157, 2, 80, 80, 80, 80, 80, 80, 2, 2,127,127,127,127,127,127,
- 127, 2, 79, 2, 2, 2,115,115,115,115,115,115,115, 2,115,115,
- 2, 2, 2, 2,115,115,159,159,159,159,159,159,159, 2,159,159,
- 2, 2,103,103,103,103,103,103, 2, 2,119,119,119,119,119,119,
- 2, 2,119,119, 2,119, 2,119,119,119,146,146,146,146,146,146,
- 146, 2, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 99,136,139,
- 13, 13,155, 2, 2, 2,136,136,136,136,155,155,155,155,155,155,
- 2, 2,136, 2, 2, 2, 2, 17, 17, 17, 2, 17, 17, 2, 17, 15,
- 15, 15, 17, 17, 17, 2, 2, 2, 15, 2, 2, 17, 2, 2,139,139,
- 139,139,105,105,105,105,105,105,105, 2,105, 2, 2, 2,105,105,
- 2, 2, 1, 1, 2, 2, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0,
- 1, 1, 2, 2, 0, 2, 2, 0, 0, 2, 0, 2, 0, 2,131,131,
- 131,131, 2, 2, 2,131, 2,131,131,131, 56, 56, 56, 2, 56, 2,
- 2, 56, 56, 56, 2, 56, 56, 2, 56, 56, 6, 6, 2, 2, 2, 2,
- 2, 6,151,151,151,151,151, 2, 2, 2,151,151, 2, 2, 2, 2,
- 151,151,160,160,160,160,160,160,160, 2,152,152,152,152,152,152,
- 2, 2, 2, 2, 2,152,164,164,164,164,164,164, 2, 2, 2, 30,
- 30, 2,113,113,113,113,113, 2, 2,113,113,113,113, 2,132,132,
- 132,132,132,132, 2, 2, 2, 2,132,132, 2, 3, 3, 2, 3, 2,
- 2, 3, 2, 3, 2, 3, 2, 2, 3, 2, 3, 2, 3, 2, 3, 3,
- 2, 3, 15, 0, 0, 2, 13, 2, 2, 2, 13, 13, 13, 2, 2, 0,
- 2, 2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 10,
- 9, 11, 12, 13, 9, 9, 9, 14, 9, 9, 15, 9, 9, 9, 9, 9,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,177, 0, 1, 2, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 3, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 6, 7, 8, 3,
+ 3, 3, 3, 3, 9, 10, 11, 12, 13, 3, 3, 3, 3, 3, 3, 3,
+ 3, 14, 3, 15, 3, 3, 3, 3, 3, 3, 16, 17, 18, 19, 20, 21,
+ 3, 3, 3, 22, 23, 24, 3, 3, 3, 3, 3, 3, 25, 3, 3, 3,
+ 3, 3, 3, 3, 3, 26, 3, 3, 27, 28, 0, 1, 0, 0, 0, 0,
+ 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 4, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 8, 9, 0, 0, 0, 0, 0, 0, 9, 0, 9, 0, 0,
+ 0, 0, 0, 0, 0, 10, 11, 12, 13, 0, 0, 14, 15, 16, 6, 0,
+ 17, 18, 19, 19, 19, 20, 21, 22, 23, 24, 19, 25, 0, 26, 27, 19,
+ 19, 28, 29, 30, 0, 31, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0,
+ 0, 19, 28, 0, 32, 33, 9, 34, 35, 19, 0, 0, 36, 37, 38, 39,
+ 40, 19, 0, 41, 42, 43, 44, 31, 0, 1, 45, 42, 0, 0, 0, 0,
+ 0, 32, 14, 14, 0, 0, 0, 0, 14, 0, 0, 46, 47, 47, 47, 47,
+ 48, 49, 47, 47, 47, 47, 50, 51, 52, 53, 43, 21, 0, 0, 0, 0,
+ 0, 0, 0, 54, 6, 55, 0, 14, 19, 1, 0, 0, 0, 0, 56, 57,
+ 0, 0, 0, 0, 0, 19, 58, 31, 0, 0, 0, 0, 0, 0, 0, 59,
+ 14, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 60,
+ 61, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 3,
+ 0, 4, 5, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 1, 0,
+ 0, 8, 9, 0, 8, 9, 0, 0, 0, 0, 8, 9, 10, 11, 12, 0,
+ 0, 0, 13, 0, 0, 0, 0, 14, 15, 16, 17, 0, 0, 0, 1, 0,
+ 0, 18, 19, 0, 0, 0, 20, 0, 0, 0, 1, 1, 1, 1, 0, 1,
+ 1, 1, 1, 1, 1, 1, 0, 8, 21, 9, 0, 0, 22, 0, 0, 0,
+ 0, 1, 0, 23, 24, 25, 0, 0, 26, 0, 0, 0, 8, 21, 27, 0,
+ 1, 0, 0, 1, 1, 1, 1, 0, 1, 28, 29, 30, 0, 31, 32, 20,
+ 1, 1, 0, 0, 0, 8, 21, 9, 1, 4, 5, 0, 0, 0, 33, 9,
+ 0, 1, 1, 1, 0, 8, 21, 21, 21, 21, 34, 1, 35, 21, 21, 21,
+ 9, 36, 0, 0, 37, 38, 1, 0, 39, 0, 0, 0, 1, 0, 1, 0,
+ 0, 0, 0, 8, 21, 9, 1, 0, 0, 0, 40, 0, 8, 21, 21, 21,
+ 21, 21, 21, 21, 21, 9, 0, 1, 1, 1, 1, 8, 21, 21, 21, 9,
+ 0, 0, 0, 41, 0, 42, 43, 0, 0, 0, 1, 44, 0, 0, 0, 45,
+ 8, 9, 1, 0, 0, 0, 8, 21, 21, 21, 9, 0, 1, 0, 1, 1,
+ 8, 21, 21, 9, 0, 4, 5, 8, 9, 1, 0, 0, 0, 1, 2, 3,
+ 4, 5, 5, 5, 5, 5, 6, 7, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 9, 16, 17, 18, 9, 19, 20, 21, 22, 23, 24, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 25, 26, 27, 5, 28, 29, 5, 30, 31, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 16, 17,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 19, 20, 9, 21, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 32, 0, 0, 1,
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 1, 29, 30, 31,
+ 32, 32, 33, 32, 32, 32, 34, 32, 32, 35, 36, 37, 38, 39, 40, 41,
+ 42, 43, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 44, 44, 44, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 46, 46,
+ 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 44, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
+ 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 95,
+ 95, 96, 97, 98, 56, 56, 56, 56, 56, 56, 56, 56, 56, 99,100,100,
+ 100,100,101,100,100,100,100,100,100,100,100,100,100,100,100,100,
+ 100,102,103,103,104, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,105,
+ 56, 56, 56, 56, 56, 56,106,106,107,108, 56,109,110,111,112,112,
+ 112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,112,
+ 112,112,112,112,112,113,112,112,112,114,115,116, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,117,118,119,
+ 120, 56, 56, 56, 56, 56, 56, 56, 56, 56,121, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,122, 32,123,124,125,126,
+ 127,128,129,130,131,132,133,133,134, 56, 56, 56, 56,135,136,137,
+ 138, 56,139,140, 56,141,142,143, 56, 56,144,145,146, 56,147,148,
+ 149, 32, 32, 32,150,151,152, 32,153,154, 56, 56, 56, 56, 44, 44,
+ 44, 44, 44, 44,155, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+ 44, 44, 44, 44, 44,156,157, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,158, 44, 44, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44,159, 44, 44,160, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 44, 44,161, 56, 56, 56, 56, 56, 44, 44,
+ 44,162, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+ 44,163, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,164,165,
+ 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 0, 0, 0, 0, 0, 0, 0,
+ 19, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 19, 19, 0, 19, 0,
+ 0, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 0, 0, 0, 0, 0,
+ 26, 26, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9,
+ 9, 9, 0, 9, 9, 9, 2, 2, 9, 9, 9, 9, 0, 9, 2, 2,
+ 2, 2, 9, 0, 9, 0, 9, 9, 9, 2, 9, 2, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 1, 6, 2, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 2, 4, 4, 4, 2, 2, 4, 4, 4, 2, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 2, 2,
+ 2, 2, 2, 2, 2, 2, 14, 14, 14, 2, 2, 2, 2, 14, 14, 14,
+ 14, 14, 14, 2, 2, 2, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3,
+ 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 3,
+ 3, 3, 3, 3, 3, 3, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+ 37, 37, 37, 37, 2, 37, 37, 37, 37, 2, 2, 37, 37, 37, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 2, 2, 2, 2, 2, 2, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 2, 2, 64, 64, 64, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 2, 2, 90, 90,
+ 90, 90, 90, 90, 90, 2, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 2, 2, 95, 2, 37, 37, 37, 2, 2, 2, 2, 2, 3, 3,
+ 3, 3, 3, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 3,
+ 0, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1,
+ 1, 1, 1, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 5, 5,
+ 5, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 2, 2, 5, 5, 2,
+ 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 2,
+ 5, 5, 5, 5, 5, 5, 5, 2, 5, 2, 2, 2, 5, 5, 5, 5,
+ 2, 2, 5, 5, 5, 5, 5, 2, 2, 5, 5, 5, 5, 2, 2, 2,
+ 2, 2, 2, 2, 2, 5, 2, 2, 2, 2, 5, 5, 2, 5, 5, 5,
+ 5, 5, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 2, 2, 11,
+ 11, 11, 2, 11, 11, 11, 11, 11, 11, 2, 2, 2, 2, 11, 11, 2,
+ 2, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 2,
+ 11, 11, 11, 11, 11, 11, 11, 2, 11, 11, 2, 11, 11, 2, 11, 11,
+ 2, 2, 11, 2, 11, 11, 11, 2, 2, 11, 11, 11, 2, 2, 2, 11,
+ 2, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11, 2, 11, 2, 2, 2,
+ 2, 2, 2, 2, 11, 11, 11, 11, 11, 11, 11, 11, 11, 2, 2, 10,
+ 10, 10, 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2, 10, 10, 10,
+ 2, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2,
+ 10, 10, 10, 10, 10, 10, 10, 2, 10, 10, 2, 10, 10, 10, 10, 10,
+ 2, 2, 10, 10, 10, 10, 10, 10, 2, 10, 10, 10, 2, 2, 10, 2,
+ 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 2, 2, 10, 10, 10, 10,
+ 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 2, 21,
+ 21, 21, 2, 21, 21, 21, 21, 21, 21, 21, 21, 2, 2, 21, 21, 2,
+ 2, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 2,
+ 21, 21, 21, 21, 21, 21, 21, 2, 21, 21, 2, 21, 21, 21, 21, 21,
+ 2, 2, 21, 21, 21, 21, 21, 2, 2, 21, 21, 21, 2, 2, 2, 2,
+ 2, 2, 2, 21, 21, 21, 2, 2, 2, 2, 21, 21, 2, 21, 21, 21,
+ 21, 21, 2, 2, 21, 21, 2, 2, 22, 22, 2, 22, 22, 22, 22, 22,
+ 22, 2, 2, 2, 22, 22, 22, 2, 22, 22, 22, 22, 2, 2, 2, 22,
+ 22, 2, 22, 2, 22, 22, 2, 2, 2, 22, 22, 2, 2, 2, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 2, 2, 2, 2, 22, 22, 22, 2,
+ 2, 2, 2, 2, 2, 22, 2, 2, 2, 2, 2, 2, 22, 22, 22, 22,
+ 22, 2, 2, 2, 2, 2, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 2, 23, 23, 23, 2, 23, 23, 23, 23, 23, 23, 23, 23,
+ 2, 2, 23, 23, 23, 23, 23, 2, 23, 23, 23, 23, 2, 2, 2, 2,
+ 2, 2, 2, 23, 23, 2, 23, 23, 23, 2, 2, 23, 2, 2, 23, 23,
+ 23, 23, 2, 2, 23, 23, 2, 2, 2, 2, 2, 2, 2, 23, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 16, 16, 16, 2,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 16, 16, 16, 16, 16,
+ 2, 2, 16, 16, 16, 16, 16, 2, 16, 16, 16, 16, 2, 2, 2, 2,
+ 2, 2, 2, 16, 16, 2, 16, 16, 16, 16, 2, 2, 16, 16, 2, 16,
+ 16, 16, 2, 2, 2, 2, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 2, 20, 20, 20, 2, 20, 20, 20, 20, 20, 20, 2, 2,
+ 2, 2, 20, 20, 20, 20, 20, 20, 20, 20, 2, 2, 20, 20, 2, 36,
+ 36, 36, 2, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 2, 2, 2, 36, 36, 36, 36, 36, 36, 36, 36,
+ 2, 36, 36, 36, 36, 36, 36, 36, 36, 36, 2, 36, 2, 2, 2, 2,
+ 36, 2, 2, 2, 2, 36, 36, 36, 36, 36, 36, 2, 36, 2, 2, 2,
+ 2, 2, 2, 2, 36, 36, 2, 2, 36, 36, 36, 2, 2, 2, 2, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 2, 2, 2, 2, 0, 24, 24, 24, 24, 2, 2, 2, 2, 2, 18,
+ 18, 2, 18, 2, 18, 18, 18, 18, 18, 2, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 2, 18, 2, 18, 18, 18,
+ 18, 18, 18, 18, 2, 2, 18, 18, 18, 18, 18, 2, 18, 2, 18, 18,
+ 18, 18, 18, 18, 18, 2, 18, 18, 2, 2, 18, 18, 18, 18, 25, 25,
+ 25, 25, 25, 25, 25, 25, 2, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 2, 2, 2, 25, 25, 25, 25, 25, 2, 25, 25, 25, 25,
+ 25, 25, 25, 0, 0, 0, 0, 25, 25, 2, 2, 2, 2, 2, 33, 33,
+ 33, 33, 33, 33, 33, 33, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 2, 8, 2, 2, 2, 2, 2, 8, 2, 2, 8, 8,
+ 8, 0, 8, 8, 8, 8, 12, 12, 12, 12, 12, 12, 12, 12, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 2, 30, 30, 30, 30, 2, 2, 30, 30,
+ 30, 30, 30, 30, 30, 2, 30, 30, 30, 2, 2, 30, 30, 30, 30, 30,
+ 30, 30, 30, 2, 2, 2, 30, 30, 2, 2, 2, 2, 2, 2, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 2, 2, 28, 28,
+ 28, 28, 28, 28, 28, 28, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 2, 2, 2, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35,
+ 35, 0, 0, 0, 35, 35, 35, 2, 2, 2, 2, 2, 2, 2, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 45, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+ 44, 44, 44, 0, 0, 2, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
+ 43, 43, 2, 2, 2, 2, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
+ 46, 46, 46, 2, 46, 46, 46, 2, 46, 46, 2, 2, 2, 2, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 2, 2, 31, 31,
+ 2, 2, 2, 2, 2, 2, 32, 32, 0, 0, 32, 0, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32, 2, 2, 2, 2, 2, 2, 32, 2,
+ 2, 2, 2, 2, 2, 2, 32, 32, 32, 2, 2, 2, 2, 2, 28, 28,
+ 28, 28, 28, 28, 2, 2, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 2, 48, 48, 48, 48, 2, 2, 2, 2, 48, 2,
+ 2, 2, 48, 48, 48, 48, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
+ 52, 52, 52, 52, 2, 2, 52, 52, 52, 52, 52, 2, 2, 2, 58, 58,
+ 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 2, 2, 2, 2, 58, 58,
+ 2, 2, 2, 2, 2, 2, 58, 58, 58, 2, 2, 2, 58, 58, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 2, 2, 54, 54, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 2, 91, 91,
+ 91, 91, 91, 2, 2, 91, 91, 91, 2, 2, 2, 2, 2, 2, 91, 91,
+ 91, 91, 91, 91, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 2, 62, 62, 76, 76,
+ 76, 76, 76, 76, 76, 76, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,
+ 93, 93, 2, 2, 2, 2, 2, 2, 2, 2, 93, 93, 93, 93, 70, 70,
+ 70, 70, 70, 70, 70, 70, 2, 2, 2, 70, 70, 70, 70, 70, 70, 70,
+ 2, 2, 2, 70, 70, 70, 73, 73, 73, 73, 73, 73, 73, 73, 6, 6,
+ 6, 2, 2, 2, 2, 2, 8, 8, 8, 2, 2, 8, 8, 8, 1, 1,
+ 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1,
+ 0, 2, 2, 2, 2, 2, 19, 19, 19, 19, 19, 19, 9, 9, 9, 9,
+ 9, 6, 19, 19, 19, 19, 19, 19, 19, 19, 19, 9, 9, 9, 9, 9,
+ 19, 19, 19, 19, 9, 9, 9, 9, 9, 19, 19, 19, 19, 19, 6, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 9, 9, 9,
+ 9, 9, 9, 9, 2, 2, 2, 9, 2, 9, 2, 9, 2, 9, 9, 9,
+ 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 9, 9,
+ 9, 9, 2, 9, 9, 9, 2, 2, 9, 9, 9, 2, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 2, 0, 0, 0, 19, 2, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 2, 19, 19,
+ 19, 19, 19, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 1, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0,
+ 19, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 19, 0,
+ 0, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0,
+ 0, 2, 2, 2, 2, 2, 27, 27, 27, 27, 27, 27, 27, 27, 0, 0,
+ 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 56, 56,
+ 56, 56, 56, 56, 56, 56, 55, 55, 55, 55, 2, 2, 2, 2, 2, 55,
+ 55, 55, 55, 55, 55, 55, 61, 61, 61, 61, 61, 61, 61, 61, 2, 2,
+ 2, 2, 2, 2, 2, 61, 61, 2, 2, 2, 2, 2, 2, 2, 0, 0,
+ 0, 0, 0, 0, 2, 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 2, 2, 2, 2, 13, 13,
+ 13, 13, 13, 13, 2, 2, 0, 0, 0, 0, 0, 13, 0, 13, 0, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1, 1, 12, 12, 13, 13,
+ 13, 13, 0, 0, 0, 0, 2, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 2, 1,
+ 1, 0, 0, 15, 15, 15, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 17, 17, 17, 2, 2,
+ 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 2, 2, 2,
+ 2, 2, 2, 2, 2, 0, 12, 12, 12, 12, 12, 12, 12, 0, 17, 17,
+ 17, 17, 17, 17, 17, 0, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 2, 2, 2, 39, 39, 39, 39, 39, 39, 39, 2, 86, 86,
+ 86, 86, 86, 86, 86, 86, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+ 77, 77, 2, 2, 2, 2, 79, 79, 79, 79, 79, 79, 79, 79, 0, 0,
+ 19, 19, 19, 19, 19, 19, 0, 0, 0, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 2, 2, 19, 19, 2, 19, 2, 19, 19, 19, 2, 2,
+ 19, 19, 19, 19, 19, 19, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+ 60, 60, 60, 2, 2, 2, 65, 65, 65, 65, 65, 65, 65, 65, 75, 75,
+ 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 2, 2, 2, 2,
+ 2, 2, 2, 2, 75, 75, 75, 75, 2, 2, 2, 2, 2, 2, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 0, 69, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 74, 12, 12, 12, 12, 12, 2, 2, 2, 84, 84,
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 2, 0, 84, 84,
+ 2, 2, 2, 2, 84, 84, 33, 33, 33, 33, 33, 33, 33, 2, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 2, 68, 68,
+ 68, 68, 68, 68, 2, 2, 68, 68, 2, 2, 68, 68, 68, 68, 92, 92,
+ 92, 92, 92, 92, 92, 92, 92, 92, 92, 2, 2, 2, 2, 2, 2, 2,
+ 2, 92, 92, 92, 92, 92, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87,
+ 87, 87, 87, 87, 87, 2, 2, 30, 30, 30, 30, 30, 30, 2, 19, 19,
+ 19, 0, 19, 19, 19, 19, 19, 19, 19, 19, 19, 9, 19, 19, 19, 19,
+ 0, 0, 2, 2, 2, 2, 87, 87, 87, 87, 87, 87, 2, 2, 87, 87,
+ 2, 2, 2, 2, 2, 2, 12, 12, 12, 12, 2, 2, 2, 2, 2, 2,
+ 2, 12, 12, 12, 12, 12, 13, 13, 2, 2, 2, 2, 2, 2, 19, 19,
+ 19, 19, 19, 19, 19, 2, 2, 2, 2, 4, 4, 4, 4, 4, 2, 2,
+ 2, 2, 2, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 2, 14, 14,
+ 14, 14, 14, 2, 14, 2, 14, 14, 2, 14, 14, 2, 14, 14, 3, 3,
+ 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 0, 0, 2, 2, 3, 3, 3, 3, 3, 3, 2, 2,
+ 2, 2, 2, 2, 2, 3, 1, 1, 1, 1, 1, 1, 6, 6, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3,
+ 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 2, 2, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17,
+ 17, 17, 17, 17, 0, 0, 2, 2, 12, 12, 12, 12, 12, 12, 2, 2,
+ 12, 12, 12, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 2, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 2, 49, 49, 49, 2, 49, 49, 2, 49, 49, 49,
+ 49, 49, 49, 49, 2, 2, 49, 49, 49, 2, 2, 2, 2, 2, 0, 0,
+ 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0,
+ 0, 0, 0, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 0, 0,
+ 0, 0, 0, 1, 2, 2, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 2, 2, 2, 67, 67, 67, 67, 67, 67, 67, 67, 67, 2,
+ 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 42, 42,
+ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 42, 42, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
+ 41, 2, 2, 2, 2, 2,118,118,118,118,118,118,118,118,118,118,
+ 118, 2, 2, 2, 2, 2, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 2, 53, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+ 59, 59, 2, 2, 2, 2, 59, 59, 59, 59, 59, 59, 2, 2, 40, 40,
+ 40, 40, 40, 40, 40, 40, 51, 51, 51, 51, 51, 51, 51, 51, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 2, 2, 50, 50,
+ 2, 2, 2, 2, 2, 2,135,135,135,135,135,135,135,135,135,135,
+ 135,135, 2, 2, 2, 2,106,106,106,106,106,106,106,106,104,104,
+ 104,104,104,104,104,104,104,104,104,104, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2,104,161,161,161,161,161,161,161,161,161,161,
+ 161, 2,161,161,161,161,161,161,161, 2,161,161, 2,161,161,161,
+ 2,161,161,161,161,161,161,161, 2,161,161, 2, 2, 2,170,170,
+ 170,170,170,170,170,170,170,170,170,170, 2, 2, 2, 2,110,110,
+ 110,110,110,110,110,110,110,110,110,110,110,110,110, 2,110,110,
+ 110,110,110,110, 2, 2, 19, 19, 19, 19, 19, 19, 2, 19, 19, 2,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 2, 2, 2, 2, 2, 47, 47,
+ 47, 47, 47, 47, 2, 2, 47, 2, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 2, 47, 47, 2,
+ 2, 2, 47, 2, 2, 47, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 2, 81,120,120,120,120,120,120,120,120,116,116,
+ 116,116,116,116,116,116,116,116,116,116,116,116,116, 2, 2, 2,
+ 2, 2, 2, 2, 2,116,128,128,128,128,128,128,128,128,128,128,
+ 128, 2,128,128, 2, 2, 2, 2, 2,128,128,128,128,128, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 2, 2, 2, 66, 72, 72,
+ 72, 72, 72, 72, 72, 72, 72, 72, 2, 2, 2, 2, 2, 72, 98, 98,
+ 98, 98, 98, 98, 98, 98, 97, 97, 97, 97, 97, 97, 97, 97, 2, 2,
+ 2, 2, 97, 97, 97, 97, 2, 2, 97, 97, 97, 97, 97, 97, 57, 57,
+ 57, 57, 2, 57, 57, 2, 2, 2, 2, 2, 57, 57, 57, 57, 57, 57,
+ 57, 57, 2, 57, 57, 57, 2, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+ 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 2, 2, 57, 57,
+ 57, 2, 2, 2, 2, 57, 57, 2, 2, 2, 2, 2, 2, 2, 88, 88,
+ 88, 88, 88, 88, 88, 88,117,117,117,117,117,117,117,117,112,112,
+ 112,112,112,112,112,112,112,112,112,112,112,112,112, 2, 2, 2,
+ 2,112,112,112,112,112, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78,
+ 78, 78, 78, 78, 2, 2, 2, 78, 78, 78, 78, 78, 78, 78, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 2, 2, 82, 82,
+ 82, 82, 82, 82, 82, 82, 82, 82, 82, 2, 2, 2, 2, 2,122,122,
+ 122,122,122,122,122,122,122,122, 2, 2, 2, 2, 2, 2, 2,122,
+ 122,122,122, 2, 2, 2, 2,122,122,122,122,122,122,122, 89, 89,
+ 89, 89, 89, 89, 89, 89, 89, 2, 2, 2, 2, 2, 2, 2,130,130,
+ 130,130,130,130,130,130,130,130,130, 2, 2, 2, 2, 2, 2, 2,
+ 130,130,130,130,130,130,144,144,144,144,144,144,144,144,144,144,
+ 2, 2, 2, 2, 2, 2,165,165,165,165,165,165,165,165,165,165,
+ 165,165,165,165, 2, 2, 2,165,165,165,165,165,165,165, 2, 2,
+ 2, 2, 2, 2,165,165,156,156,156,156,156,156,156,156,156,156,
+ 2,156,156,156, 2, 2,156,156, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,147,147,
+ 147,147,147,147,147,147,148,148,148,148,148,148,148,148,148,148,
+ 2, 2, 2, 2, 2, 2,158,158,158,158,158,158,158,158,158,158,
+ 2, 2, 2, 2, 2, 2,153,153,153,153,153,153,153,153,153,153,
+ 153,153, 2, 2, 2, 2,149,149,149,149,149,149,149,149,149,149,
+ 149,149,149,149,149, 2, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94,
+ 94, 94, 94, 94, 2, 2, 2, 2, 94, 94, 94, 94, 94, 94, 2, 2,
+ 2, 2, 2, 2, 2, 94, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 85, 2, 2,101,101,
+ 101,101,101,101,101,101,101, 2, 2, 2, 2, 2, 2, 2,101,101,
+ 2, 2, 2, 2, 2, 2, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
+ 96, 96, 96, 2, 96, 96,111,111,111,111,111,111,111,111,111,111,
+ 111,111,111,111,111, 2,100,100,100,100,100,100,100,100, 2, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 2, 2, 2,108,108,
+ 108,108,108,108,108,108,108,108, 2,108,108,108,108,108,108,108,
+ 2, 2, 2, 2, 2, 2,129,129,129,129,129,129,129, 2,129, 2,
+ 129,129,129,129, 2,129,129,129,129,129,129,129,129,129,129,129,
+ 129,129,129,129, 2,129,129,129, 2, 2, 2, 2, 2, 2,109,109,
+ 109,109,109,109,109,109,109,109,109, 2, 2, 2, 2, 2,109,109,
+ 2, 2, 2, 2, 2, 2,107,107,107,107, 2,107,107,107,107,107,
+ 107,107,107, 2, 2,107,107, 2, 2,107,107,107,107,107,107,107,
+ 107,107,107,107,107,107,107, 2,107,107,107,107,107,107,107, 2,
+ 107,107, 2,107,107,107,107,107, 2, 1,107,107,107,107,107, 2,
+ 2,107,107,107, 2, 2,107, 2, 2, 2, 2, 2, 2,107, 2, 2,
+ 2, 2, 2,107,107,107,107,107,107,107, 2, 2,107,107,107,107,
+ 107,107,107, 2, 2, 2,171,171,171,171,171,171,171,171,171,171,
+ 2,171, 2, 2,171, 2,171,171,171,171,171,171, 2,171,171, 2,
+ 171, 2, 2,171, 2,171,171,171,171, 2,171,171,171,171,171, 2,
+ 2, 2, 2, 2, 2, 2, 2,171,171, 2, 2, 2, 2, 2,137,137,
+ 137,137,137,137,137,137,137,137,137,137, 2,137,137,137,137,137,
+ 2, 2, 2, 2, 2, 2,124,124,124,124,124,124,124,124,124,124,
+ 2, 2, 2, 2, 2, 2,123,123,123,123,123,123,123,123,123,123,
+ 123,123,123,123, 2, 2,114,114,114,114,114,114,114,114,114,114,
+ 114,114,114, 2, 2, 2,114,114, 2, 2, 2, 2, 2, 2, 32, 32,
+ 32, 32, 32, 2, 2, 2,102,102,102,102,102,102,102,102,102,102,
+ 2, 2, 2, 2, 2, 2, 33, 33, 33, 33, 2, 2, 2, 2,126,126,
+ 126,126,126,126,126,126,126,126,126, 2, 2,126,126,126,126,126,
+ 126,126, 2, 2, 2, 2,126,126,126,126,126,126,126, 2,142,142,
+ 142,142,142,142,142,142,142,142,142,142, 2, 2, 2, 2,125,125,
+ 125,125,125,125,125,125,125,125,125, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2,125,154,154,154,154,154,154,154, 2, 2,154,
+ 2, 2,154,154,154,154,154,154,154,154, 2,154,154, 2,154,154,
+ 154,154,154,154,154,154,154,154,154,154,154,154, 2,154,154, 2,
+ 2,154,154,154,154,154,154,154, 2, 2, 2, 2, 2, 2,150,150,
+ 150,150,150,150,150,150, 2, 2,150,150,150,150,150,150,150,150,
+ 150,150,150, 2, 2, 2,141,141,141,141,141,141,141,141,140,140,
+ 140,140,140,140,140,140,140,140,140, 2, 2, 2, 2, 2,121,121,
+ 121,121,121,121,121,121,121, 2, 2, 2, 2, 2, 2, 2, 7, 7,
+ 2, 2, 2, 2, 2, 2,169,169,169,169,169,169,169,169,169,169,
+ 2, 2, 2, 2, 2, 2,133,133,133,133,133,133,133,133,133, 2,
+ 133,133,133,133,133,133,133,133,133,133,133,133,133, 2,133,133,
+ 133,133,133,133, 2, 2,133,133,133,133,133, 2, 2, 2,134,134,
+ 134,134,134,134,134,134, 2, 2,134,134,134,134,134,134, 2,134,
+ 134,134,134,134,134,134,134,134,134,134,134,134,134, 2,138,138,
+ 138,138,138,138,138, 2,138,138, 2,138,138,138,138,138,138,138,
+ 138,138,138,138,138,138, 2, 2,138, 2,138,138, 2,138,138,138,
+ 2, 2, 2, 2, 2, 2,143,143,143,143,143,143, 2,143,143, 2,
+ 143,143,143,143,143,143,143,143,143,143,143,143,143,143,143,143,
+ 143,143,143,143,143, 2,143,143, 2,143,143,143,143,143,143, 2,
+ 2, 2, 2, 2, 2, 2,143,143, 2, 2, 2, 2, 2, 2,145,145,
+ 145,145,145,145,145,145,145, 2, 2, 2, 2, 2, 2, 2,163,163,
+ 163,163,163,163,163,163,163, 2,163,163,163,163,163,163,163,163,
+ 163, 2, 2, 2,163,163,163,163,163, 2, 2, 2, 2, 2, 86, 2,
+ 2, 2, 2, 2, 2, 2, 22, 22, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 22, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+ 2, 2, 2, 2, 2, 2, 63, 63, 63, 63, 63, 63, 63, 2, 63, 63,
+ 63, 63, 63, 2, 2, 2, 63, 63, 63, 63, 2, 2, 2, 2,157,157,
+ 157,157,157,157,157,157,157,157,157, 2, 2, 2, 2, 2, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 2, 2, 80, 80,
+ 80, 2, 2, 2, 2, 2,127,127,127,127,127,127,127,127,127,127,
+ 127,127,127,127,127, 2,166,166,166,166,166,166,166,166,166,166,
+ 2, 2, 2, 2, 2, 2, 79, 2, 2, 2, 2, 2, 2, 2,115,115,
+ 115,115,115,115,115,115,115,115,115,115,115,115,115, 2,115,115,
+ 2, 2, 2, 2,115,115,159,159,159,159,159,159,159,159,159,159,
+ 159,159,159,159,159, 2,159,159, 2, 2, 2, 2, 2, 2,103,103,
+ 103,103,103,103,103,103,103,103,103,103,103,103, 2, 2,119,119,
+ 119,119,119,119,119,119,119,119,119,119,119,119, 2, 2,119,119,
+ 2,119,119,119,119,119, 2, 2, 2, 2, 2,119,119,119,167,167,
+ 167,167,167,167,167,167,167,167, 2, 2, 2, 2, 2, 2,146,146,
+ 146,146,146,146,146,146,146,146,146, 2, 2, 2, 2, 2, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 99, 2, 2,
+ 2, 2, 2, 2, 2, 99,136,139, 13, 13,155, 2, 2, 2,136,136,
+ 136,136,136,136,136,136,155,155,155,155,155,155,155,155,155,155,
+ 155,155,155,155, 2, 2, 2, 2, 2, 2, 2, 2, 2,155,136, 2,
+ 2, 2, 2, 2, 2, 2, 17, 17, 17, 17, 2, 17, 17, 17, 17, 17,
+ 17, 17, 2, 17, 17, 2, 17, 15, 15, 15, 15, 15, 15, 15, 17, 17,
+ 17, 2, 2, 2, 2, 2, 2, 2, 15, 2, 2, 2, 2, 2, 15, 15,
+ 15, 2, 2, 17, 2, 2, 2, 2, 2, 2, 17, 17, 17, 17,139,139,
+ 139,139,139,139,139,139,139,139,139,139, 2, 2, 2, 2,105,105,
+ 105,105,105,105,105,105,105,105,105, 2, 2, 2, 2, 2,105,105,
+ 105,105,105, 2, 2, 2,105, 2, 2, 2, 2, 2, 2, 2,105,105,
+ 2, 2,105,105,105,105, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 2, 2,
+ 0, 2, 2, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0,
+ 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0,
+ 0, 0, 0, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2,
+ 0, 0, 0, 0, 0, 0,131,131,131,131,131,131,131,131,131,131,
+ 131,131, 2, 2, 2, 2, 2, 2, 2,131,131,131,131,131, 2,131,
+ 131,131,131,131,131,131, 2, 2, 2, 2, 2, 19, 19, 19, 56, 56,
+ 56, 56, 56, 56, 56, 2, 56, 2, 2, 56, 56, 56, 56, 56, 56, 56,
+ 2, 56, 56, 2, 56, 56, 56, 56, 56, 2, 2, 2, 2, 2, 6, 6,
+ 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,151,151,
+ 151,151,151,151,151,151,151,151,151,151,151, 2, 2, 2,151,151,
+ 151,151,151,151, 2, 2,151,151, 2, 2, 2, 2,151,151,160,160,
+ 160,160,160,160,160,160,160,160,160,160,160,160,160, 2,152,152,
+ 152,152,152,152,152,152,152,152, 2, 2, 2, 2, 2,152,164,164,
+ 164,164,164,164,164,164,164,164, 2, 2, 2, 2, 2, 2,168,168,
+ 168,168,168,168,168,168,168,168,168, 2, 2, 2, 2,168, 30, 30,
+ 30, 30, 2, 30, 30, 2,113,113,113,113,113,113,113,113,113,113,
+ 113,113,113, 2, 2,113,113,113,113,113,113,113,113, 2,132,132,
+ 132,132,132,132,132,132,132,132,132,132, 2, 2, 2, 2,132,132,
+ 2, 2, 2, 2,132,132, 3, 3, 3, 3, 2, 3, 3, 3, 2, 3,
+ 3, 2, 3, 2, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 2, 3, 3, 3, 3, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2,
+ 3, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 2, 3,
+ 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 2, 3, 2, 3, 3,
+ 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 3,
+ 3, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 0, 0, 15, 0, 0, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 0, 13, 2,
+ 2, 2, 2, 2, 2, 2, 13, 13, 13, 2, 2, 2, 2, 2, 2, 0,
+ 2, 2, 2, 2, 2, 2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 9, 9, 9, 10, 9, 11, 12, 13, 9, 9, 9, 14, 9, 9, 15, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 16, 17, 9, 9, 9, 9, 18, 9, 9, 9, 9, 9, 19, 20,
+ 21, 9, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 23, 9, 9, 9, 9, 9, 24, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 23, 24,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
- 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 13, 14, 15, 16, 17, 18,
- 19, 20, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 23, 0, 0, 24, 25, 26, 27, 28, 29, 30, 0, 0,
- 31, 32, 0, 33, 0, 34, 0, 35, 0, 0, 0, 0, 36, 37, 38, 39,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 25, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 26, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 24, 25, 26, 27, 28,
+ 29, 30, 0, 0, 31, 32, 0, 33, 0, 34, 0, 35, 0, 0, 0, 0,
+ 36, 37, 38, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 41, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0,
+ 0, 0, 0, 0, 0, 0, 43, 44, 0, 45, 0, 0, 0, 0, 0, 0,
+ 46, 47, 0, 0, 0, 0, 0, 48, 0, 49, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 50, 51, 0, 0, 0, 52, 0, 0,
+ 53, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0,
+ 55, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0,
+ 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 61, 62, 63, 64, 65,
+ 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 43, 44, 0, 45, 0, 0, 0, 0, 0, 0, 46, 47, 0, 0,
- 0, 0, 0, 48, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 50, 51, 0, 0, 0, 52, 0, 0, 53, 0, 0, 0,
- 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0,
- 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 57, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 58, 59, 60, 61, 62, 63, 64, 65, 0, 0, 0, 0,
- 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 67, 68, 0, 69, 70, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
+ 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
+ 99,100,101,102,103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,104, 0, 0, 0, 0, 0, 0,105,106, 0,
+ 107, 0, 0, 0,108, 0,109, 0,110, 0,111,112,113, 0,114, 0,
+ 0, 0,115, 0, 0, 0,116, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,117, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,118,119,120,121, 0,122,123,124,
+ 125,126, 0,127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,128,129,130,131,132,133,134,135,136,137,138,139,
+ 140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,
+ 156,157, 0, 0, 0,158,159,160,161, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 67, 68, 0, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86,
- 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,
- 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0,104, 0, 0, 0, 0, 0, 0,105,106, 0,107, 0, 0, 0,
- 108, 0,109, 0,110, 0,111,112,113, 0,114, 0, 0, 0,115, 0,
- 0, 0,116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,117, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,118,119,120,121, 0,122,123,124,125,126, 0,127,
+ 162, 0,163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,164,165, 0,
+ 0, 0, 0, 0, 0, 0,166, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,167, 0, 0, 0,168,169, 0, 0,
+ 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,171,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,172,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
- 144,145,146,147,148,149,150,151,152,153,154,155,156,157, 0, 0,
- 0,158,159,160,161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0,162,163, 0, 0, 0, 0, 0,
- 0, 0,164, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,173, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,166, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,167, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,174, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0,168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,175, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,169,170, 0, 0, 0, 0,171,172, 0, 0, 0,
- 173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,
- 189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,
- 205,206, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,176,177, 0, 0, 0, 0,178,
+ 179, 0, 0, 0,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
+ 208,209,210,211,212,213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 2, 3, 4,
};
static const uint16_t
-_hb_ucd_u16[10060] =
+_hb_ucd_u16[9668] =
{
0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 7, 8, 9, 10, 11, 12,
13, 13, 13, 14, 15, 13, 13, 16, 17, 18, 19, 20, 21, 22, 13, 23,
@@ -3776,9 +4012,10 @@ _hb_ucd_u16[10060] =
136, 48, 48, 137, 138, 139, 140, 140, 141, 48, 142, 143, 144, 145, 140, 140,
146, 147, 148, 149, 150, 48, 151, 152, 153, 154, 32, 155, 156, 157, 140, 140,
48, 48, 158, 159, 160, 161, 162, 163, 164, 165, 9, 9, 166, 11, 11, 167,
- 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 168, 169, 48, 48,
- 168, 48, 48, 170, 171, 172, 48, 48, 48, 171, 48, 48, 48, 173, 174, 175,
- 48, 176, 9, 9, 9, 9, 9, 177, 178, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 168, 169, 48, 48, 168, 48, 48, 170, 171, 172, 48, 48,
+ 48, 171, 48, 48, 48, 173, 174, 175, 48, 176, 9, 9, 9, 9, 9, 177,
+ 178, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 179, 48, 180, 181, 48, 48, 48, 48, 182, 183,
48, 184, 48, 185, 48, 186, 187, 188, 48, 48, 48, 189, 190, 191, 192, 193,
194, 192, 48, 48, 195, 48, 48, 196, 197, 48, 198, 48, 48, 48, 48, 199,
@@ -3791,28 +4028,34 @@ _hb_ucd_u16[10060] =
241, 242, 241, 241, 242, 243, 241, 244, 245, 245, 245, 246, 247, 248, 249, 250,
251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 261, 262, 263, 264, 265,
266, 267, 268, 269, 270, 271, 272, 272, 273, 274, 275, 209, 276, 277, 209, 278,
- 279, 279, 279, 279, 279, 279, 279, 279, 280, 209, 281, 209, 209, 209, 209, 282,
- 209, 283, 279, 284, 209, 285, 286, 209, 209, 209, 287, 140, 288, 140, 271, 271,
- 271, 289, 209, 209, 209, 209, 290, 271, 209, 209, 209, 209, 209, 209, 209, 209,
- 209, 209, 209, 291, 292, 209, 209, 293, 209, 209, 209, 209, 209, 209, 294, 209,
- 209, 209, 209, 209, 209, 209, 295, 296, 271, 297, 209, 209, 298, 279, 299, 279,
- 300, 301, 279, 279, 279, 302, 279, 303, 209, 209, 209, 279, 304, 209, 209, 305,
- 209, 306, 209, 209, 209, 209, 209, 209, 9, 9, 9, 11, 11, 11, 307, 308,
- 13, 13, 13, 13, 13, 13, 309, 310, 11, 11, 311, 48, 48, 48, 312, 313,
- 48, 314, 315, 315, 315, 315, 32, 32, 316, 317, 318, 319, 320, 321, 140, 140,
- 209, 322, 209, 209, 209, 209, 209, 323, 209, 209, 209, 209, 209, 324, 140, 209,
- 325, 326, 327, 328, 136, 48, 48, 48, 48, 329, 178, 48, 48, 48, 48, 330,
- 331, 48, 48, 136, 48, 48, 48, 48, 200, 332, 48, 48, 209, 209, 333, 48,
- 209, 334, 335, 209, 336, 337, 209, 209, 335, 209, 209, 337, 209, 209, 209, 209,
- 48, 48, 48, 48, 209, 209, 209, 209, 48, 338, 48, 48, 48, 48, 48, 48,
- 151, 209, 209, 209, 287, 48, 48, 229, 339, 48, 340, 140, 13, 13, 341, 342,
- 13, 343, 48, 48, 48, 48, 344, 345, 31, 346, 347, 348, 13, 13, 13, 349,
- 350, 351, 352, 353, 354, 355, 140, 356, 357, 48, 358, 359, 48, 48, 48, 360,
- 361, 48, 48, 362, 363, 192, 32, 364, 64, 48, 365, 48, 366, 367, 48, 151,
- 76, 48, 48, 368, 369, 370, 371, 372, 48, 48, 373, 374, 375, 376, 48, 377,
- 48, 48, 48, 378, 379, 380, 381, 382, 383, 384, 315, 11, 11, 385, 386, 11,
- 11, 11, 11, 11, 48, 48, 387, 192, 48, 48, 388, 48, 389, 48, 48, 206,
- 390, 390, 390, 390, 390, 390, 390, 390, 391, 391, 391, 391, 391, 391, 391, 391,
+ 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279,
+ 280, 209, 281, 209, 209, 209, 209, 282, 209, 283, 279, 284, 209, 285, 286, 209,
+ 209, 209, 176, 140, 287, 140, 271, 271, 271, 288, 209, 209, 209, 209, 289, 271,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 290, 291, 209, 209, 292,
+ 209, 209, 209, 209, 209, 209, 293, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 294, 295, 271, 296, 209, 209, 297, 279, 298, 279,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 279, 279, 279, 279, 279, 279, 279, 279, 299, 300, 279, 279, 279, 301, 279, 302,
+ 209, 209, 209, 279, 303, 209, 209, 304, 209, 305, 209, 209, 209, 209, 209, 209,
+ 9, 9, 9, 11, 11, 11, 306, 307, 13, 13, 13, 13, 13, 13, 308, 309,
+ 11, 11, 310, 48, 48, 48, 311, 312, 48, 313, 314, 314, 314, 314, 32, 32,
+ 315, 316, 317, 318, 319, 320, 140, 140, 209, 321, 209, 209, 209, 209, 209, 322,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 323, 140, 209,
+ 324, 325, 326, 327, 136, 48, 48, 48, 48, 328, 178, 48, 48, 48, 48, 329,
+ 330, 48, 48, 136, 48, 48, 48, 48, 200, 331, 48, 48, 209, 209, 332, 48,
+ 209, 333, 334, 209, 335, 336, 209, 209, 334, 209, 209, 336, 209, 209, 209, 209,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 209, 209, 209, 209,
+ 48, 337, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 151, 209, 209, 209, 338, 48, 48, 229,
+ 339, 48, 340, 140, 13, 13, 341, 342, 13, 343, 48, 48, 48, 48, 344, 345,
+ 31, 346, 347, 348, 13, 13, 13, 349, 350, 351, 352, 353, 354, 355, 140, 356,
+ 357, 48, 358, 359, 48, 48, 48, 360, 361, 48, 48, 362, 363, 192, 32, 364,
+ 64, 48, 365, 48, 366, 367, 48, 151, 76, 48, 48, 368, 369, 370, 371, 372,
+ 48, 48, 373, 374, 375, 376, 48, 377, 48, 48, 48, 378, 379, 380, 381, 382,
+ 383, 384, 314, 11, 11, 385, 386, 11, 11, 11, 11, 11, 48, 48, 387, 192,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 388, 48, 389, 48, 48, 206,
+ 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390, 390,
+ 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391,
48, 48, 48, 48, 48, 48, 204, 48, 48, 48, 48, 48, 48, 207, 140, 140,
392, 393, 394, 395, 396, 48, 48, 48, 48, 48, 48, 397, 398, 399, 48, 48,
48, 48, 48, 400, 209, 48, 48, 48, 48, 401, 48, 48, 402, 140, 140, 403,
@@ -3823,571 +4066,540 @@ _hb_ucd_u16[10060] =
140, 140, 140, 140, 140, 140, 140, 140, 48, 151, 48, 48, 48, 100, 429, 430,
48, 48, 431, 48, 432, 48, 48, 433, 48, 434, 48, 48, 435, 436, 140, 140,
9, 9, 437, 11, 11, 48, 48, 48, 48, 204, 192, 9, 9, 438, 11, 439,
- 48, 48, 440, 48, 48, 48, 441, 442, 442, 443, 444, 445, 140, 140, 140, 140,
- 48, 48, 48, 314, 48, 199, 440, 140, 446, 27, 27, 447, 140, 140, 140, 140,
+ 48, 48, 440, 48, 48, 48, 441, 442, 442, 443, 444, 445, 48, 48, 48, 388,
+ 48, 48, 48, 313, 48, 199, 440, 140, 446, 27, 27, 447, 140, 140, 140, 140,
448, 48, 48, 449, 48, 450, 48, 451, 48, 200, 452, 140, 140, 140, 48, 453,
48, 454, 48, 455, 140, 140, 140, 140, 48, 48, 48, 456, 271, 457, 271, 271,
458, 459, 48, 460, 461, 462, 48, 463, 48, 464, 140, 140, 465, 48, 466, 467,
48, 48, 48, 468, 48, 469, 48, 470, 48, 471, 472, 140, 140, 140, 140, 140,
48, 48, 48, 48, 196, 140, 140, 140, 9, 9, 9, 473, 11, 11, 11, 474,
- 48, 48, 475, 192, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 271, 476,
- 48, 48, 477, 478, 140, 140, 140, 479, 48, 464, 480, 48, 62, 481, 140, 48,
- 482, 140, 140, 48, 483, 140, 48, 314, 484, 48, 48, 485, 486, 457, 487, 488,
- 222, 48, 48, 489, 490, 48, 196, 192, 491, 48, 492, 493, 494, 48, 48, 495,
- 222, 48, 48, 496, 497, 498, 499, 500, 48, 97, 501, 502, 503, 140, 140, 140,
- 504, 505, 506, 48, 48, 507, 508, 192, 509, 83, 84, 510, 511, 512, 513, 514,
- 48, 48, 48, 515, 516, 517, 478, 140, 48, 48, 48, 518, 519, 192, 140, 140,
- 48, 48, 520, 521, 522, 523, 140, 140, 48, 48, 48, 524, 525, 192, 526, 140,
- 48, 48, 527, 528, 192, 140, 140, 140, 48, 173, 529, 530, 314, 140, 140, 140,
- 48, 48, 501, 531, 140, 140, 140, 140, 140, 140, 9, 9, 11, 11, 148, 532,
- 533, 534, 48, 535, 536, 192, 140, 140, 140, 140, 537, 48, 48, 538, 539, 140,
- 540, 48, 48, 541, 542, 543, 48, 48, 544, 545, 546, 48, 48, 48, 48, 196,
- 547, 140, 140, 140, 140, 140, 140, 140, 84, 48, 520, 548, 549, 148, 175, 550,
- 48, 551, 552, 553, 140, 140, 140, 140, 554, 48, 48, 555, 556, 192, 557, 48,
- 558, 559, 192, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 560,
- 561, 115, 48, 562, 563, 192, 140, 140, 140, 140, 140, 100, 271, 564, 565, 566,
- 48, 207, 140, 140, 140, 140, 140, 140, 272, 272, 272, 272, 272, 272, 567, 568,
- 48, 48, 48, 48, 388, 140, 140, 140, 140, 48, 48, 48, 48, 48, 48, 569,
- 48, 48, 48, 570, 571, 572, 140, 140, 48, 48, 48, 48, 314, 140, 140, 140,
- 48, 48, 48, 196, 48, 200, 370, 48, 48, 48, 48, 200, 192, 48, 204, 573,
- 48, 48, 48, 574, 575, 576, 577, 578, 48, 140, 140, 140, 140, 140, 140, 140,
- 140, 140, 140, 140, 9, 9, 11, 11, 271, 579, 140, 140, 140, 140, 140, 140,
- 48, 48, 48, 48, 580, 581, 582, 582, 583, 584, 140, 140, 140, 140, 585, 586,
- 48, 48, 48, 48, 48, 48, 48, 440, 48, 48, 48, 48, 48, 199, 140, 140,
- 196, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 587,
- 48, 48, 588, 589, 140, 590, 591, 48, 48, 48, 48, 48, 48, 48, 48, 206,
- 48, 48, 48, 48, 48, 48, 71, 151, 196, 592, 593, 140, 140, 140, 140, 140,
- 32, 32, 594, 32, 595, 209, 209, 209, 209, 209, 209, 209, 323, 140, 140, 140,
- 209, 209, 209, 209, 209, 209, 209, 324, 209, 209, 596, 209, 209, 209, 597, 598,
- 599, 209, 600, 209, 209, 209, 288, 140, 209, 209, 209, 209, 601, 140, 140, 140,
- 140, 140, 140, 140, 271, 602, 271, 602, 209, 209, 209, 209, 209, 287, 271, 461,
- 9, 603, 11, 604, 605, 606, 241, 9, 607, 608, 609, 610, 611, 9, 603, 11,
- 612, 613, 11, 614, 615, 616, 617, 9, 618, 11, 9, 603, 11, 604, 605, 11,
- 241, 9, 607, 617, 9, 618, 11, 9, 603, 11, 619, 9, 620, 621, 622, 623,
- 11, 624, 9, 625, 626, 627, 628, 11, 629, 9, 630, 11, 631, 632, 632, 632,
- 32, 32, 32, 633, 32, 32, 634, 635, 636, 637, 45, 140, 140, 140, 140, 140,
- 638, 639, 640, 140, 140, 140, 140, 140, 641, 642, 643, 27, 27, 27, 644, 140,
- 645, 140, 140, 140, 140, 140, 140, 140, 48, 48, 151, 646, 647, 140, 140, 140,
- 140, 48, 648, 140, 48, 48, 649, 650, 140, 140, 140, 140, 140, 48, 651, 192,
- 140, 140, 140, 140, 140, 140, 652, 200, 48, 48, 48, 48, 653, 595, 140, 140,
- 9, 9, 607, 11, 654, 370, 140, 140, 140, 140, 140, 140, 140, 140, 140, 499,
- 271, 271, 655, 656, 140, 140, 140, 140, 499, 271, 657, 658, 140, 140, 140, 140,
- 659, 48, 660, 661, 662, 663, 664, 665, 666, 206, 667, 206, 140, 140, 140, 668,
- 209, 209, 669, 209, 209, 209, 209, 209, 209, 323, 334, 670, 670, 670, 209, 324,
- 671, 209, 209, 209, 209, 209, 209, 209, 209, 209, 672, 140, 140, 140, 673, 209,
- 674, 209, 209, 669, 675, 676, 324, 140, 209, 209, 209, 209, 209, 209, 209, 677,
- 209, 209, 209, 209, 209, 678, 426, 426, 209, 209, 209, 209, 209, 209, 209, 679,
- 209, 209, 209, 209, 209, 176, 669, 427, 669, 209, 209, 209, 680, 176, 209, 209,
- 680, 209, 672, 676, 140, 140, 140, 140, 209, 209, 209, 209, 209, 323, 672, 426,
- 675, 209, 209, 681, 682, 669, 675, 675, 209, 683, 209, 209, 288, 140, 140, 192,
- 48, 48, 48, 48, 48, 48, 140, 140, 48, 48, 48, 207, 48, 48, 48, 48,
- 48, 204, 48, 48, 48, 48, 48, 48, 48, 48, 478, 48, 48, 48, 48, 48,
- 48, 48, 48, 48, 48, 48, 100, 48, 48, 48, 48, 48, 48, 204, 140, 140,
- 48, 204, 140, 140, 140, 140, 140, 140, 48, 48, 48, 48, 71, 48, 48, 48,
- 48, 48, 48, 140, 140, 140, 140, 140, 684, 140, 570, 570, 570, 570, 570, 570,
+ 48, 48, 475, 192, 476, 9, 477, 11, 478, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 271, 479, 48, 48, 480, 481, 482, 140, 140, 483,
+ 48, 464, 484, 48, 62, 485, 140, 48, 486, 140, 140, 48, 487, 140, 48, 313,
+ 488, 48, 48, 489, 490, 457, 491, 492, 222, 48, 48, 493, 494, 48, 196, 192,
+ 495, 48, 496, 497, 498, 48, 48, 499, 222, 48, 48, 500, 501, 502, 503, 504,
+ 48, 97, 505, 506, 507, 140, 140, 140, 508, 509, 510, 48, 48, 511, 512, 192,
+ 513, 83, 84, 514, 515, 516, 517, 518, 519, 48, 48, 520, 521, 522, 523, 140,
+ 48, 48, 48, 524, 525, 526, 481, 140, 48, 48, 48, 527, 528, 192, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 48, 48, 529, 530, 531, 532, 140, 140,
+ 48, 48, 48, 533, 534, 192, 535, 140, 48, 48, 536, 537, 192, 538, 539, 140,
+ 48, 540, 541, 542, 313, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 505, 543, 140, 140, 140, 140, 140, 140, 9, 9, 11, 11, 148, 544,
+ 545, 546, 48, 547, 548, 192, 140, 140, 140, 140, 549, 48, 48, 550, 551, 140,
+ 552, 48, 48, 553, 554, 555, 48, 48, 556, 557, 558, 48, 48, 48, 48, 196,
+ 559, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 48, 560, 192,
+ 84, 48, 529, 561, 562, 148, 175, 563, 48, 564, 565, 566, 140, 140, 140, 140,
+ 567, 48, 48, 568, 569, 192, 570, 48, 571, 572, 192, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 573,
+ 574, 115, 48, 575, 576, 577, 140, 140, 140, 140, 140, 100, 271, 578, 579, 580,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 207, 140, 140, 140, 140, 140, 140,
+ 272, 272, 272, 272, 272, 272, 581, 582, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 388, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 48, 48, 48, 48, 48, 583,
+ 48, 48, 48, 584, 585, 586, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 71,
+ 48, 48, 48, 48, 313, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 587, 588, 192, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 48, 196, 48, 200, 370, 48, 48, 48, 48, 200, 192, 48, 204, 589,
+ 48, 48, 48, 590, 591, 592, 593, 594, 48, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 595, 48, 596, 192, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 9, 9, 11, 11, 271, 597, 140, 140, 140, 140, 140, 140,
+ 48, 48, 48, 48, 598, 599, 600, 600, 601, 602, 140, 140, 140, 140, 603, 604,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 440,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 199, 140, 605,
+ 196, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 606,
+ 48, 48, 607, 608, 140, 609, 610, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 206,
+ 48, 48, 48, 48, 48, 48, 71, 151, 196, 611, 612, 140, 140, 140, 140, 140,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 192,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 322, 140, 140, 140, 140,
+ 32, 32, 613, 32, 614, 209, 209, 209, 209, 209, 209, 209, 322, 140, 140, 140,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 323,
+ 209, 209, 615, 209, 209, 209, 616, 617, 618, 209, 619, 209, 209, 209, 287, 140,
+ 209, 209, 209, 209, 620, 140, 140, 140, 140, 140, 140, 140, 271, 621, 271, 621,
+ 209, 209, 209, 209, 209, 338, 271, 461, 140, 140, 140, 140, 140, 140, 140, 140,
+ 9, 622, 11, 623, 624, 625, 241, 9, 626, 627, 628, 629, 630, 9, 622, 11,
+ 631, 632, 11, 633, 634, 635, 636, 9, 637, 11, 9, 622, 11, 623, 624, 11,
+ 241, 9, 626, 636, 9, 637, 11, 9, 622, 11, 638, 9, 639, 640, 641, 642,
+ 11, 643, 9, 644, 645, 646, 647, 11, 648, 9, 649, 11, 650, 538, 538, 538,
+ 32, 32, 32, 651, 32, 32, 652, 653, 654, 655, 45, 140, 140, 140, 140, 140,
+ 656, 657, 658, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 659, 660, 661, 27, 27, 27, 662, 140, 663, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 151, 664, 665, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 666, 140, 48, 48, 667, 668,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 669, 192,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 48, 587, 670,
+ 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 671, 200,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 672, 614, 140, 140,
+ 9, 9, 626, 11, 673, 370, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 140, 140, 140, 140, 140, 140, 140, 503, 271, 271, 674, 675, 140, 140, 140, 140,
+ 503, 271, 676, 677, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 678, 48, 679, 680, 681, 682, 683, 684, 685, 206, 686, 206, 140, 140, 140, 687,
+ 209, 209, 688, 209, 209, 209, 209, 209, 209, 322, 333, 689, 689, 689, 209, 323,
+ 690, 209, 209, 209, 209, 209, 209, 209, 209, 209, 691, 140, 140, 140, 692, 209,
+ 693, 209, 209, 688, 694, 695, 323, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 696,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 697, 426, 426,
+ 209, 209, 209, 209, 209, 209, 209, 698, 209, 209, 209, 209, 209, 176, 688, 427,
+ 688, 209, 209, 209, 699, 176, 209, 209, 699, 209, 691, 688, 695, 140, 140, 140,
+ 209, 209, 209, 209, 209, 322, 691, 426, 700, 209, 209, 209, 701, 702, 176, 694,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 703, 209, 209, 209, 209, 209, 192,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 140, 140,
+ 48, 48, 48, 207, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 204, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 481, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 100, 48,
+ 48, 48, 48, 48, 48, 204, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 204, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140, 140,
+ 48, 48, 48, 48, 71, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 140, 140, 140, 140, 140,
+ 704, 140, 584, 584, 584, 584, 584, 584, 140, 140, 140, 140, 140, 140, 140, 140,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 140,
- 391, 391, 391, 391, 391, 391, 391, 685, 391, 391, 391, 391, 391, 391, 391, 686,
- 0, 0, 0, 0, 1, 2, 1, 2, 0, 0, 3, 3, 4, 5, 4, 5,
- 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 0, 0, 7, 0,
- 8, 8, 8, 8, 8, 8, 8, 9, 10, 11, 12, 11, 11, 11, 13, 11,
- 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 14, 14, 14, 14,
- 14, 14, 14, 16, 17, 18, 17, 17, 19, 20, 21, 21, 22, 21, 23, 24,
- 25, 26, 27, 27, 28, 29, 27, 30, 27, 27, 27, 27, 27, 31, 27, 27,
- 32, 33, 33, 33, 34, 27, 27, 27, 35, 35, 35, 36, 37, 37, 37, 38,
- 39, 39, 40, 41, 42, 43, 44, 27, 45, 46, 27, 27, 27, 27, 47, 27,
- 48, 48, 48, 48, 48, 49, 50, 48, 51, 52, 53, 54, 55, 56, 57, 58,
- 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
- 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
- 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
- 107, 108, 109, 109, 110, 111, 112, 109, 113, 114, 115, 116, 117, 118, 119, 120,
- 121, 122, 122, 123, 122, 124, 125, 125, 126, 127, 128, 129, 130, 131, 125, 125,
- 132, 132, 132, 132, 133, 132, 134, 135, 132, 133, 132, 136, 136, 137, 125, 125,
- 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 140, 139, 139, 141,
- 142, 142, 142, 142, 142, 142, 142, 142, 143, 143, 143, 143, 144, 145, 143, 143,
- 144, 143, 143, 146, 147, 148, 143, 143, 143, 147, 143, 143, 143, 149, 143, 150,
- 143, 151, 152, 152, 152, 152, 152, 153, 154, 154, 154, 154, 154, 154, 154, 154,
- 155, 156, 157, 157, 157, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167,
- 168, 168, 168, 168, 168, 169, 170, 170, 171, 172, 173, 173, 173, 173, 173, 174,
- 173, 173, 175, 154, 154, 154, 154, 176, 177, 178, 179, 179, 180, 181, 182, 183,
- 184, 184, 185, 184, 186, 187, 168, 168, 188, 189, 190, 190, 190, 191, 190, 192,
- 193, 193, 194, 8, 195, 125, 125, 125, 196, 196, 196, 196, 197, 196, 196, 198,
- 199, 199, 199, 199, 200, 200, 200, 201, 202, 202, 202, 203, 204, 205, 205, 205,
- 206, 139, 139, 207, 208, 209, 210, 211, 4, 4, 212, 4, 4, 213, 214, 215,
- 4, 4, 4, 216, 8, 8, 8, 8, 11, 217, 11, 11, 217, 218, 11, 219,
- 11, 11, 11, 220, 220, 221, 11, 222, 223, 0, 0, 0, 0, 0, 224, 225,
- 226, 227, 0, 0, 228, 8, 8, 229, 0, 0, 230, 231, 232, 0, 4, 4,
- 233, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 234, 125, 235, 125, 0, 0, 236, 236, 236, 236, 236, 236, 236, 236,
- 0, 0, 0, 0, 0, 0, 0, 237, 0, 238, 0, 0, 0, 0, 0, 0,
- 239, 239, 239, 239, 239, 239, 4, 4, 240, 240, 240, 240, 240, 240, 240, 241,
- 139, 139, 140, 242, 242, 242, 243, 244, 143, 245, 246, 246, 246, 246, 14, 14,
- 0, 0, 0, 0, 0, 247, 125, 125, 248, 249, 248, 248, 248, 248, 248, 250,
- 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 251, 125, 0,
- 252, 0, 253, 254, 255, 256, 256, 256, 256, 257, 258, 259, 259, 259, 259, 260,
- 261, 262, 262, 263, 142, 142, 142, 142, 264, 0, 262, 262, 0, 0, 265, 259,
- 142, 264, 0, 0, 0, 0, 142, 266, 0, 0, 0, 0, 0, 259, 259, 267,
- 259, 259, 259, 259, 259, 268, 0, 0, 248, 248, 248, 248, 0, 0, 0, 0,
- 269, 269, 269, 269, 269, 269, 269, 269, 270, 269, 269, 269, 271, 272, 272, 272,
- 273, 273, 273, 273, 273, 273, 273, 273, 273, 273, 274, 125, 14, 14, 14, 14,
- 14, 14, 275, 275, 275, 275, 275, 276, 0, 0, 277, 4, 4, 4, 4, 4,
- 278, 4, 4, 4, 279, 280, 125, 281, 282, 282, 283, 284, 285, 285, 285, 286,
- 287, 287, 287, 287, 288, 289, 48, 48, 290, 290, 291, 292, 292, 293, 142, 294,
- 295, 295, 295, 295, 296, 297, 138, 298, 299, 299, 299, 300, 301, 302, 138, 138,
- 303, 303, 303, 303, 304, 305, 306, 307, 308, 309, 246, 4, 4, 310, 311, 152,
- 152, 152, 152, 152, 306, 306, 312, 313, 142, 142, 314, 142, 315, 142, 142, 316,
- 125, 125, 125, 125, 125, 125, 125, 125, 248, 248, 248, 248, 248, 248, 317, 248,
- 248, 248, 248, 248, 248, 318, 125, 125, 319, 320, 21, 321, 322, 27, 27, 27,
- 27, 27, 27, 27, 323, 324, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
- 27, 27, 27, 325, 27, 27, 27, 27, 27, 326, 27, 27, 327, 125, 125, 27,
- 8, 284, 328, 0, 0, 329, 330, 331, 27, 27, 27, 27, 27, 27, 27, 332,
- 333, 0, 1, 2, 1, 2, 334, 258, 259, 335, 142, 264, 336, 337, 338, 339,
- 340, 341, 342, 343, 344, 344, 125, 125, 341, 341, 341, 341, 341, 341, 341, 345,
- 346, 0, 0, 347, 11, 11, 11, 11, 348, 349, 350, 125, 125, 0, 0, 351,
- 352, 353, 354, 354, 354, 355, 356, 357, 358, 358, 359, 360, 361, 362, 362, 363,
- 364, 365, 366, 366, 367, 368, 125, 125, 369, 369, 369, 369, 369, 370, 370, 370,
- 371, 372, 373, 374, 374, 375, 374, 376, 377, 377, 378, 379, 379, 379, 380, 381,
- 381, 382, 383, 384, 125, 125, 125, 125, 385, 385, 385, 385, 385, 385, 385, 385,
- 385, 385, 385, 386, 385, 387, 388, 125, 389, 4, 4, 390, 125, 125, 125, 125,
- 391, 392, 392, 393, 394, 395, 396, 396, 397, 398, 399, 125, 125, 125, 400, 401,
- 402, 403, 404, 405, 125, 125, 125, 125, 406, 406, 407, 408, 407, 409, 407, 407,
- 410, 411, 412, 413, 414, 414, 415, 415, 416, 416, 125, 125, 417, 417, 418, 419,
- 420, 420, 420, 421, 422, 423, 424, 425, 426, 427, 428, 125, 125, 125, 125, 125,
- 429, 429, 429, 429, 430, 125, 125, 125, 431, 431, 431, 432, 431, 431, 431, 433,
- 434, 434, 435, 436, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 27, 45,
- 437, 437, 438, 439, 125, 125, 125, 440, 441, 441, 442, 443, 443, 444, 125, 445,
- 446, 125, 125, 447, 448, 125, 449, 450, 451, 451, 451, 451, 452, 453, 451, 454,
- 455, 455, 455, 455, 456, 457, 458, 459, 460, 460, 460, 461, 462, 463, 463, 464,
- 465, 465, 465, 465, 465, 465, 466, 467, 468, 469, 468, 468, 470, 125, 125, 125,
- 471, 472, 473, 474, 474, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484,
- 485, 485, 485, 485, 485, 486, 487, 125, 488, 488, 488, 488, 489, 490, 125, 125,
- 491, 491, 491, 492, 491, 493, 125, 125, 494, 494, 494, 494, 495, 496, 497, 125,
- 498, 498, 498, 499, 499, 125, 125, 125, 500, 501, 502, 500, 503, 125, 125, 125,
- 504, 504, 504, 505, 125, 125, 125, 125, 125, 125, 506, 506, 506, 506, 506, 507,
- 508, 509, 510, 511, 512, 513, 125, 125, 125, 125, 514, 515, 515, 514, 516, 125,
- 517, 517, 517, 517, 518, 519, 519, 519, 519, 519, 520, 154, 521, 521, 521, 522,
- 523, 125, 125, 125, 125, 125, 125, 125, 524, 525, 525, 526, 527, 525, 528, 529,
- 529, 530, 531, 532, 125, 125, 125, 125, 533, 534, 534, 535, 536, 537, 538, 539,
- 540, 541, 542, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 543, 544,
- 545, 546, 545, 547, 545, 548, 125, 125, 125, 125, 125, 549, 550, 550, 550, 551,
- 552, 552, 552, 552, 552, 552, 552, 552, 552, 553, 125, 125, 125, 125, 125, 125,
- 552, 552, 552, 552, 552, 552, 554, 555, 552, 552, 552, 552, 556, 125, 125, 125,
- 125, 557, 557, 557, 557, 557, 557, 558, 559, 559, 559, 559, 559, 559, 559, 559,
- 559, 559, 559, 559, 559, 560, 125, 125, 561, 561, 561, 561, 561, 561, 561, 561,
- 561, 561, 561, 561, 562, 125, 125, 125, 275, 275, 275, 275, 275, 275, 275, 275,
- 275, 275, 275, 563, 564, 565, 566, 567, 567, 567, 567, 568, 569, 570, 571, 572,
- 573, 573, 573, 573, 574, 575, 576, 577, 573, 125, 125, 125, 125, 125, 125, 125,
- 125, 125, 125, 125, 578, 578, 578, 578, 578, 579, 125, 125, 125, 125, 125, 125,
- 580, 580, 580, 580, 581, 580, 580, 580, 582, 580, 125, 125, 125, 125, 583, 584,
- 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 586,
- 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 587, 588, 125, 125,
- 589, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 590,
- 591, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
- 256, 256, 592, 593, 125, 594, 595, 596, 596, 596, 596, 596, 596, 596, 596, 596,
- 596, 596, 596, 596, 596, 596, 596, 597, 598, 598, 598, 598, 598, 598, 599, 600,
- 601, 602, 603, 125, 125, 125, 125, 125, 8, 8, 604, 8, 605, 0, 0, 0,
- 0, 0, 0, 0, 603, 125, 125, 125, 0, 0, 0, 0, 0, 0, 0, 606,
- 0, 0, 607, 0, 0, 0, 608, 609, 610, 0, 611, 0, 0, 0, 235, 125,
- 11, 11, 11, 11, 612, 125, 125, 125, 125, 125, 125, 125, 0, 603, 0, 603,
- 0, 0, 0, 0, 0, 234, 0, 613, 0, 0, 0, 0, 0, 224, 0, 0,
- 0, 614, 615, 616, 617, 0, 0, 0, 618, 619, 0, 620, 621, 622, 0, 0,
- 0, 0, 623, 0, 0, 0, 0, 0, 0, 0, 0, 0, 624, 0, 0, 0,
- 625, 625, 625, 625, 625, 625, 625, 625, 626, 627, 628, 125, 125, 125, 125, 125,
- 4, 629, 630, 125, 125, 125, 125, 125, 631, 632, 633, 14, 14, 14, 634, 125,
- 635, 125, 125, 125, 125, 125, 125, 125, 636, 636, 637, 638, 639, 125, 125, 125,
- 125, 640, 641, 125, 642, 642, 642, 643, 125, 125, 125, 125, 125, 644, 644, 645,
- 125, 125, 125, 125, 125, 125, 646, 647, 648, 648, 648, 648, 648, 648, 648, 648,
- 648, 648, 648, 648, 649, 650, 125, 125, 651, 651, 651, 651, 652, 653, 125, 125,
- 125, 125, 125, 125, 125, 125, 125, 333, 0, 0, 0, 654, 125, 125, 125, 125,
- 333, 0, 0, 247, 125, 125, 125, 125, 655, 27, 656, 657, 658, 659, 660, 661,
- 662, 663, 664, 663, 125, 125, 125, 665, 0, 0, 357, 0, 0, 0, 0, 0,
- 0, 603, 226, 333, 333, 333, 0, 606, 0, 0, 247, 125, 125, 125, 666, 0,
- 667, 0, 0, 357, 613, 668, 606, 125, 0, 0, 0, 0, 0, 669, 349, 349,
- 0, 0, 0, 0, 0, 0, 0, 670, 0, 0, 0, 0, 0, 284, 357, 228,
- 357, 0, 0, 0, 671, 284, 0, 0, 671, 0, 247, 668, 125, 125, 125, 125,
- 0, 0, 0, 0, 0, 603, 247, 349, 613, 0, 0, 672, 673, 357, 613, 613,
- 0, 329, 0, 0, 235, 125, 125, 284, 248, 248, 248, 248, 248, 248, 125, 125,
- 248, 248, 248, 318, 248, 248, 248, 248, 248, 317, 248, 248, 248, 248, 248, 248,
- 248, 248, 584, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 674, 248,
- 248, 248, 248, 248, 248, 317, 125, 125, 248, 317, 125, 125, 125, 125, 125, 125,
- 248, 248, 248, 248, 675, 248, 248, 248, 248, 248, 248, 125, 125, 125, 125, 125,
- 676, 125, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 1, 2, 2, 2,
- 2, 2, 3, 0, 0, 0, 4, 0, 2, 2, 2, 2, 2, 3, 2, 2,
- 2, 2, 5, 0, 2, 5, 6, 0, 7, 7, 7, 7, 8, 9, 10, 11,
- 12, 13, 14, 15, 8, 8, 8, 8, 16, 8, 8, 8, 17, 18, 18, 18,
- 19, 19, 19, 19, 19, 20, 19, 19, 21, 22, 22, 22, 22, 22, 22, 22,
- 22, 23, 21, 22, 22, 22, 23, 21, 24, 25, 25, 25, 25, 25, 25, 25,
- 25, 25, 12, 12, 25, 25, 26, 27, 25, 28, 12, 12, 29, 30, 29, 31,
- 29, 29, 32, 32, 29, 29, 29, 29, 31, 29, 33, 7, 7, 34, 29, 29,
- 35, 29, 29, 29, 29, 29, 29, 30, 36, 36, 36, 37, 36, 36, 36, 36,
- 36, 36, 38, 39, 40, 40, 40, 40, 41, 12, 12, 12, 42, 42, 42, 42,
- 42, 42, 43, 44, 45, 45, 45, 45, 45, 45, 45, 46, 45, 45, 45, 47,
- 48, 48, 48, 48, 48, 48, 48, 49, 36, 36, 38, 12, 29, 29, 29, 50,
- 51, 12, 29, 29, 52, 29, 29, 29, 53, 53, 53, 53, 54, 55, 53, 53,
- 53, 56, 53, 53, 57, 58, 57, 59, 59, 57, 57, 57, 57, 57, 60, 57,
- 61, 62, 63, 57, 57, 59, 59, 64, 12, 65, 12, 66, 57, 62, 57, 57,
- 57, 57, 57, 64, 67, 67, 68, 69, 70, 71, 71, 71, 71, 71, 72, 71,
- 72, 73, 74, 72, 68, 69, 70, 74, 75, 12, 67, 76, 12, 77, 71, 71,
- 71, 68, 12, 12, 78, 78, 79, 80, 80, 79, 79, 79, 79, 79, 81, 79,
- 81, 78, 82, 79, 79, 80, 80, 82, 83, 12, 12, 12, 79, 84, 79, 79,
- 82, 12, 78, 79, 85, 85, 86, 87, 87, 86, 86, 86, 86, 86, 88, 86,
- 88, 85, 89, 86, 86, 87, 87, 89, 12, 85, 12, 90, 86, 91, 86, 86,
- 86, 86, 12, 12, 92, 93, 94, 92, 95, 96, 97, 95, 98, 99, 94, 92,
- 100, 100, 96, 92, 94, 92, 95, 96, 99, 98, 12, 12, 12, 92, 100, 100,
- 100, 100, 94, 12, 101, 101, 101, 102, 102, 101, 101, 101, 101, 101, 102, 101,
- 101, 101, 103, 101, 101, 102, 102, 103, 12, 104, 105, 106, 101, 107, 101, 101,
- 12, 108, 101, 101, 109, 109, 109, 110, 110, 109, 109, 109, 109, 109, 110, 109,
- 109, 111, 112, 109, 109, 110, 110, 112, 12, 113, 12, 113, 109, 114, 109, 109,
- 111, 12, 12, 12, 115, 115, 115, 116, 116, 115, 115, 115, 115, 115, 115, 115,
- 115, 116, 116, 115, 12, 115, 115, 115, 115, 117, 115, 115, 118, 118, 119, 119,
- 119, 120, 121, 119, 119, 119, 119, 119, 122, 119, 119, 123, 119, 120, 124, 125,
- 119, 126, 119, 119, 12, 121, 119, 119, 121, 127, 12, 12, 128, 129, 129, 129,
- 129, 129, 129, 129, 129, 129, 130, 131, 129, 129, 129, 12, 12, 12, 12, 12,
- 132, 133, 134, 135, 135, 135, 135, 135, 135, 136, 135, 135, 135, 135, 135, 137,
- 135, 138, 135, 134, 135, 135, 137, 135, 139, 139, 139, 139, 139, 139, 140, 139,
- 139, 139, 139, 141, 140, 139, 139, 139, 139, 139, 139, 142, 139, 143, 144, 12,
- 145, 145, 145, 145, 146, 146, 146, 146, 146, 147, 12, 148, 146, 146, 149, 146,
- 150, 150, 150, 150, 151, 151, 151, 151, 151, 151, 152, 153, 151, 154, 152, 153,
- 152, 153, 151, 154, 152, 153, 151, 151, 151, 154, 151, 151, 151, 151, 154, 155,
- 151, 151, 151, 156, 151, 151, 153, 12, 157, 157, 157, 157, 157, 158, 157, 158,
- 159, 159, 159, 159, 160, 160, 160, 160, 160, 160, 160, 161, 162, 162, 162, 162,
- 162, 162, 163, 164, 162, 162, 165, 12, 166, 166, 166, 166, 166, 167, 12, 168,
- 169, 169, 169, 169, 169, 170, 12, 12, 171, 171, 171, 171, 171, 12, 12, 12,
- 172, 172, 172, 173, 173, 12, 12, 12, 174, 174, 174, 174, 174, 174, 174, 175,
- 174, 174, 175, 12, 176, 177, 178, 178, 178, 178, 179, 12, 178, 178, 178, 178,
- 178, 178, 180, 12, 178, 178, 181, 12, 159, 182, 12, 12, 183, 183, 183, 183,
- 183, 183, 183, 184, 183, 183, 183, 12, 185, 183, 183, 183, 186, 186, 186, 186,
- 186, 186, 186, 187, 186, 188, 12, 12, 189, 189, 189, 189, 189, 189, 189, 12,
- 189, 189, 190, 12, 189, 189, 191, 192, 193, 193, 193, 193, 193, 193, 193, 194,
- 195, 195, 195, 195, 195, 195, 195, 196, 195, 195, 195, 197, 195, 195, 198, 12,
- 195, 195, 195, 198, 7, 7, 7, 199, 200, 200, 200, 200, 200, 200, 200, 201,
- 200, 200, 200, 202, 203, 203, 203, 203, 204, 204, 204, 204, 204, 12, 12, 204,
- 205, 205, 205, 205, 205, 205, 206, 205, 205, 205, 207, 208, 209, 209, 209, 209,
- 19, 19, 210, 12, 146, 146, 211, 212, 203, 203, 12, 12, 213, 7, 7, 7,
- 214, 7, 215, 216, 0, 215, 217, 12, 2, 218, 219, 2, 2, 2, 2, 220,
- 221, 218, 222, 2, 2, 2, 223, 2, 2, 2, 2, 224, 8, 225, 8, 225,
- 8, 8, 226, 226, 8, 8, 8, 225, 8, 15, 8, 8, 8, 10, 8, 227,
- 10, 15, 8, 14, 0, 0, 0, 228, 0, 229, 0, 0, 230, 0, 0, 231,
- 0, 0, 0, 232, 2, 2, 2, 233, 234, 12, 12, 12, 235, 12, 12, 12,
- 0, 236, 237, 0, 4, 0, 0, 0, 0, 0, 0, 4, 2, 2, 5, 12,
- 0, 232, 12, 12, 0, 0, 232, 12, 238, 238, 238, 238, 0, 239, 0, 0,
- 0, 240, 0, 0, 241, 241, 241, 241, 18, 18, 18, 18, 18, 12, 242, 18,
- 243, 243, 243, 243, 243, 243, 12, 244, 245, 12, 12, 244, 151, 154, 12, 12,
- 151, 154, 151, 154, 0, 0, 0, 246, 247, 247, 247, 247, 247, 247, 248, 247,
- 247, 12, 12, 12, 247, 249, 12, 12, 0, 250, 0, 0, 251, 247, 252, 253,
- 0, 0, 247, 0, 254, 255, 255, 255, 255, 255, 255, 255, 255, 256, 257, 258,
- 259, 260, 260, 260, 260, 260, 260, 260, 260, 260, 261, 259, 12, 262, 263, 263,
- 263, 263, 263, 263, 264, 150, 150, 150, 150, 150, 150, 265, 0, 12, 12, 131,
- 150, 150, 150, 266, 260, 260, 260, 261, 260, 260, 0, 0, 267, 267, 267, 267,
- 267, 267, 267, 268, 267, 269, 12, 12, 270, 270, 270, 270, 271, 271, 271, 271,
- 271, 271, 271, 12, 272, 272, 272, 272, 272, 272, 12, 12, 237, 2, 2, 2,
- 2, 2, 231, 2, 2, 2, 273, 12, 274, 275, 276, 12, 277, 2, 2, 2,
- 278, 278, 278, 278, 278, 278, 278, 279, 0, 0, 246, 12, 280, 280, 280, 280,
- 280, 280, 12, 12, 281, 281, 281, 281, 281, 282, 12, 283, 281, 281, 282, 12,
- 284, 284, 284, 284, 284, 284, 284, 285, 286, 286, 286, 286, 286, 12, 12, 287,
- 150, 150, 150, 288, 289, 289, 289, 289, 289, 289, 289, 290, 289, 289, 291, 292,
- 145, 145, 145, 293, 294, 294, 294, 294, 294, 295, 12, 12, 294, 294, 294, 296,
- 294, 294, 296, 294, 297, 297, 297, 297, 298, 12, 12, 12, 12, 12, 299, 297,
- 300, 300, 300, 300, 300, 301, 12, 12, 155, 154, 155, 154, 155, 154, 12, 12,
- 2, 2, 3, 2, 2, 302, 303, 12, 300, 300, 300, 304, 300, 300, 304, 12,
- 150, 12, 12, 12, 150, 265, 305, 150, 150, 150, 150, 12, 247, 247, 247, 249,
- 247, 247, 249, 12, 2, 273, 12, 12, 306, 22, 12, 24, 25, 26, 25, 307,
- 308, 309, 25, 25, 50, 12, 12, 12, 310, 29, 29, 29, 29, 29, 29, 311,
- 312, 29, 29, 29, 29, 29, 12, 310, 7, 7, 7, 313, 232, 0, 0, 0,
- 0, 232, 0, 12, 29, 314, 29, 29, 29, 29, 29, 315, 316, 0, 0, 0,
- 0, 317, 260, 260, 260, 260, 260, 318, 319, 150, 319, 150, 319, 150, 319, 288,
- 0, 232, 0, 232, 12, 12, 316, 246, 320, 320, 320, 321, 320, 320, 320, 320,
- 320, 322, 320, 320, 320, 320, 322, 323, 320, 320, 320, 324, 320, 320, 322, 12,
- 232, 131, 0, 0, 0, 131, 0, 0, 8, 8, 8, 14, 0, 0, 0, 234,
- 325, 12, 12, 12, 0, 0, 0, 326, 327, 327, 327, 327, 327, 327, 327, 328,
- 329, 329, 329, 329, 330, 12, 12, 12, 215, 0, 0, 0, 0, 0, 0, 12,
- 331, 331, 331, 331, 331, 12, 12, 332, 333, 333, 333, 333, 333, 333, 334, 12,
- 335, 335, 335, 335, 335, 335, 336, 12, 337, 337, 337, 337, 337, 337, 337, 338,
- 339, 339, 339, 339, 339, 12, 339, 339, 339, 340, 12, 12, 341, 341, 341, 341,
- 342, 342, 342, 342, 343, 343, 343, 343, 343, 343, 343, 344, 343, 343, 344, 12,
- 345, 345, 345, 345, 345, 12, 345, 345, 345, 345, 345, 12, 346, 346, 346, 346,
- 346, 346, 12, 12, 347, 347, 347, 347, 347, 12, 12, 348, 349, 349, 350, 349,
- 350, 351, 349, 349, 351, 349, 349, 349, 351, 349, 351, 352, 353, 353, 353, 353,
- 353, 354, 12, 12, 353, 355, 12, 12, 353, 353, 12, 12, 2, 274, 2, 2,
- 356, 2, 273, 12, 357, 358, 359, 357, 357, 357, 357, 357, 357, 360, 361, 362,
- 363, 363, 363, 363, 363, 364, 363, 363, 365, 365, 365, 365, 366, 366, 366, 366,
- 366, 366, 366, 367, 12, 368, 366, 366, 369, 369, 369, 369, 370, 371, 372, 369,
- 373, 373, 373, 373, 373, 373, 373, 374, 375, 375, 375, 375, 375, 375, 376, 377,
- 378, 378, 378, 378, 379, 379, 379, 379, 379, 379, 12, 379, 380, 379, 379, 379,
- 381, 382, 12, 381, 381, 383, 383, 381, 381, 381, 381, 381, 381, 384, 385, 386,
- 381, 381, 387, 12, 388, 388, 388, 388, 389, 389, 389, 389, 390, 390, 390, 390,
- 390, 391, 392, 390, 390, 391, 12, 12, 393, 393, 393, 393, 393, 394, 395, 393,
- 396, 396, 396, 396, 396, 397, 396, 396, 398, 398, 398, 398, 399, 12, 398, 398,
- 400, 400, 400, 400, 401, 12, 402, 403, 12, 12, 402, 400, 404, 404, 404, 404,
- 404, 404, 405, 12, 406, 406, 406, 406, 407, 12, 12, 12, 407, 12, 408, 406,
- 409, 409, 409, 409, 409, 409, 12, 12, 409, 409, 410, 12, 411, 411, 411, 411,
- 411, 411, 412, 413, 413, 12, 12, 12, 12, 12, 12, 414, 415, 415, 415, 415,
- 415, 415, 12, 12, 416, 416, 416, 416, 416, 416, 417, 12, 418, 418, 418, 418,
- 418, 418, 419, 12, 420, 420, 420, 420, 420, 420, 420, 12, 421, 421, 421, 421,
- 421, 422, 12, 12, 423, 423, 423, 423, 423, 423, 423, 424, 425, 423, 423, 423,
- 423, 424, 12, 426, 427, 427, 427, 427, 428, 12, 12, 429, 430, 430, 430, 430,
- 430, 430, 431, 12, 430, 430, 432, 12, 433, 433, 433, 433, 433, 434, 433, 433,
- 433, 433, 12, 12, 435, 435, 435, 435, 435, 436, 12, 12, 437, 437, 437, 437,
- 118, 119, 119, 119, 119, 127, 12, 12, 438, 438, 438, 438, 439, 438, 438, 438,
- 440, 12, 12, 12, 441, 442, 443, 444, 441, 441, 441, 444, 441, 441, 445, 12,
- 446, 446, 446, 446, 446, 446, 447, 12, 446, 446, 448, 12, 449, 450, 449, 451,
- 451, 449, 449, 449, 449, 449, 452, 449, 452, 450, 453, 449, 449, 451, 451, 454,
- 455, 456, 12, 450, 449, 457, 449, 455, 449, 455, 12, 12, 458, 458, 458, 458,
- 458, 458, 458, 459, 460, 12, 12, 12, 461, 461, 461, 461, 461, 461, 12, 12,
- 461, 461, 462, 12, 463, 463, 463, 463, 463, 464, 463, 463, 463, 463, 463, 464,
- 465, 465, 465, 465, 465, 466, 12, 12, 465, 465, 467, 12, 178, 178, 178, 180,
- 468, 468, 468, 468, 468, 468, 469, 12, 470, 470, 470, 470, 470, 470, 471, 472,
- 470, 470, 470, 12, 470, 471, 12, 12, 473, 473, 473, 473, 473, 473, 473, 12,
- 474, 474, 474, 474, 475, 12, 12, 476, 477, 478, 479, 477, 477, 480, 477, 477,
- 477, 477, 477, 477, 477, 481, 482, 477, 477, 478, 12, 12, 477, 477, 483, 12,
- 484, 484, 485, 484, 484, 484, 484, 484, 484, 486, 12, 12, 487, 487, 487, 487,
- 487, 487, 12, 12, 488, 488, 488, 488, 489, 12, 12, 12, 490, 490, 490, 490,
- 490, 490, 491, 12, 53, 53, 492, 12, 493, 493, 494, 493, 493, 493, 493, 493,
- 493, 495, 493, 493, 493, 496, 12, 12, 493, 493, 493, 497, 498, 498, 498, 498,
- 499, 498, 498, 498, 498, 498, 500, 498, 498, 501, 12, 12, 502, 503, 504, 502,
- 502, 502, 502, 502, 502, 503, 505, 504, 502, 502, 12, 12, 502, 502, 506, 12,
- 507, 508, 509, 507, 507, 507, 507, 507, 507, 507, 507, 510, 508, 507, 511, 12,
- 507, 507, 512, 12, 513, 513, 513, 513, 513, 513, 514, 12, 515, 515, 515, 515,
- 516, 515, 515, 515, 515, 515, 517, 518, 515, 515, 519, 12, 520, 12, 12, 12,
- 100, 100, 100, 100, 96, 12, 12, 98, 521, 521, 521, 521, 521, 521, 522, 12,
- 521, 521, 521, 523, 521, 524, 12, 12, 521, 12, 12, 12, 525, 525, 525, 525,
- 526, 12, 12, 12, 527, 527, 527, 527, 527, 528, 12, 12, 529, 529, 529, 529,
- 529, 530, 12, 12, 272, 272, 531, 12, 532, 532, 532, 532, 532, 532, 532, 533,
- 532, 532, 534, 535, 536, 536, 536, 536, 536, 536, 536, 537, 536, 536, 538, 12,
- 539, 539, 539, 539, 539, 539, 539, 540, 539, 540, 12, 12, 541, 541, 541, 541,
- 541, 542, 12, 12, 541, 541, 543, 541, 543, 541, 541, 541, 541, 541, 12, 544,
- 545, 545, 545, 545, 545, 545, 546, 12, 547, 547, 547, 547, 547, 547, 548, 549,
- 547, 547, 12, 549, 550, 551, 12, 12, 249, 12, 12, 12, 552, 552, 552, 552,
- 552, 552, 12, 12, 553, 553, 553, 553, 553, 554, 12, 12, 552, 552, 555, 12,
- 260, 556, 260, 557, 558, 255, 255, 255, 559, 12, 12, 12, 560, 12, 12, 12,
- 256, 561, 12, 12, 12, 260, 12, 12, 562, 562, 562, 562, 562, 562, 562, 12,
- 563, 563, 563, 563, 563, 563, 564, 12, 563, 563, 563, 565, 563, 563, 565, 12,
- 563, 563, 566, 563, 0, 12, 12, 12, 7, 7, 7, 567, 7, 199, 12, 12,
- 0, 246, 12, 12, 0, 232, 316, 0, 0, 568, 228, 0, 0, 0, 568, 7,
- 213, 569, 7, 0, 0, 0, 570, 228, 8, 225, 12, 12, 0, 0, 234, 12,
- 0, 0, 0, 229, 571, 572, 316, 229, 0, 0, 240, 316, 0, 316, 0, 0,
- 0, 240, 232, 316, 0, 229, 0, 229, 0, 0, 240, 232, 0, 573, 239, 0,
- 229, 0, 0, 0, 0, 246, 0, 0, 0, 0, 0, 239, 574, 574, 574, 574,
- 574, 574, 574, 12, 12, 12, 575, 574, 576, 574, 574, 574, 2, 2, 2, 273,
- 12, 275, 273, 12, 241, 577, 241, 241, 241, 241, 578, 241, 579, 580, 577, 12,
- 19, 19, 19, 581, 12, 12, 12, 582, 583, 583, 583, 583, 583, 583, 583, 584,
- 583, 583, 583, 585, 583, 583, 585, 586, 587, 587, 587, 587, 587, 587, 587, 588,
- 589, 589, 589, 589, 589, 589, 590, 591, 592, 592, 592, 592, 592, 592, 593, 12,
- 151, 154, 151, 594, 151, 151, 151, 154, 595, 595, 595, 595, 595, 596, 595, 595,
- 595, 597, 12, 12, 598, 598, 598, 598, 598, 598, 598, 12, 598, 598, 599, 600,
- 0, 234, 12, 12, 29, 414, 29, 29, 601, 602, 414, 29, 50, 29, 603, 12,
- 604, 310, 603, 414, 601, 602, 603, 603, 601, 602, 50, 29, 50, 29, 414, 605,
- 29, 29, 606, 29, 29, 29, 29, 12, 414, 414, 606, 29, 51, 12, 12, 12,
- 12, 239, 0, 0, 607, 12, 12, 12, 246, 12, 12, 12, 0, 0, 12, 0,
- 0, 232, 131, 0, 0, 0, 12, 12, 0, 0, 0, 240, 0, 246, 12, 239,
- 608, 12, 12, 12, 247, 247, 609, 12, 610, 12, 12, 12, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 939, 940, 941, 942,
- 946, 948, 0, 962, 969, 970, 971, 976,1001,1002,1003,1008, 0,1033,1040,1041,
- 1042,1043,1047, 0, 0,1080,1081,1082,1086,1110, 0, 0,1124,1125,1126,1127,
- 1131,1133, 0,1147,1154,1155,1156,1161,1187,1188,1189,1193, 0,1219,1226,1227,
- 1228,1229,1233, 0, 0,1267,1268,1269,1273,1298, 0,1303, 943,1128, 944,1129,
- 954,1139, 958,1143, 959,1144, 960,1145, 961,1146, 964,1149, 0, 0, 973,1158,
- 974,1159, 975,1160, 983,1168, 978,1163, 988,1173, 990,1175, 991,1176, 993,1178,
- 994,1179, 0, 0,1004,1190,1005,1191,1006,1192,1014,1199,1007, 0, 0, 0,
- 1016,1201,1020,1206, 0,1022,1208,1025,1211,1023,1209, 0, 0, 0, 0,1032,
- 1218,1037,1223,1035,1221, 0, 0, 0,1044,1230,1045,1231,1049,1235, 0, 0,
- 1058,1244,1064,1250,1060,1246,1066,1252,1067,1253,1072,1258,1069,1255,1077,1264,
- 1074,1261, 0, 0,1083,1270,1084,1271,1085,1272,1088,1275,1089,1276,1096,1283,
- 1103,1290,1111,1299,1115,1118,1307,1120,1309,1121,1310, 0,1053,1239, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1093,1280, 0, 0, 0,
+ 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 705,
+ 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 391, 706,
+ 0, 0, 1, 1, 0, 2, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 5, 0, 6, 7, 7, 7, 8, 9, 10, 11, 12,
+ 13, 13, 13, 13, 14, 13, 13, 13, 13, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 23, 23, 26, 23, 27, 28, 29, 23, 30, 31, 32, 33,
+ 34, 35, 36, 37, 38, 23, 23, 39, 40, 40, 41, 42, 43, 44, 45, 46,
+ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78,
+ 79, 80, 81, 82, 83, 84, 85, 82, 86, 86, 87, 88, 89, 90, 91, 82,
+ 92, 92, 92, 92, 92, 93, 94, 95, 96, 96, 96, 96, 96, 96, 96, 96,
+ 97, 97, 98, 97, 99, 100, 101, 97, 102, 97, 103, 104, 105, 106, 106, 107,
+ 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 109, 110, 110, 111,
+ 112, 113, 114, 115, 116, 116, 117, 118, 119, 120, 120, 121, 120, 122, 108, 123,
+ 124, 125, 126, 127, 128, 129, 130, 116, 131, 132, 133, 134, 135, 136, 137, 82,
+ 138, 138, 139, 138, 140, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150,
+ 4, 151, 152, 153, 4, 154, 7, 7, 155, 11, 156, 157, 11, 158, 159, 160,
+ 161, 0, 0, 162, 163, 0, 164, 165, 0, 166, 167, 4, 168, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 169, 170, 0, 0, 0, 0, 0,
+ 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 172, 173, 0, 0, 0,
+ 174, 174, 174, 4, 175, 175, 175, 176, 93, 177, 178, 179, 180, 181, 181, 13,
+ 0, 0, 182, 82, 183, 184, 184, 185, 184, 184, 184, 184, 184, 184, 186, 187,
+ 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 96, 96, 198, 199, 0, 200,
+ 201, 0, 0, 202, 0, 0, 203, 204, 194, 194, 205, 0, 0, 0, 0, 0,
+ 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 0, 0,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 207, 206, 208, 209,
+ 210, 210, 210, 210, 210, 210, 210, 210, 210, 211, 13, 13, 13, 212, 212, 213,
+ 0, 214, 4, 4, 215, 4, 216, 217, 218, 219, 220, 221, 222, 222, 223, 40,
+ 224, 225, 226, 227, 228, 228, 229, 230, 231, 232, 233, 92, 234, 234, 235, 236,
+ 237, 238, 239, 240, 106, 106, 241, 242, 96, 96, 96, 96, 96, 243, 244, 245,
+ 82, 82, 82, 82, 82, 82, 82, 82, 184, 184, 184, 246, 184, 184, 247, 82,
+ 248, 249, 250, 23, 23, 23, 251, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 252, 23, 23, 253, 23, 254, 255, 256, 257, 258, 259, 23, 23, 23, 260,
+ 261, 1, 1, 262, 263, 201, 264, 265, 266, 267, 268, 82, 269, 269, 269, 270,
+ 271, 272, 11, 11, 273, 274, 187, 275, 82, 82, 82, 82, 276, 277, 278, 279,
+ 280, 281, 282, 283, 284, 285, 286, 82, 287, 287, 288, 289, 290, 291, 292, 293,
+ 294, 295, 296, 297, 298, 299, 300, 301, 302, 302, 302, 302, 302, 302, 302, 302,
+ 302, 303, 304, 305, 306, 307, 82, 82, 308, 309, 310, 311, 312, 313, 82, 314,
+ 315, 316, 82, 82, 317, 318, 319, 320, 321, 322, 323, 324, 325, 82, 326, 327,
+ 328, 329, 330, 331, 332, 333, 82, 82, 334, 334, 335, 82, 336, 337, 336, 338,
+ 339, 340, 341, 342, 343, 82, 82, 82, 82, 82, 82, 344, 345, 346, 347, 348,
+ 349, 350, 351, 352, 353, 354, 355, 356, 357, 357, 358, 359, 360, 360, 361, 362,
+ 363, 364, 365, 366, 367, 367, 367, 368, 369, 370, 371, 82, 372, 373, 374, 375,
+ 376, 377, 378, 379, 380, 381, 382, 383, 384, 384, 385, 386, 387, 387, 388, 82,
+ 82, 82, 82, 82, 389, 390, 391, 82, 392, 392, 393, 394, 395, 396, 397, 398,
+ 399, 400, 401, 82, 82, 82, 82, 82, 402, 403, 82, 82, 82, 404, 404, 405,
+ 406, 407, 408, 82, 82, 409, 410, 411, 412, 412, 413, 414, 414, 415, 416, 417,
+ 418, 82, 82, 82, 82, 82, 419, 420, 421, 422, 423, 424, 425, 426, 82, 82,
+ 427, 428, 429, 430, 431, 432, 82, 82, 82, 82, 82, 82, 82, 82, 82, 433,
+ 434, 435, 436, 82, 82, 437, 438, 439, 440, 440, 440, 440, 440, 440, 440, 440,
+ 440, 440, 440, 440, 441, 82, 82, 82, 440, 440, 440, 442, 440, 440, 440, 440,
+ 440, 440, 443, 82, 82, 82, 82, 82, 82, 82, 82, 82, 444, 445, 445, 446,
+ 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 448, 447, 447, 447, 447, 447,
+ 447, 447, 447, 447, 447, 447, 447, 449, 450, 450, 450, 450, 450, 450, 450, 450,
+ 450, 450, 451, 82, 82, 82, 82, 82, 452, 453, 82, 82, 82, 82, 82, 82,
+ 212, 212, 212, 212, 212, 212, 212, 212, 212, 454, 455, 456, 457, 458, 459, 460,
+ 461, 461, 462, 463, 464, 82, 82, 82, 82, 82, 465, 466, 82, 82, 82, 82,
+ 82, 82, 467, 467, 468, 82, 82, 82, 469, 469, 470, 469, 471, 82, 82, 472,
+ 473, 473, 473, 473, 473, 473, 473, 473, 473, 473, 473, 473, 473, 473, 473, 474,
+ 475, 475, 475, 475, 475, 475, 475, 475, 475, 475, 475, 475, 475, 475, 476, 477,
+ 478, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 479,
+ 480, 191, 191, 191, 191, 191, 191, 191, 191, 481, 482, 483, 484, 484, 484, 484,
+ 484, 484, 484, 484, 484, 484, 484, 485, 486, 486, 486, 487, 488, 489, 82, 82,
+ 0, 0, 0, 0, 0, 0, 0, 490, 0, 0, 0, 0, 0, 491, 82, 82,
+ 7, 492, 493, 0, 0, 0, 489, 82, 0, 0, 0, 0, 0, 0, 0, 494,
+ 0, 495, 0, 496, 497, 498, 0, 170, 11, 11, 499, 82, 82, 82, 491, 491,
+ 0, 0, 500, 501, 82, 82, 82, 82, 0, 0, 502, 0, 503, 504, 505, 0,
+ 506, 507, 508, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 509, 0, 0,
+ 0, 0, 0, 0, 0, 0, 510, 0, 511, 511, 511, 511, 511, 511, 511, 511,
+ 511, 511, 511, 511, 512, 513, 82, 82, 514, 515, 82, 82, 82, 82, 82, 82,
+ 516, 517, 13, 518, 519, 82, 82, 82, 520, 521, 522, 82, 82, 82, 82, 82,
+ 82, 82, 82, 82, 523, 524, 525, 526, 82, 82, 82, 82, 82, 82, 527, 528,
+ 82, 82, 82, 82, 82, 82, 529, 530, 82, 82, 82, 82, 82, 82, 82, 531,
+ 532, 532, 532, 532, 532, 532, 533, 82, 534, 534, 535, 82, 82, 82, 82, 82,
+ 82, 82, 82, 536, 0, 537, 82, 82, 261, 182, 82, 82, 82, 82, 82, 82,
+ 538, 539, 540, 541, 542, 543, 82, 544, 0, 545, 0, 0, 491, 546, 547, 494,
+ 0, 0, 0, 0, 0, 548, 82, 549, 550, 551, 552, 553, 82, 82, 82, 82,
+ 0, 0, 0, 0, 0, 0, 554, 555, 0, 0, 0, 556, 0, 0, 490, 557,
+ 545, 0, 558, 0, 559, 560, 561, 82, 0, 0, 491, 562, 563, 0, 564, 565,
+ 0, 0, 0, 0, 258, 0, 0, 490, 184, 184, 184, 184, 184, 184, 184, 82,
+ 184, 247, 184, 184, 184, 184, 184, 184, 566, 184, 184, 184, 184, 184, 184, 184,
+ 184, 184, 184, 184, 184, 567, 184, 184, 184, 184, 184, 184, 184, 184, 184, 568,
+ 184, 184, 566, 82, 82, 82, 82, 82, 566, 82, 82, 82, 82, 82, 82, 82,
+ 184, 184, 569, 184, 184, 184, 184, 184, 184, 184, 184, 184, 184, 570, 82, 82,
+ 571, 0, 0, 0, 82, 82, 82, 82, 7, 7, 7, 7, 7, 7, 7, 572,
+ 0, 0, 0, 0, 1, 2, 2, 3, 0, 4, 0, 4, 2, 2, 5, 2,
+ 2, 2, 2, 2, 2, 2, 2, 6, 7, 8, 0, 0, 9, 9, 9, 9,
+ 9, 9, 10, 11, 12, 13, 14, 14, 15, 14, 14, 14, 14, 14, 14, 14,
+ 16, 17, 14, 14, 18, 18, 18, 18, 19, 18, 18, 18, 18, 18, 20, 21,
+ 21, 21, 22, 20, 21, 21, 21, 21, 21, 23, 24, 25, 25, 25, 25, 25,
+ 25, 26, 25, 25, 25, 27, 28, 26, 29, 30, 31, 32, 31, 31, 31, 31,
+ 33, 34, 35, 31, 31, 31, 36, 31, 31, 31, 31, 29, 37, 38, 37, 37,
+ 37, 37, 37, 37, 37, 39, 31, 31, 40, 40, 40, 40, 40, 40, 41, 26,
+ 42, 42, 42, 42, 42, 42, 42, 43, 44, 44, 44, 44, 44, 45, 44, 46,
+ 47, 47, 47, 48, 37, 49, 31, 31, 31, 50, 51, 31, 52, 31, 31, 31,
+ 53, 53, 53, 53, 53, 53, 54, 53, 55, 53, 53, 53, 56, 57, 58, 59,
+ 59, 60, 61, 62, 57, 63, 64, 65, 66, 59, 59, 67, 68, 69, 70, 71,
+ 71, 72, 73, 74, 69, 75, 76, 77, 78, 71, 79, 26, 80, 81, 82, 83,
+ 83, 84, 85, 86, 81, 87, 88, 26, 89, 83, 90, 91, 92, 93, 94, 95,
+ 95, 96, 97, 98, 93, 99, 100, 101, 102, 95, 95, 26, 103, 104, 105, 106,
+ 107, 104, 108, 109, 104, 105, 110, 26, 111, 108, 108, 112, 113, 114, 115, 113,
+ 113, 115, 113, 116, 114, 117, 118, 119, 120, 113, 121, 113, 122, 123, 124, 122,
+ 122, 124, 125, 126, 123, 127, 128, 128, 129, 122, 130, 26, 131, 132, 133, 131,
+ 131, 131, 131, 131, 132, 133, 134, 131, 135, 131, 131, 131, 136, 137, 138, 139,
+ 137, 137, 140, 141, 138, 142, 143, 137, 144, 137, 145, 26, 146, 147, 147, 147,
+ 147, 147, 147, 148, 147, 147, 147, 149, 26, 26, 26, 26, 150, 151, 152, 152,
+ 153, 152, 152, 154, 155, 156, 152, 157, 158, 158, 158, 158, 158, 159, 158, 158,
+ 158, 160, 159, 158, 158, 158, 158, 159, 158, 158, 158, 161, 158, 161, 162, 163,
+ 164, 164, 164, 164, 165, 165, 165, 165, 166, 167, 165, 165, 165, 165, 165, 168,
+ 169, 169, 169, 169, 170, 170, 170, 170, 170, 171, 172, 171, 170, 171, 170, 170,
+ 170, 170, 171, 172, 171, 170, 172, 170, 170, 170, 171, 170, 170, 170, 170, 173,
+ 170, 170, 170, 174, 170, 170, 170, 175, 176, 176, 176, 176, 176, 176, 177, 177,
+ 178, 178, 178, 178, 179, 179, 179, 180, 181, 181, 181, 181, 181, 182, 181, 183,
+ 184, 184, 185, 186, 187, 187, 188, 26, 189, 189, 190, 26, 191, 192, 193, 26,
+ 194, 194, 194, 194, 194, 194, 194, 195, 194, 196, 194, 196, 197, 198, 198, 199,
+ 198, 198, 198, 198, 198, 198, 198, 200, 198, 201, 178, 178, 178, 178, 202, 26,
+ 203, 203, 203, 204, 203, 205, 203, 205, 206, 203, 207, 207, 207, 208, 209, 26,
+ 210, 210, 210, 210, 210, 211, 210, 210, 210, 212, 210, 213, 214, 214, 214, 215,
+ 216, 216, 216, 216, 216, 216, 216, 217, 216, 216, 216, 218, 216, 219, 216, 219,
+ 216, 220, 9, 9, 9, 221, 26, 26, 222, 222, 222, 222, 222, 223, 222, 222,
+ 224, 224, 224, 224, 225, 225, 225, 225, 225, 225, 226, 227, 228, 228, 228, 228,
+ 228, 228, 228, 229, 228, 230, 231, 231, 231, 231, 231, 231, 18, 232, 165, 165,
+ 165, 165, 165, 233, 224, 26, 234, 9, 235, 236, 237, 238, 239, 240, 2, 2,
+ 2, 2, 2, 241, 242, 243, 2, 244, 2, 2, 2, 245, 14, 14, 246, 246,
+ 246, 246, 14, 247, 14, 14, 14, 246, 14, 14, 248, 14, 248, 14, 249, 250,
+ 14, 14, 251, 252, 0, 253, 0, 0, 254, 0, 255, 256, 0, 257, 2, 258,
+ 259, 26, 9, 9, 9, 9, 260, 26, 261, 262, 4, 0, 0, 263, 0, 0,
+ 2, 264, 0, 0, 0, 265, 26, 26, 0, 266, 26, 26, 267, 267, 267, 267,
+ 0, 0, 268, 0, 0, 0, 269, 0, 270, 270, 270, 270, 17, 17, 17, 17,
+ 17, 17, 271, 272, 166, 167, 273, 273, 273, 273, 273, 273, 273, 274, 275, 274,
+ 170, 170, 172, 26, 172, 172, 172, 172, 0, 0, 0, 276, 277, 277, 277, 278,
+ 277, 277, 277, 277, 277, 277, 279, 26, 277, 277, 280, 26, 26, 26, 0, 0,
+ 281, 0, 0, 0, 282, 283, 0, 284, 285, 286, 286, 286, 286, 286, 286, 286,
+ 286, 286, 287, 288, 289, 290, 290, 290, 290, 290, 290, 290, 290, 290, 290, 291,
+ 292, 293, 293, 293, 293, 293, 294, 169, 169, 295, 0, 0, 293, 293, 293, 293,
+ 276, 296, 290, 290, 169, 169, 169, 295, 169, 169, 169, 297, 0, 0, 290, 290,
+ 290, 290, 290, 298, 290, 290, 290, 0, 299, 299, 299, 299, 299, 300, 299, 299,
+ 301, 26, 302, 302, 302, 302, 302, 302, 303, 303, 303, 303, 303, 304, 26, 26,
+ 305, 305, 305, 305, 305, 305, 305, 26, 306, 2, 2, 2, 2, 307, 2, 2,
+ 2, 308, 309, 258, 26, 26, 310, 2, 311, 311, 311, 311, 311, 312, 0, 265,
+ 313, 313, 313, 313, 313, 313, 313, 26, 314, 314, 314, 314, 315, 316, 314, 317,
+ 318, 318, 318, 318, 318, 319, 320, 320, 320, 320, 321, 322, 169, 169, 169, 323,
+ 324, 324, 324, 324, 324, 325, 324, 326, 164, 164, 164, 327, 328, 328, 328, 328,
+ 328, 328, 329, 26, 328, 330, 328, 331, 332, 332, 332, 332, 333, 26, 26, 334,
+ 335, 335, 336, 26, 337, 337, 337, 26, 172, 172, 2, 2, 2, 2, 2, 338,
+ 339, 340, 176, 176, 335, 335, 335, 335, 335, 341, 335, 342, 343, 26, 169, 169,
+ 295, 344, 169, 169, 169, 169, 169, 343, 277, 280, 277, 277, 277, 277, 277, 345,
+ 346, 26, 347, 348, 25, 25, 349, 350, 351, 25, 31, 31, 352, 26, 353, 31,
+ 31, 31, 31, 354, 31, 31, 355, 31, 31, 356, 26, 26, 26, 26, 31, 31,
+ 9, 9, 0, 265, 9, 357, 0, 0, 0, 0, 358, 0, 257, 359, 360, 31,
+ 31, 31, 31, 361, 362, 0, 0, 0, 363, 290, 289, 290, 290, 290, 290, 364,
+ 365, 365, 365, 366, 257, 257, 26, 367, 368, 369, 368, 368, 370, 368, 368, 371,
+ 368, 372, 368, 372, 368, 368, 368, 368, 368, 368, 368, 373, 374, 0, 0, 0,
+ 0, 0, 375, 0, 14, 252, 0, 376, 377, 26, 26, 26, 0, 0, 0, 378,
+ 379, 379, 379, 380, 381, 381, 381, 381, 381, 381, 382, 26, 383, 0, 0, 359,
+ 384, 384, 384, 384, 385, 386, 387, 387, 387, 388, 389, 389, 389, 389, 389, 390,
+ 391, 391, 391, 392, 393, 393, 393, 393, 394, 393, 395, 26, 396, 396, 396, 396,
+ 396, 396, 397, 397, 397, 397, 397, 397, 398, 398, 398, 399, 398, 400, 401, 401,
+ 401, 401, 402, 401, 401, 401, 401, 402, 403, 403, 403, 403, 403, 26, 404, 404,
+ 404, 404, 404, 404, 405, 406, 407, 408, 407, 408, 409, 407, 410, 407, 410, 411,
+ 412, 412, 412, 412, 412, 412, 413, 26, 414, 414, 414, 414, 414, 414, 415, 26,
+ 414, 414, 416, 26, 414, 26, 26, 26, 417, 2, 2, 2, 2, 2, 418, 419,
+ 420, 421, 422, 422, 422, 422, 423, 424, 425, 425, 426, 425, 427, 427, 427, 427,
+ 428, 428, 428, 429, 430, 428, 26, 26, 431, 431, 432, 433, 434, 434, 434, 435,
+ 436, 436, 436, 437, 438, 438, 438, 438, 439, 439, 439, 440, 439, 439, 441, 439,
+ 439, 439, 439, 439, 442, 443, 444, 445, 446, 446, 447, 448, 446, 449, 446, 449,
+ 450, 450, 450, 450, 451, 451, 451, 451, 452, 452, 452, 452, 453, 454, 453, 26,
+ 455, 455, 455, 455, 455, 455, 456, 457, 458, 458, 459, 458, 460, 460, 461, 460,
+ 462, 462, 463, 464, 26, 465, 26, 26, 466, 466, 466, 466, 466, 467, 26, 26,
+ 468, 468, 468, 468, 468, 468, 469, 26, 468, 468, 469, 470, 471, 471, 471, 471,
+ 471, 26, 471, 472, 473, 473, 473, 473, 474, 475, 473, 473, 474, 476, 26, 26,
+ 31, 31, 31, 50, 477, 477, 477, 477, 477, 478, 479, 26, 480, 26, 26, 26,
+ 26, 26, 26, 481, 482, 482, 482, 482, 482, 26, 483, 483, 483, 483, 483, 484,
+ 26, 26, 485, 485, 485, 486, 26, 26, 26, 26, 487, 487, 487, 488, 26, 26,
+ 489, 489, 490, 26, 491, 491, 491, 491, 491, 492, 493, 491, 491, 491, 492, 494,
+ 495, 495, 495, 495, 496, 497, 498, 498, 498, 499, 498, 500, 501, 501, 501, 501,
+ 501, 501, 502, 501, 501, 26, 503, 503, 503, 503, 504, 26, 505, 505, 505, 505,
+ 506, 137, 507, 26, 508, 508, 509, 508, 508, 508, 508, 508, 510, 26, 26, 26,
+ 511, 512, 513, 514, 513, 515, 516, 516, 516, 516, 516, 516, 516, 517, 516, 518,
+ 519, 520, 521, 522, 522, 523, 524, 525, 520, 526, 527, 528, 529, 530, 530, 26,
+ 531, 532, 531, 531, 531, 531, 533, 531, 534, 535, 533, 536, 537, 26, 26, 26,
+ 538, 538, 538, 538, 538, 538, 538, 539, 540, 26, 26, 26, 541, 541, 541, 541,
+ 541, 26, 541, 542, 543, 543, 543, 543, 543, 543, 544, 543, 543, 543, 543, 544,
+ 545, 545, 545, 545, 546, 26, 545, 547, 198, 548, 26, 26, 549, 549, 549, 549,
+ 549, 549, 549, 550, 549, 550, 164, 164, 551, 26, 26, 26, 552, 552, 552, 553,
+ 552, 554, 552, 552, 555, 26, 26, 26, 556, 556, 556, 556, 556, 556, 556, 557,
+ 558, 558, 558, 558, 558, 558, 559, 560, 561, 562, 563, 564, 564, 564, 565, 566,
+ 561, 26, 564, 567, 568, 569, 568, 568, 568, 568, 568, 569, 570, 26, 26, 26,
+ 571, 571, 571, 571, 571, 26, 572, 572, 572, 572, 572, 572, 573, 26, 178, 178,
+ 574, 574, 574, 574, 574, 574, 574, 575, 53, 576, 26, 26, 577, 577, 577, 577,
+ 578, 26, 577, 578, 579, 580, 579, 579, 579, 579, 581, 579, 582, 26, 579, 579,
+ 579, 583, 584, 584, 584, 584, 585, 584, 584, 586, 587, 26, 588, 589, 590, 590,
+ 590, 590, 588, 591, 590, 26, 590, 592, 593, 594, 595, 595, 595, 596, 597, 598,
+ 595, 599, 26, 26, 600, 600, 600, 601, 602, 602, 603, 602, 602, 602, 602, 604,
+ 602, 602, 602, 605, 26, 26, 606, 26, 108, 108, 108, 108, 108, 108, 607, 608,
+ 609, 609, 609, 609, 609, 609, 609, 610, 609, 611, 612, 26, 613, 26, 26, 26,
+ 26, 26, 614, 614, 614, 614, 614, 614, 614, 614, 615, 26, 616, 616, 616, 616,
+ 616, 616, 617, 26, 616, 616, 616, 618, 619, 619, 619, 619, 620, 26, 26, 26,
+ 621, 621, 621, 621, 621, 621, 621, 622, 305, 305, 305, 623, 624, 624, 624, 625,
+ 624, 626, 627, 627, 627, 627, 627, 627, 627, 627, 627, 628, 627, 629, 630, 630,
+ 630, 631, 631, 26, 632, 632, 632, 632, 633, 26, 632, 634, 634, 632, 632, 635,
+ 632, 632, 26, 26, 636, 636, 636, 636, 636, 636, 636, 637, 638, 638, 638, 638,
+ 638, 638, 638, 639, 640, 640, 640, 640, 640, 641, 640, 640, 640, 642, 640, 640,
+ 643, 26, 345, 26, 644, 644, 644, 644, 644, 644, 644, 26, 645, 645, 645, 645,
+ 645, 645, 646, 26, 26, 26, 26, 647, 644, 648, 26, 26, 26, 26, 649, 650,
+ 651, 286, 286, 286, 652, 26, 653, 26, 26, 26, 654, 26, 655, 26, 656, 656,
+ 656, 656, 656, 656, 656, 656, 656, 657, 658, 658, 658, 658, 658, 659, 658, 660,
+ 658, 661, 658, 662, 359, 26, 26, 26, 0, 0, 0, 265, 0, 0, 359, 26,
+ 9, 663, 9, 9, 221, 26, 0, 0, 0, 0, 276, 26, 257, 362, 0, 0,
+ 664, 665, 0, 666, 667, 668, 0, 0, 0, 669, 0, 0, 246, 26, 26, 26,
+ 0, 0, 257, 26, 0, 0, 0, 259, 0, 0, 254, 0, 0, 0, 0, 254,
+ 670, 671, 0, 672, 673, 0, 0, 0, 269, 674, 254, 254, 0, 0, 0, 675,
+ 676, 677, 678, 0, 276, 0, 0, 0, 0, 268, 0, 0, 679, 679, 679, 679,
+ 679, 680, 26, 681, 682, 679, 26, 26, 2, 2, 2, 346, 683, 419, 26, 26,
+ 684, 270, 270, 685, 686, 687, 18, 18, 18, 688, 26, 26, 26, 689, 26, 26,
+ 690, 690, 690, 690, 690, 691, 690, 692, 690, 693, 26, 26, 26, 26, 694, 694,
+ 694, 695, 26, 26, 696, 696, 696, 696, 696, 696, 696, 697, 26, 26, 698, 698,
+ 698, 698, 698, 699, 26, 26, 700, 700, 700, 700, 700, 701, 172, 702, 170, 172,
+ 703, 703, 703, 703, 704, 703, 705, 26, 706, 706, 706, 706, 706, 707, 706, 708,
+ 26, 26, 362, 0, 0, 0, 376, 26, 709, 31, 31, 31, 710, 711, 712, 713,
+ 714, 715, 710, 716, 710, 712, 712, 717, 31, 718, 31, 719, 720, 718, 31, 719,
+ 26, 26, 721, 26, 0, 359, 0, 0, 0, 257, 362, 0, 362, 0, 362, 0,
+ 0, 276, 26, 26, 722, 0, 0, 0, 723, 26, 0, 0, 0, 0, 0, 359,
+ 0, 259, 265, 26, 276, 26, 26, 26, 0, 0, 0, 724, 0, 376, 0, 376,
+ 0, 0, 257, 725, 0, 359, 259, 26, 0, 26, 0, 265, 0, 26, 0, 0,
+ 0, 276, 0, 359, 265, 26, 26, 26, 0, 276, 0, 376, 0, 726, 0, 0,
+ 257, 722, 0, 727, 0, 265, 0, 259, 277, 277, 277, 280, 345, 26, 277, 277,
+ 728, 26, 277, 277, 277, 729, 277, 277, 277, 277, 26, 26, 730, 26, 26, 26,
+ 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 939, 940, 941, 942, 946, 948, 0, 962, 969, 970, 971, 976,
+ 1001,1002,1003,1008, 0,1033,1040,1041,1042,1043,1047, 0, 0,1080,1081,1082,
+ 1086,1110, 0, 0,1124,1125,1126,1127,1131,1133, 0,1147,1154,1155,1156,1161,
+ 1187,1188,1189,1193, 0,1219,1226,1227,1228,1229,1233, 0, 0,1267,1268,1269,
+ 1273,1298, 0,1303, 943,1128, 944,1129, 954,1139, 958,1143, 959,1144, 960,1145,
+ 961,1146, 964,1149, 0, 0, 973,1158, 974,1159, 975,1160, 983,1168, 978,1163,
+ 988,1173, 990,1175, 991,1176, 993,1178, 994,1179, 0, 0,1004,1190,1005,1191,
+ 1006,1192,1014,1199,1007, 0, 0, 0,1016,1201,1020,1206, 0,1022,1208,1025,
+ 1211,1023,1209, 0, 0, 0, 0,1032,1218,1037,1223,1035,1221, 0, 0, 0,
+ 1044,1230,1045,1231,1049,1235, 0, 0,1058,1244,1064,1250,1060,1246,1066,1252,
+ 1067,1253,1072,1258,1069,1255,1077,1264,1074,1261, 0, 0,1083,1270,1084,1271,
+ 1085,1272,1088,1275,1089,1276,1096,1283,1103,1290,1111,1299,1115,1118,1307,1120,
+ 1309,1121,1310, 0,1053,1239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,1093,1280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 949,1134,1010,1195,1050,1236,1090,1277,1341,1368,1340,1367,1342,1369,1339,
+ 1366, 0,1320,1347,1418,1419,1323,1350, 0, 0, 992,1177,1018,1204,1055,1241,
+ 1416,1417,1415,1424,1202, 0, 0, 0, 987,1172, 0, 0,1031,1217,1321,1348,
+ 1322,1349,1338,1365, 950,1135, 951,1136, 979,1164, 980,1165,1011,1196,1012,1197,
+ 1051,1237,1052,1238,1061,1247,1062,1248,1091,1278,1092,1279,1071,1257,1076,1263,
+ 0, 0, 997,1182, 0, 0, 0, 0, 0, 0, 945,1130, 982,1167,1337,1364,
+ 1335,1362,1046,1232,1422,1423,1113,1301, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 8, 9, 0, 10,1425, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0,1314,1427, 5,1434,1438,1443, 0,
+ 1450, 0,1455,1461,1514, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1446,1458,
+ 1468,1476,1480,1486,1517, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1489,1503,
+ 1494,1500,1508, 0, 0, 0, 0,1520,1521, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,1526,1528, 0,1525, 0, 0, 0,1522, 0, 0, 0, 0,
+ 1536,1532,1539, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1534, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1556, 0, 0,
+ 0, 0, 0, 0,1548,1550, 0,1547, 0, 0, 0,1567, 0, 0, 0, 0,
+ 1558,1554,1561, 0, 0, 0, 0, 0, 0, 0,1568,1569, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,1529,1551, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,1523,1545,1524,1546, 0, 0,1527,1549, 0, 0,1570,1571,
+ 1530,1552,1531,1553, 0, 0,1533,1555,1535,1557,1537,1559, 0, 0,1572,1573,
+ 1544,1566,1538,1560,1540,1562,1541,1563,1542,1564, 0, 0,1543,1565, 0, 0,
+ 0, 0, 0, 0, 0, 0,1606,1607,1609,1608,1610, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,1613, 0,1611, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,1612, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1620, 0, 0,
+ 0, 0, 0, 0, 0,1623, 0, 0,1624, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1614,1615,1616,1617,
+ 1618,1619,1621,1622, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1628,
+ 1629, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1625,1626, 0,1627, 0, 0, 0,1634, 0, 0,1635, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1630,1631,1632,
+ 0, 0,1633, 0, 0, 0, 0, 0, 0, 0, 0, 0,1639, 0, 0,1638,
+ 1640, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1636,1637, 0, 0, 0, 0, 0, 0,1641, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1642,1644,
+ 1643, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1645, 0, 0, 0,
+ 0, 0, 0, 0,1646, 0, 0, 0, 0, 0, 0,1648,1649, 0,1647,1650,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1651,1653,
+ 1652, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1654, 0,
+ 1655,1657,1656, 0, 0, 0, 0,1659, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,1660, 0, 0, 0, 0,1661, 0, 0, 0, 0,1662, 0, 0, 0, 0,
+ 1663, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1658, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,1664, 0,1665,1673, 0,1674, 0, 0, 0,
+ 0, 0, 0, 0, 0,1666, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,1668, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,1669, 0, 0, 0, 0,1670, 0, 0, 0, 0,1671, 0, 0, 0, 0,
+ 1672, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1667, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1675, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1676, 0,1677, 0,1678, 0,
+ 1679, 0,1680, 0, 0, 0,1681, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1682,
+ 0,1683, 0, 0,1684,1685, 0,1686, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 953,1138, 955,1140, 956,1141, 957,1142,1324,1351, 963,1148,
+ 965,1150, 968,1153, 966,1151, 967,1152,1378,1380,1379,1381, 984,1169, 985,1170,
+ 1420,1421, 986,1171, 989,1174, 995,1180, 998,1183, 996,1181, 999,1184,1000,1185,
+ 1015,1200,1329,1356,1017,1203,1019,1205,1021,1207,1024,1210,1687,1688,1027,1213,
+ 1026,1212,1028,1214,1029,1215,1030,1216,1034,1220,1036,1222,1039,1225,1038,1224,
+ 1334,1361,1336,1363,1382,1384,1383,1385,1056,1242,1057,1243,1059,1245,1063,1249,
+ 1689,1690,1065,1251,1068,1254,1070,1256,1386,1387,1388,1389,1691,1692,1073,1259,
+ 1075,1262,1079,1266,1078,1265,1095,1282,1098,1285,1097,1284,1390,1391,1392,1393,
+ 1099,1286,1100,1287,1101,1288,1102,1289,1105,1292,1104,1291,1106,1294,1107,1295,
+ 1108,1296,1114,1302,1119,1308,1122,1311,1123,1312,1186,1260,1293,1305, 0,1394,
+ 0, 0, 0, 0, 952,1137, 947,1132,1317,1344,1316,1343,1319,1346,1318,1345,
+ 1693,1695,1371,1375,1370,1374,1373,1377,1372,1376,1694,1696, 981,1166, 977,1162,
+ 972,1157,1326,1353,1325,1352,1328,1355,1327,1354,1697,1698,1009,1194,1013,1198,
+ 1054,1240,1048,1234,1331,1358,1330,1357,1333,1360,1332,1359,1699,1700,1396,1401,
+ 1395,1400,1398,1403,1397,1402,1399,1404,1094,1281,1087,1274,1406,1411,1405,1410,
+ 1408,1413,1407,1412,1409,1414,1109,1297,1117,1306,1116,1304,1112,1300, 0, 0,
+ 0, 0, 0, 0,1471,1472,1701,1705,1702,1706,1703,1707,1430,1431,1715,1719,
+ 1716,1720,1717,1721,1477,1478,1729,1731,1730,1732, 0, 0,1435,1436,1733,1735,
+ 1734,1736, 0, 0,1481,1482,1737,1741,1738,1742,1739,1743,1439,1440,1751,1755,
+ 1752,1756,1753,1757,1490,1491,1765,1768,1766,1769,1767,1770,1447,1448,1771,1774,
+ 1772,1775,1773,1776,1495,1496,1777,1779,1778,1780, 0, 0,1451,1452,1781,1783,
+ 1782,1784, 0, 0,1504,1505,1785,1788,1786,1789,1787,1790, 0,1459, 0,1791,
+ 0,1792, 0,1793,1509,1510,1794,1798,1795,1799,1796,1800,1462,1463,1808,1812,
+ 1809,1813,1810,1814,1467, 21,1475, 22,1479, 23,1485, 24,1493, 27,1499, 28,
+ 1507, 29, 0, 0,1704,1708,1709,1710,1711,1712,1713,1714,1718,1722,1723,1724,
+ 1725,1726,1727,1728,1740,1744,1745,1746,1747,1748,1749,1750,1754,1758,1759,1760,
+ 1761,1762,1763,1764,1797,1801,1802,1803,1804,1805,1806,1807,1811,1815,1816,1817,
+ 1818,1819,1820,1821,1470,1469,1822,1474,1465, 0,1473,1825,1429,1428,1426, 12,
+ 1432, 0, 26, 0, 0,1315,1823,1484,1466, 0,1483,1829,1433, 13,1437, 14,
+ 1441,1826,1827,1828,1488,1487,1513, 19, 0, 0,1492,1515,1445,1444,1442, 15,
+ 0,1831,1832,1833,1502,1501,1516, 25,1497,1498,1506,1518,1457,1456,1454, 17,
+ 1453,1313, 11, 3, 0, 0,1824,1512,1519, 0,1511,1830,1449, 16,1460, 18,
+ 1464, 4, 0, 0, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 2, 6,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1834,1835,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 949,1134,1010,1195,1050,1236,1090,
- 1277,1341,1368,1340,1367,1342,1369,1339,1366, 0,1320,1347,1418,1419,1323,1350,
- 0, 0, 992,1177,1018,1204,1055,1241,1416,1417,1415,1424,1202, 0, 0, 0,
- 987,1172, 0, 0,1031,1217,1321,1348,1322,1349,1338,1365, 950,1135, 951,1136,
- 979,1164, 980,1165,1011,1196,1012,1197,1051,1237,1052,1238,1061,1247,1062,1248,
- 1091,1278,1092,1279,1071,1257,1076,1263, 0, 0, 997,1182, 0, 0, 0, 0,
- 0, 0, 945,1130, 982,1167,1337,1364,1335,1362,1046,1232,1422,1423,1113,1301,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 10,
- 1425, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
- 0,1314,1427, 5,1434,1438,1443, 0,1450, 0,1455,1461,1514, 0, 0, 0,
+ 0, 0,1836, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,1837,1839,1838, 0, 0, 0, 0,1840, 0, 0, 0, 0,1841, 0, 0,
+ 1842, 0, 0, 0, 0, 0, 0, 0,1843, 0,1844, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,1845, 0, 0,1846, 0, 0,1847, 0,1848, 0, 0,
+ 0, 0, 0, 0, 937, 0,1850, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,1849, 936, 938,1851,1852, 0, 0,1853,1854, 0, 0,1855,1856, 0, 0,
+ 0, 0, 0, 0,1857,1858, 0, 0,1861,1862, 0, 0,1863,1864, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1446,1458,1468,1476,1480,1486,1517, 0, 0, 0,
+ 1867,1868,1869,1870,1859,1860,1865,1866, 0, 0, 0, 0, 0, 0,1871,1872,
+ 1873,1874, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 33, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1489,1503,1494,1500,1508, 0, 0, 0, 0,1520,
- 1521, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1526,1528, 0,1525,
- 0, 0, 0,1522, 0, 0, 0, 0,1536,1532,1539, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,1534, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,1556, 0, 0, 0, 0, 0, 0,1548,1550, 0,1547,
- 0, 0, 0,1567, 0, 0, 0, 0,1558,1554,1561, 0, 0, 0, 0, 0,
- 0, 0,1568,1569, 0, 0, 0, 0, 0, 0, 0, 0, 0,1529,1551, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1523,1545,1524,1546,
- 0, 0,1527,1549, 0, 0,1570,1571,1530,1552,1531,1553, 0, 0,1533,1555,
- 1535,1557,1537,1559, 0, 0,1572,1573,1544,1566,1538,1560,1540,1562,1541,1563,
- 1542,1564, 0, 0,1543,1565, 0, 0, 0, 0, 0, 0, 0, 0,1606,1607,
- 1609,1608,1610, 0, 0, 0, 0, 0, 0, 0, 0, 0,1613, 0,1611, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1612,
+ 1875, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1877, 0,1878, 0,1879, 0,1880, 0,1881, 0,1882, 0,1883, 0,1884, 0,
+ 1885, 0,1886, 0,1887, 0,1888, 0, 0,1889, 0,1890, 0,1891, 0, 0,
+ 0, 0, 0, 0,1892,1893, 0,1894,1895, 0,1896,1897, 0,1898,1899, 0,
+ 1900,1901, 0, 0, 0, 0, 0, 0,1876, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0,1902, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1904, 0,1905, 0,1906, 0,1907, 0,1908, 0,1909, 0,1910, 0,1911, 0,
+ 1912, 0,1913, 0,1914, 0,1915, 0, 0,1916, 0,1917, 0,1918, 0, 0,
+ 0, 0, 0, 0,1919,1920, 0,1921,1922, 0,1923,1924, 0,1925,1926, 0,
+ 1927,1928, 0, 0, 0, 0, 0, 0,1903, 0, 0,1929,1930,1931,1932, 0,
+ 0, 0,1933, 0, 710, 385, 724, 715, 455, 103, 186, 825, 825, 242, 751, 205,
+ 241, 336, 524, 601, 663, 676, 688, 738, 411, 434, 474, 500, 649, 746, 799, 108,
+ 180, 416, 482, 662, 810, 275, 462, 658, 692, 344, 618, 679, 293, 388, 440, 492,
+ 740, 116, 146, 168, 368, 414, 481, 527, 606, 660, 665, 722, 781, 803, 809, 538,
+ 553, 588, 642, 758, 811, 701, 233, 299, 573, 612, 487, 540, 714, 779, 232, 267,
+ 412, 445, 457, 585, 594, 766, 167, 613, 149, 148, 560, 589, 648, 768, 708, 345,
+ 411, 704, 105, 259, 313, 496, 518, 174, 542, 120, 307, 101, 430, 372, 584, 183,
+ 228, 529, 650, 697, 424, 732, 428, 349, 632, 355, 517, 110, 135, 147, 403, 580,
+ 624, 700, 750, 170, 193, 245, 297, 374, 463, 543, 763, 801, 812, 815, 162, 384,
+ 420, 730, 287, 330, 337, 366, 459, 476, 509, 558, 591, 610, 726, 652, 734, 759,
+ 154, 163, 198, 473, 683, 697, 292, 311, 353, 423, 572, 494, 113, 217, 259, 280,
+ 314, 499, 506, 603, 608, 752, 778, 782, 788, 117, 557, 748, 774, 320, 109, 126,
+ 260, 265, 373, 411, 479, 523, 655, 737, 823, 380, 765, 161, 395, 398, 438, 451,
+ 502, 516, 537, 583, 791, 136, 340, 769, 122, 273, 446, 727, 305, 322, 400, 496,
+ 771, 155, 190, 269, 377, 391, 406, 432, 501, 519, 599, 684, 687, 749, 776, 175,
+ 452, 191, 480, 510, 659, 772, 805, 813, 397, 444, 619, 566, 568, 575, 491, 471,
+ 707, 111, 636, 156, 153, 288, 346, 578, 256, 435, 383, 729, 680, 767, 694, 295,
+ 128, 210, 0, 0, 227, 0, 379, 0, 0, 150, 493, 525, 544, 551, 552, 556,
+ 783, 576, 604, 0, 661, 0, 703, 0, 0, 735, 743, 0, 0, 0, 793, 794,
+ 795, 808, 741, 773, 118, 127, 130, 166, 169, 177, 207, 213, 215, 226, 229, 268,
+ 270, 317, 327, 329, 335, 369, 375, 381, 404, 441, 448, 458, 477, 484, 503, 539,
+ 545, 547, 546, 548, 549, 550, 554, 555, 561, 564, 569, 591, 593, 595, 598, 607,
+ 620, 625, 625, 651, 690, 695, 705, 706, 716, 717, 733, 735, 777, 786, 790, 315,
+ 869, 623, 0, 0, 102, 145, 134, 115, 129, 138, 165, 171, 207, 202, 206, 212,
+ 227, 231, 240, 243, 250, 254, 294, 296, 303, 308, 319, 325, 321, 329, 326, 335,
+ 341, 357, 360, 362, 370, 379, 388, 389, 393, 421, 424, 438, 456, 454, 458, 465,
+ 477, 535, 485, 490, 493, 507, 512, 514, 521, 522, 525, 526, 528, 533, 532, 541,
+ 565, 569, 574, 586, 591, 597, 607, 637, 647, 674, 691, 693, 695, 698, 703, 699,
+ 705, 704, 702, 706, 709, 717, 728, 736, 747, 754, 770, 777, 783, 784, 786, 787,
+ 790, 802, 825, 848, 847, 857, 55, 65, 66, 883, 892, 916, 822, 824, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,1620, 0, 0, 0, 0, 0, 0, 0,1623, 0, 0,
- 1624, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,1614,1615,1616,1617,1618,1619,1621,1622, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,1628,1629, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1625,1626, 0,1627, 0, 0, 0,1634,
- 0, 0,1635, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,1630,1631,1632, 0, 0,1633, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,1639, 0, 0,1638,1640, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1636,1637, 0, 0, 0, 0, 0, 0,
- 1641, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1642,1644,1643, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,1645, 0, 0, 0, 0, 0, 0, 0,1646, 0, 0, 0,
- 0, 0, 0,1648,1649, 0,1647,1650, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1651,1653,1652, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1654, 0,1655,1657,1656, 0, 0, 0, 0,1659,
- 0, 0, 0, 0, 0, 0, 0, 0, 0,1660, 0, 0, 0, 0,1661, 0,
- 0, 0, 0,1662, 0, 0, 0, 0,1663, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,1658, 0, 0, 0, 0, 0, 0, 0, 0, 0,1664,
- 0,1665,1673, 0,1674, 0, 0, 0, 0, 0, 0, 0, 0,1666, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1668,
- 0, 0, 0, 0, 0, 0, 0, 0, 0,1669, 0, 0, 0, 0,1670, 0,
- 0, 0, 0,1671, 0, 0, 0, 0,1672, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,1667, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0,1675, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0,1676, 0,1677, 0,1678, 0,1679, 0,1680, 0, 0, 0,1681, 0,
+ 0,1586, 0,1605, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1602,1603,
+ 1934,1935,1574,1575,1576,1577,1579,1580,1581,1583,1584, 0,1585,1587,1588,1589,
+ 1591, 0,1592, 0,1593,1594, 0,1595,1596, 0,1598,1599,1600,1601,1604,1582,
+ 1578,1590,1597, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1936, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,1937, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1938, 0,
+ 1939, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1940,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,1682, 0,1683, 0, 0,1684,1685, 0,1686,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 953,1138, 955,1140,
- 956,1141, 957,1142,1324,1351, 963,1148, 965,1150, 968,1153, 966,1151, 967,1152,
- 1378,1380,1379,1381, 984,1169, 985,1170,1420,1421, 986,1171, 989,1174, 995,1180,
- 998,1183, 996,1181, 999,1184,1000,1185,1015,1200,1329,1356,1017,1203,1019,1205,
- 1021,1207,1024,1210,1687,1688,1027,1213,1026,1212,1028,1214,1029,1215,1030,1216,
- 1034,1220,1036,1222,1039,1225,1038,1224,1334,1361,1336,1363,1382,1384,1383,1385,
- 1056,1242,1057,1243,1059,1245,1063,1249,1689,1690,1065,1251,1068,1254,1070,1256,
- 1386,1387,1388,1389,1691,1692,1073,1259,1075,1262,1079,1266,1078,1265,1095,1282,
- 1098,1285,1097,1284,1390,1391,1392,1393,1099,1286,1100,1287,1101,1288,1102,1289,
- 1105,1292,1104,1291,1106,1294,1107,1295,1108,1296,1114,1302,1119,1308,1122,1311,
- 1123,1312,1186,1260,1293,1305, 0,1394, 0, 0, 0, 0, 952,1137, 947,1132,
- 1317,1344,1316,1343,1319,1346,1318,1345,1693,1695,1371,1375,1370,1374,1373,1377,
- 1372,1376,1694,1696, 981,1166, 977,1162, 972,1157,1326,1353,1325,1352,1328,1355,
- 1327,1354,1697,1698,1009,1194,1013,1198,1054,1240,1048,1234,1331,1358,1330,1357,
- 1333,1360,1332,1359,1699,1700,1396,1401,1395,1400,1398,1403,1397,1402,1399,1404,
- 1094,1281,1087,1274,1406,1411,1405,1410,1408,1413,1407,1412,1409,1414,1109,1297,
- 1117,1306,1116,1304,1112,1300, 0, 0, 0, 0, 0, 0,1471,1472,1701,1705,
- 1702,1706,1703,1707,1430,1431,1715,1719,1716,1720,1717,1721,1477,1478,1729,1731,
- 1730,1732, 0, 0,1435,1436,1733,1735,1734,1736, 0, 0,1481,1482,1737,1741,
- 1738,1742,1739,1743,1439,1440,1751,1755,1752,1756,1753,1757,1490,1491,1765,1768,
- 1766,1769,1767,1770,1447,1448,1771,1774,1772,1775,1773,1776,1495,1496,1777,1779,
- 1778,1780, 0, 0,1451,1452,1781,1783,1782,1784, 0, 0,1504,1505,1785,1788,
- 1786,1789,1787,1790, 0,1459, 0,1791, 0,1792, 0,1793,1509,1510,1794,1798,
- 1795,1799,1796,1800,1462,1463,1808,1812,1809,1813,1810,1814,1467, 21,1475, 22,
- 1479, 23,1485, 24,1493, 27,1499, 28,1507, 29, 0, 0,1704,1708,1709,1710,
- 1711,1712,1713,1714,1718,1722,1723,1724,1725,1726,1727,1728,1740,1744,1745,1746,
- 1747,1748,1749,1750,1754,1758,1759,1760,1761,1762,1763,1764,1797,1801,1802,1803,
- 1804,1805,1806,1807,1811,1815,1816,1817,1818,1819,1820,1821,1470,1469,1822,1474,
- 1465, 0,1473,1825,1429,1428,1426, 12,1432, 0, 26, 0, 0,1315,1823,1484,
- 1466, 0,1483,1829,1433, 13,1437, 14,1441,1826,1827,1828,1488,1487,1513, 19,
- 0, 0,1492,1515,1445,1444,1442, 15, 0,1831,1832,1833,1502,1501,1516, 25,
- 1497,1498,1506,1518,1457,1456,1454, 17,1453,1313, 11, 3, 0, 0,1824,1512,
- 1519, 0,1511,1830,1449, 16,1460, 18,1464, 4, 0, 0, 30, 31, 0, 0,
+ 0, 0,1941,1942, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1943,
+ 1944, 0, 0, 0, 0, 0, 0,1945, 0,1946, 0, 0, 0, 0, 0, 0,
+ 0, 0,1947, 0, 0,1948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,1950, 0,1949,1951, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1953,
+ 1952, 0,1954, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1955,1956,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1957, 0, 0, 0,
+ 0, 0, 0, 0, 0,1958,1961,1959,1965,1960,1962,1964,1963, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1967,1966,1968, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 20, 0, 0, 0, 2, 6, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1834,1835, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1836, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0,1837,1839,1838, 0, 0, 0, 0,
- 1840, 0, 0, 0, 0,1841, 0, 0,1842, 0, 0, 0, 0, 0, 0, 0,
- 1843, 0,1844, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1845, 0, 0,
- 1846, 0, 0,1847, 0,1848, 0, 0, 0, 0, 0, 0, 937, 0,1850, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0,1849, 936, 938,1851,1852, 0, 0,
- 1853,1854, 0, 0,1855,1856, 0, 0, 0, 0, 0, 0,1857,1858, 0, 0,
- 1861,1862, 0, 0,1863,1864, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1867,1868,1869,1870,1859,1860,1865,1866,
- 0, 0, 0, 0, 0, 0,1871,1872,1873,1874, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 32, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1875, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1877, 0,1878, 0,1879, 0,1880, 0,
- 1881, 0,1882, 0,1883, 0,1884, 0,1885, 0,1886, 0,1887, 0,1888, 0,
- 0,1889, 0,1890, 0,1891, 0, 0, 0, 0, 0, 0,1892,1893, 0,1894,
- 1895, 0,1896,1897, 0,1898,1899, 0,1900,1901, 0, 0, 0, 0, 0, 0,
- 1876, 0, 0, 0, 0, 0, 0, 0, 0, 0,1902, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,1904, 0,1905, 0,1906, 0,1907, 0,
- 1908, 0,1909, 0,1910, 0,1911, 0,1912, 0,1913, 0,1914, 0,1915, 0,
- 0,1916, 0,1917, 0,1918, 0, 0, 0, 0, 0, 0,1919,1920, 0,1921,
- 1922, 0,1923,1924, 0,1925,1926, 0,1927,1928, 0, 0, 0, 0, 0, 0,
- 1903, 0, 0,1929,1930,1931,1932, 0, 0, 0,1933, 0, 710, 385, 724, 715,
- 455, 103, 186, 825, 825, 242, 751, 205, 241, 336, 524, 601, 663, 676, 688, 738,
- 411, 434, 474, 500, 649, 746, 799, 108, 180, 416, 482, 662, 810, 275, 462, 658,
- 692, 344, 618, 679, 293, 388, 440, 492, 740, 116, 146, 168, 368, 414, 481, 527,
- 606, 660, 665, 722, 781, 803, 809, 538, 553, 588, 642, 758, 811, 701, 233, 299,
- 573, 612, 487, 540, 714, 779, 232, 267, 412, 445, 457, 585, 594, 766, 167, 613,
- 149, 148, 560, 589, 648, 768, 708, 345, 411, 704, 105, 259, 313, 496, 518, 174,
- 542, 120, 307, 101, 430, 372, 584, 183, 228, 529, 650, 697, 424, 732, 428, 349,
- 632, 355, 517, 110, 135, 147, 403, 580, 624, 700, 750, 170, 193, 245, 297, 374,
- 463, 543, 763, 801, 812, 815, 162, 384, 420, 730, 287, 330, 337, 366, 459, 476,
- 509, 558, 591, 610, 726, 652, 734, 759, 154, 163, 198, 473, 683, 697, 292, 311,
- 353, 423, 572, 494, 113, 217, 259, 280, 314, 499, 506, 603, 608, 752, 778, 782,
- 788, 117, 557, 748, 774, 320, 109, 126, 260, 265, 373, 411, 479, 523, 655, 737,
- 823, 380, 765, 161, 395, 398, 438, 451, 502, 516, 537, 583, 791, 136, 340, 769,
- 122, 273, 446, 727, 305, 322, 400, 496, 771, 155, 190, 269, 377, 391, 406, 432,
- 501, 519, 599, 684, 687, 749, 776, 175, 452, 191, 480, 510, 659, 772, 805, 813,
- 397, 444, 619, 566, 568, 575, 491, 471, 707, 111, 636, 156, 153, 288, 346, 578,
- 256, 435, 383, 729, 680, 767, 694, 295, 128, 210, 0, 0, 227, 0, 379, 0,
- 0, 150, 493, 525, 544, 551, 552, 556, 783, 576, 604, 0, 661, 0, 703, 0,
- 0, 735, 743, 0, 0, 0, 793, 794, 795, 808, 741, 773, 118, 127, 130, 166,
- 169, 177, 207, 213, 215, 226, 229, 268, 270, 317, 327, 329, 335, 369, 375, 381,
- 404, 441, 448, 458, 477, 484, 503, 539, 545, 547, 546, 548, 549, 550, 554, 555,
- 561, 564, 569, 591, 593, 595, 598, 607, 620, 625, 625, 651, 690, 695, 705, 706,
- 716, 717, 733, 735, 777, 786, 790, 315, 869, 623, 0, 0, 102, 145, 134, 115,
- 129, 138, 165, 171, 207, 202, 206, 212, 227, 231, 240, 243, 250, 254, 294, 296,
- 303, 308, 319, 325, 321, 329, 326, 335, 341, 357, 360, 362, 370, 379, 388, 389,
- 393, 421, 424, 438, 456, 454, 458, 465, 477, 535, 485, 490, 493, 507, 512, 514,
- 521, 522, 525, 526, 528, 533, 532, 541, 565, 569, 574, 586, 591, 597, 607, 637,
- 647, 674, 691, 693, 695, 698, 703, 699, 705, 704, 702, 706, 709, 717, 728, 736,
- 747, 754, 770, 777, 783, 784, 786, 787, 790, 802, 825, 848, 847, 857, 55, 65,
- 66, 883, 892, 916, 822, 824, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0,1586, 0,1605, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1602,1603,1934,1935,1574,1575,1576,1577,1579,1580,
- 1581,1583,1584, 0,1585,1587,1588,1589,1591, 0,1592, 0,1593,1594, 0,1595,
- 1596, 0,1598,1599,1600,1601,1604,1582,1578,1590,1597, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1936, 0,1937, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,1938, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1939,1940, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,1941,1942, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,1944,1943, 0,1945, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1946,1947, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0,1948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1949,1950,1951,1952,1953,1954,
- 1955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,1956,1957,1958,1960,1959,1961, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 104, 107, 826,
- 114, 118, 119, 121, 123, 124, 127, 125, 34, 830, 130, 131, 132, 137, 827, 35,
- 133, 139, 829, 142, 143, 112, 144, 145, 924, 151, 152, 37, 157, 158, 159, 160,
- 38, 165, 166, 169, 171, 172, 173, 174, 176, 177, 178, 179, 181, 182, 182, 182,
- 833, 468, 184, 185, 834, 187, 188, 189, 196, 192, 194, 195, 197, 199, 200, 201,
- 203, 204, 204, 206, 208, 209, 211, 218, 213, 219, 214, 216, 153, 234, 221, 222,
- 223, 220, 225, 224, 230, 835, 235, 236, 237, 238, 239, 244, 836, 837, 247, 248,
- 249, 246, 251, 39, 40, 253, 255, 255, 838, 257, 258, 259, 261, 839, 262, 263,
- 301, 264, 41, 266, 270, 272, 271, 841, 274, 842, 277, 276, 278, 281, 282, 42,
- 283, 284, 285, 286, 43, 843, 44, 289, 290, 291, 293, 934, 298, 845, 845, 621,
- 300, 300, 45, 852, 894, 302, 304, 46, 306, 309, 310, 312, 316, 48, 47, 317,
- 846, 318, 323, 324, 325, 324, 328, 329, 333, 331, 332, 334, 335, 336, 338, 339,
- 342, 343, 347, 351, 849, 350, 348, 352, 354, 359, 850, 361, 358, 356, 49, 363,
- 365, 367, 364, 50, 369, 371, 851, 376, 386, 378, 53, 381, 52, 51, 140, 141,
- 387, 382, 614, 78, 388, 389, 390, 394, 392, 856, 54, 399, 396, 402, 404, 858,
- 405, 401, 407, 55, 408, 409, 410, 413, 859, 415, 56, 417, 860, 418, 57, 419,
- 422, 424, 425, 861, 840, 862, 426, 863, 429, 431, 427, 433, 437, 441, 438, 439,
- 442, 443, 864, 436, 449, 450, 58, 454, 453, 865, 447, 460, 866, 867, 461, 466,
- 465, 464, 59, 467, 470, 469, 472, 828, 475, 868, 478, 870, 483, 485, 486, 871,
- 488, 489, 872, 873, 495, 497, 60, 498, 61, 61, 504, 505, 507, 508, 511, 62,
- 513, 874, 515, 875, 518, 844, 520, 876, 877, 878, 63, 64, 528, 880, 879, 881,
- 882, 530, 531, 531, 533, 66, 534, 67, 68, 884, 536, 538, 541, 69, 885, 549,
- 886, 887, 556, 559, 70, 561, 562, 563, 888, 889, 889, 567, 71, 890, 570, 571,
- 72, 891, 577, 73, 581, 579, 582, 893, 587, 74, 590, 592, 596, 75, 895, 896,
- 76, 897, 600, 898, 602, 605, 607, 899, 900, 609, 901, 611, 853, 77, 615, 616,
- 79, 617, 252, 902, 903, 854, 855, 621, 622, 731, 80, 627, 626, 628, 164, 629,
- 630, 631, 633, 904, 632, 634, 639, 640, 635, 641, 646, 651, 638, 643, 644, 645,
- 905, 907, 906, 81, 653, 654, 656, 911, 657, 908, 82, 83, 909, 910, 84, 664,
- 665, 666, 667, 669, 668, 671, 670, 674, 672, 673, 675, 85, 677, 678, 86, 681,
- 682, 912, 685, 686, 87, 689, 36, 913, 914, 88, 89, 696, 702, 709, 711, 915,
- 712, 713, 718, 719, 917, 831, 721, 720, 723, 832, 725, 728, 918, 919, 739, 742,
- 744, 920, 745, 753, 756, 757, 755, 760, 761, 921, 762, 90, 764, 922, 91, 775,
- 279, 780, 923, 925, 92, 93, 785, 926, 94, 927, 787, 787, 789, 928, 792, 95,
- 796, 797, 798, 800, 96, 929, 802, 804, 806, 97, 98, 807, 930, 99, 931, 932,
- 933, 814, 100, 816, 817, 818, 819, 820, 821, 935, 0, 0,
+ 0, 0,1969,1970,1971,1972,1973,1974,1975, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1976,
+ 1977,1978,1980,1979,1981, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 106, 104, 107, 826, 114, 118, 119, 121, 123, 124, 127, 125,
+ 34, 830, 130, 131, 132, 137, 827, 35, 133, 139, 829, 142, 143, 112, 144, 145,
+ 924, 151, 152, 37, 157, 158, 159, 160, 38, 165, 166, 169, 171, 172, 173, 174,
+ 176, 177, 178, 179, 181, 182, 182, 182, 833, 468, 184, 185, 834, 187, 188, 189,
+ 196, 192, 194, 195, 197, 199, 200, 201, 203, 204, 204, 206, 208, 209, 211, 218,
+ 213, 219, 214, 216, 153, 234, 221, 222, 223, 220, 225, 224, 230, 835, 235, 236,
+ 237, 238, 239, 244, 836, 837, 247, 248, 249, 246, 251, 39, 40, 253, 255, 255,
+ 838, 257, 258, 259, 261, 839, 262, 263, 301, 264, 41, 266, 270, 272, 271, 841,
+ 274, 842, 277, 276, 278, 281, 282, 42, 283, 284, 285, 286, 43, 843, 44, 289,
+ 290, 291, 293, 934, 298, 845, 845, 621, 300, 300, 45, 852, 894, 302, 304, 46,
+ 306, 309, 310, 312, 316, 48, 47, 317, 846, 318, 323, 324, 325, 324, 328, 329,
+ 333, 331, 332, 334, 335, 336, 338, 339, 342, 343, 347, 351, 849, 350, 348, 352,
+ 354, 359, 850, 361, 358, 356, 49, 363, 365, 367, 364, 50, 369, 371, 851, 376,
+ 386, 378, 53, 381, 52, 51, 140, 141, 387, 382, 614, 78, 388, 389, 390, 394,
+ 392, 856, 54, 399, 396, 402, 404, 858, 405, 401, 407, 55, 408, 409, 410, 413,
+ 859, 415, 56, 417, 860, 418, 57, 419, 422, 424, 425, 861, 840, 862, 426, 863,
+ 429, 431, 427, 433, 437, 441, 438, 439, 442, 443, 864, 436, 449, 450, 58, 454,
+ 453, 865, 447, 460, 866, 867, 461, 466, 465, 464, 59, 467, 470, 469, 472, 828,
+ 475, 868, 478, 870, 483, 485, 486, 871, 488, 489, 872, 873, 495, 497, 60, 498,
+ 61, 61, 504, 505, 507, 508, 511, 62, 513, 874, 515, 875, 518, 844, 520, 876,
+ 877, 878, 63, 64, 528, 880, 879, 881, 882, 530, 531, 531, 533, 66, 534, 67,
+ 68, 884, 536, 538, 541, 69, 885, 549, 886, 887, 556, 559, 70, 561, 562, 563,
+ 888, 889, 889, 567, 71, 890, 570, 571, 72, 891, 577, 73, 581, 579, 582, 893,
+ 587, 74, 590, 592, 596, 75, 895, 896, 76, 897, 600, 898, 602, 605, 607, 899,
+ 900, 609, 901, 611, 853, 77, 615, 616, 79, 617, 252, 902, 903, 854, 855, 621,
+ 622, 731, 80, 627, 626, 628, 164, 629, 630, 631, 633, 904, 632, 634, 639, 640,
+ 635, 641, 646, 651, 638, 643, 644, 645, 905, 907, 906, 81, 653, 654, 656, 911,
+ 657, 908, 82, 83, 909, 910, 84, 664, 665, 666, 667, 669, 668, 671, 670, 674,
+ 672, 673, 675, 85, 677, 678, 86, 681, 682, 912, 685, 686, 87, 689, 36, 913,
+ 914, 88, 89, 696, 702, 709, 711, 915, 712, 713, 718, 719, 917, 831, 721, 720,
+ 723, 832, 725, 728, 918, 919, 739, 742, 744, 920, 745, 753, 756, 757, 755, 760,
+ 761, 921, 762, 90, 764, 922, 91, 775, 279, 780, 923, 925, 92, 93, 785, 926,
+ 94, 927, 787, 787, 789, 928, 792, 95, 796, 797, 798, 800, 96, 929, 802, 804,
+ 806, 97, 98, 807, 930, 99, 931, 932, 933, 814, 100, 816, 817, 818, 819, 820,
+ 821, 935, 0, 0,
};
static const int16_t
_hb_ucd_i16[92] =
@@ -4403,12 +4615,12 @@ _hb_ucd_i16[92] =
static inline uint_fast8_t
_hb_ucd_gc (unsigned u)
{
- return u<1114110u?_hb_ucd_u8[6808+(((_hb_ucd_u8[1312+(((_hb_ucd_u16[((_hb_ucd_u8[544+(((_hb_ucd_u8[u>>1>>3>>3>>4])<<4)+((u>>1>>3>>3)&15u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:2;
+ return u<1114110u?_hb_ucd_u8[6472+(((_hb_ucd_u8[816+(((_hb_ucd_u16[((_hb_ucd_u8[272+(((_hb_ucd_u8[u>>1>>3>>4>>4])<<4)+((u>>1>>3>>4)&15u))])<<4)+((u>>1>>3)&15u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:2;
}
static inline uint_fast8_t
_hb_ucd_ccc (unsigned u)
{
- return u<125259u?_hb_ucd_u8[8800+(((_hb_ucd_u8[8244+(((_hb_ucd_u8[7784+(((_hb_ucd_u8[7432+(((_hb_ucd_u8[7186+(u>>2>>2>>2>>3)])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:0;
+ return u<125259u?_hb_ucd_u8[8504+(((_hb_ucd_u8[7936+(((_hb_ucd_u8[7460+(((_hb_ucd_u8[7100+(((_hb_ucd_u8[6854+(u>>2>>2>>2>>3)])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:0;
}
static inline unsigned
_hb_ucd_b4 (const uint8_t* a, unsigned i)
@@ -4418,55 +4630,55 @@ _hb_ucd_b4 (const uint8_t* a, unsigned i)
static inline int_fast16_t
_hb_ucd_bmg (unsigned u)
{
- return u<65380u?_hb_ucd_i16[((_hb_ucd_u8[9692+(((_hb_ucd_u8[9460+(((_hb_ucd_u8[9364+(((_hb_ucd_b4(9300+_hb_ucd_u8,u>>1>>2>>3>>3))<<3)+((u>>1>>2>>3)&7u))])<<3)+((u>>1>>2)&7u))])<<2)+((u>>1)&3u))])<<1)+((u)&1u)]:0;
+ return u<65380u?_hb_ucd_i16[((_hb_ucd_u8[9396+(((_hb_ucd_u8[9164+(((_hb_ucd_u8[9068+(((_hb_ucd_b4(9004+_hb_ucd_u8,u>>1>>2>>3>>3))<<3)+((u>>1>>2>>3)&7u))])<<3)+((u>>1>>2)&7u))])<<2)+((u>>1)&3u))])<<1)+((u)&1u)]:0;
}
static inline uint_fast8_t
_hb_ucd_sc (unsigned u)
{
- return u<918000u?_hb_ucd_u8[11126+(((_hb_ucd_u16[4040+(((_hb_ucd_u16[2048+(((_hb_ucd_u8[10390+(((_hb_ucd_u8[9940+(u>>2>>2>>3>>4)])<<4)+((u>>2>>2>>3)&15u))])<<3)+((u>>2>>2)&7u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:2;
+ return u<918000u?_hb_ucd_u8[10398+(((_hb_ucd_u16[3952+(((_hb_ucd_u16[2624+(((_hb_ucd_u8[9870+(((_hb_ucd_u8[9644+(u>>3>>2>>3>>4)])<<4)+((u>>3>>2>>3)&15u))])<<3)+((u>>3>>2)&7u))])<<2)+((u>>3)&3u))])<<3)+((u)&7u))]:2;
}
static inline uint_fast16_t
_hb_ucd_dm (unsigned u)
{
- return u<195102u?_hb_ucd_u16[6748+(((_hb_ucd_u8[13952+(((_hb_ucd_u8[13570+(u>>4>>5)])<<5)+((u>>4)&31u))])<<4)+((u)&15u))]:0;
+ return u<195102u?_hb_ucd_u16[6244+(((_hb_ucd_u8[16628+(((_hb_ucd_u8[16246+(u>>4>>5)])<<5)+((u>>4)&31u))])<<4)+((u)&15u))]:0;
}
#else
static const uint8_t
-_hb_ucd_u8[13386] =
+_hb_ucd_u8[13730] =
{
0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 9, 10, 7, 7, 7, 7, 7, 11, 12, 12, 12, 13,
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 21, 21, 21, 21, 23, 7, 7,
- 7, 24, 21, 21, 21, 25, 26, 27, 21, 28, 29, 30, 31, 32, 33, 34,
+ 14, 15, 16, 17, 18, 19, 20, 7, 21, 22, 22, 22, 23, 24, 7, 7,
+ 7, 25, 22, 22, 22, 26, 27, 28, 22, 29, 30, 31, 32, 33, 34, 35,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 35, 21, 36,
- 7, 7, 7, 7, 37, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 38, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
- 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 21, 22, 36,
+ 7, 7, 7, 7, 37, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 38, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
@@ -4486,30 +4698,30 @@ _hb_ucd_u8[13386] =
100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,
100,100, 34, 34, 34, 34,101,102, 34, 34,103,104,105,106,107,108,
34, 34,109,110,111,112,113,114,115,116,117,118, 34, 34, 34,119,
- 120,121,122,123,124,125,126,127, 34,128,129,111,130,131,132,133,
- 134,135,136,137,138,139,140,111,141,142,111,143,144,145,146,111,
- 147,148,149,150,151,152,153,111,154,155,156,157,111,158,159,160,
- 34, 34, 34, 34, 34, 34, 34, 34,161, 34, 34,111,111,111,111,111,
- 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,162,
- 34, 34, 34, 34, 34, 34, 34, 34,163,111,111,111,111,111,111,111,
+ 120,121,122,123,124,125,126,127, 34,128,129,130,131,132,133,134,
+ 135,136,137,138,139,140,141,142,143,144,111,145,146,147,148,111,
+ 149,150,151,152,153,154,155,156,157,158,159,160,111,161,162,163,
+ 34, 34, 34, 34, 34, 34, 34, 34,164, 34, 34,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,165,
+ 34, 34, 34, 34, 34, 34, 34, 34,166, 34, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,111,111,111,
111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,
- 111,111,111,111,111,111,111,111, 34, 34, 34, 34, 34,111,111,111,
- 34, 34, 34, 34,164,165,166, 34,111,111,111,111,167,168,169,170,
+ 111,111,167,111,111,111,111,111,111,111,111,111,111,111,111,111,
+ 34, 34, 34, 34,168,169,170, 34,111,111,171,111,172,173,174,175,
34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,111,111,111,111,111,
111,111,111,111,111,111,111,111,111,111,111,111,111,111,111,119,
34, 34, 34, 34, 34, 34,111,111,111,111,111,111,111,111,111,111,
- 111,111,111,111,111,111,111,111, 34,171,111,111,111,111,111,111,
- 111,111,111,111,111,111,111,111,111,111,111,111,111,111,172, 67,
- 67, 67,173,174,175,130, 65,111,176,177,178,179,180,181,182,183,
- 67, 67, 67, 67,184,185,111,111,111,111,111,111,111,111,186,111,
- 187,188,189,111,111,190,111,111,111,191,111,111,111,111,111, 34,
- 34,192,193,111,111,111,111,111,130,194,195,111, 34,196,111,111,
- 67, 67,197, 67, 67,111, 67,198, 67, 67, 67, 67, 67, 67, 67, 67,
- 67, 67, 67, 67, 67, 67, 67,199,111,111,111,111,111,111,111,111,
- 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,111,111,111,
+ 111,111,111,111,111,111,111,111, 34,176,111,111,111,111,111,111,
+ 111,111,111,111,111,111,111,111, 67,177, 67, 67, 67, 67,178, 67,
+ 67, 67,179,180,181,131, 65,111,182,183,184,185,186,187,188,189,
+ 67, 67, 67, 67,190,191,111,111,111,111,111,111,111,111,192,111,
+ 193,194,195,111,111,196,111,111,111,197,111,198,111,111,111, 34,
+ 34,199,200,111,111,111,111,111,131,201,202,111, 34,203,111,111,
+ 67, 67,204, 67, 67,111, 67,205, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67,177,111,111,111,111,111,111,111,111,
34, 34, 34, 34, 34,111,111,111,111,111,111,111,111,111,111,111,
34, 34, 34, 34, 34, 34, 34, 34,111,111,111,111,111,111,111,111,
- 200,111,188,188,111,111,111,111,111,111,111,111,111,111,111,111,
+ 206,111,194,194,111,111,111,111,111,111,111,111,111,111,111,111,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 2, 4, 5, 6, 2,
7, 7, 7, 7, 7, 2, 8, 9, 10, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16, 16,
@@ -4544,8 +4756,8 @@ _hb_ucd_u8[13386] =
36, 36, 36, 36, 36, 64, 43, 43, 43, 43, 40, 21, 2, 40, 69, 20,
36, 36, 36, 43, 43, 69, 43, 43, 43, 43, 69, 43, 69, 43, 43, 43,
2, 2, 2, 2, 2, 2, 2, 2, 36, 36, 36, 36, 64, 43, 43, 2,
- 36, 36, 36, 36, 74, 36, 36, 36, 59, 59, 59, 59, 43, 43, 43, 43,
- 36, 36, 36, 36, 75, 43, 43, 43, 43, 76, 43, 43, 43, 43, 43, 43,
+ 36, 36, 36, 36, 74, 36, 36, 36, 59, 59, 59, 75, 43, 43, 43, 43,
+ 36, 36, 36, 36, 76, 43, 43, 43, 43, 75, 43, 43, 43, 43, 43, 43,
43, 77, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 77, 65, 78,
79, 43, 43, 43, 77, 78, 79, 78, 64, 43, 43, 43, 36, 36, 36, 36,
36, 43, 2, 7, 7, 7, 7, 7, 80, 36, 36, 36, 36, 36, 36, 36,
@@ -4590,130 +4802,135 @@ _hb_ucd_u8[13386] =
36, 43, 77, 78, 78, 78, 78, 81, 36, 43, 97, 2, 2, 2, 2, 2,
36, 43, 43, 43, 43, 43, 43, 43, 36, 36, 43, 79, 43, 43, 43, 78,
78, 78, 78, 77, 79, 43, 43, 43, 43, 43, 2, 80, 2, 60, 64, 43,
- 7, 7, 7, 7, 7, 7, 7, 7, 2, 2, 2, 98, 2, 56, 43, 76,
- 36, 75, 36, 36, 36, 36, 36, 36, 36, 36, 64, 65, 36, 36, 36, 36,
+ 7, 7, 7, 7, 7, 7, 7, 7, 2, 2, 2, 98, 2, 56, 43, 75,
+ 36, 76, 36, 36, 36, 36, 36, 36, 36, 36, 64, 65, 36, 36, 36, 36,
36, 36, 36, 36, 64, 36, 36, 36, 43, 77, 78, 79, 77, 78, 78, 78,
78, 77, 78, 78, 79, 43, 43, 43, 61, 61, 2, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 27, 27, 61, 36, 36, 36, 64, 77, 79, 43, 2,
36, 36, 82, 77, 43, 43, 43, 43, 77, 77, 79, 43, 43, 43, 77, 78,
78, 79, 43, 43, 43, 43, 43, 43, 2, 2, 2, 80, 2, 2, 2, 2,
43, 43, 43, 43, 43, 43, 43, 99, 43, 43, 81, 36, 36, 36, 36, 36,
- 36, 36, 77, 43, 43, 77, 77, 78, 78, 77, 81, 36, 36, 36, 36, 36,
+ 36, 36, 77, 43, 43, 77, 77, 78, 78, 77, 81, 36, 36, 36, 36, 2,
89, 61, 61, 61, 61, 47, 43, 43, 43, 43, 61, 61, 61, 61, 21, 2,
43, 81, 36, 36, 36, 36, 36, 36, 82, 43, 43, 78, 43, 79, 43, 36,
36, 36, 36, 77, 43, 78, 79, 79, 43, 78, 78, 78, 78, 78, 2, 2,
36, 36, 78, 78, 78, 78, 43, 43, 43, 43, 78, 43, 43, 57, 2, 2,
7, 7, 7, 7, 7, 7, 86, 36, 36, 36, 36, 36, 40, 40, 40, 2,
- 43, 57, 43, 43, 43, 43, 43, 43, 77, 43, 43, 43, 65, 36, 64, 36,
- 36, 36, 65, 82, 43, 36, 36, 36, 16, 16, 16, 16, 16, 16, 40, 40,
- 40, 40, 40, 40, 40, 44, 16, 16, 16, 16, 16, 16, 44, 16, 16, 16,
- 16, 16, 16, 16, 16,100, 40, 40, 32, 32, 32, 16, 16, 16, 16, 32,
- 16, 16, 16, 16, 11, 11, 11, 11, 16, 16, 16, 16, 34, 11, 11, 11,
- 16, 16, 16, 16,101,101,101,101, 16, 16, 16, 16, 11, 11,102,103,
- 41, 16, 16, 16, 11, 11,102, 41, 16, 16, 16, 16, 11, 11,104, 41,
- 105,105,105,105,105,106, 59, 59, 51, 51, 51, 2,107,108,107,108,
- 2, 2, 2, 2,109, 59, 59,110, 2, 2, 2, 2,111,112, 2,113,
- 114, 2,115,116, 2, 2, 2, 2, 2, 9,114, 2, 2, 2, 2,117,
- 59, 59, 59, 59, 59, 59, 59, 59,118, 40, 27, 27, 27, 8,115,119,
- 27, 27, 27, 27, 27, 8,115, 94, 20, 20, 20, 20, 20, 20, 20, 20,
- 43, 43, 43, 43, 43, 43,120, 48, 99, 48, 99, 43, 43, 43, 43, 43,
- 61,121, 61,122, 61, 34, 11, 16, 11, 32,122, 61, 46, 11, 11, 61,
- 61, 61,121,121,121, 11, 11,123, 11, 11, 35, 36, 39, 61, 16, 11,
- 8, 8, 46, 16, 16, 26, 61,124, 95, 95, 95, 95, 95, 95, 95, 95,
- 95,125,126, 95,127, 61, 61, 61, 8, 8,128, 61, 61, 8, 61, 61,
- 128, 26, 61,128, 61, 61, 61,128, 61, 61, 61, 61, 61, 61, 61, 8,
- 61,128,128, 61, 61, 61, 61, 61, 61, 61, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 61, 61, 61, 61, 4, 4, 61, 61,
- 8, 61, 61, 61,129,130, 61, 61, 61, 61, 61, 61, 61, 61,128, 61,
- 61, 61, 61, 61, 61, 26, 8, 8, 8, 8, 61, 61, 61, 61, 61, 61,
- 61, 61, 61, 61, 61, 61, 8, 8, 8, 61, 61, 61, 61, 61, 61, 61,
- 27, 27, 27, 27, 27, 27, 61, 61, 61, 61, 61, 61, 61, 27, 27, 27,
- 61, 61, 61, 26, 61, 61, 61, 61, 26, 61, 61, 61, 61, 61, 61, 61,
- 61, 61, 61, 61, 8, 8, 8, 8, 61, 61, 61, 61, 61, 61, 61, 26,
- 61, 61, 61, 61, 4, 4, 4, 4, 4, 4, 4, 27, 27, 27, 27, 27,
- 27, 27, 61, 61, 61, 61, 61, 61, 8, 8,115,131, 8, 8, 8, 8,
- 8, 8, 8, 4, 4, 4, 4, 4, 8,115,132,132,132,132,132,132,
- 132,132,132,132,131, 8, 8, 8, 8, 8, 8, 8, 4, 4, 8, 8,
- 8, 8, 8, 8, 8, 8, 4, 8, 8, 8,128, 26, 8, 8,128, 61,
- 32, 11, 32, 34, 34, 34, 34, 11, 32, 32, 34, 16, 16, 16, 40, 11,
- 32, 32,124, 61, 61,122, 34,133, 43, 32, 16, 16, 50, 2, 90, 2,
- 36, 36, 36, 36, 36, 36, 36, 75, 2, 2, 2, 2, 2, 2, 2, 56,
- 2,107,107, 2,111,112,107, 2, 2, 2, 2, 6, 2, 98,107, 2,
- 107, 4, 4, 4, 4, 2, 2, 80, 2, 2, 2, 2, 2, 51, 2, 2,
- 98,134, 2, 2, 2, 2, 2, 2, 61, 2,135,132,132,132,136, 51,
- 51, 51, 51, 51, 51, 51, 51, 51, 1, 2,137,138, 4, 4, 4, 4,
- 4, 61, 4, 4, 4, 4,139, 94,140, 95, 95, 95, 95, 43, 43, 78,
- 141, 40, 40, 61, 95,142, 58, 61, 72, 36, 36, 36, 36, 36, 36, 36,
- 36, 36, 36, 36, 64,143,144, 63, 36, 36, 36, 36, 36, 58, 40, 63,
- 61, 27, 27, 61, 61, 61, 61, 61, 27, 27, 27, 27, 27, 61, 61, 61,
- 61, 61, 61, 61, 27, 27, 27, 27,145, 27, 27, 27, 27, 27, 27, 27,
- 36, 36, 75, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,146, 2,
- 32, 32, 32, 32, 32, 32, 32, 64, 48,147, 43, 43, 43, 43, 43, 80,
- 32, 32, 32, 32, 32, 32, 40, 43, 36, 36, 36, 95, 95, 95, 95, 95,
- 43, 2, 2, 2, 2, 2, 2, 2, 41, 41, 41,144, 40, 40, 40, 40,
- 41, 32, 32, 32, 32, 32, 32, 32, 16, 32, 32, 32, 32, 32, 32, 32,
- 44, 16, 16, 16, 34, 34, 34, 32, 32, 32, 32, 32, 42,148, 34, 35,
- 32, 32, 16, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 11, 11, 32,
- 11, 11, 32, 32, 32, 32, 32, 32, 32, 32, 11, 11, 34, 16, 16, 16,
- 32, 16, 16, 32, 32, 16, 16, 16, 16, 40,149, 35, 40, 35, 36, 36,
- 36, 65, 36, 65, 36, 64, 36, 36, 36, 82, 79, 77, 61, 61, 43, 43,
- 27, 27, 27, 61,150, 61, 61, 61, 36, 36, 2, 2, 2, 2, 2, 2,
- 78, 36, 36, 36, 36, 36, 36, 36, 36, 36, 78, 78, 78, 78, 78, 78,
- 78, 78, 43, 43, 43, 43, 43, 2, 43, 36, 36, 36, 2, 66, 66, 64,
- 36, 36, 36, 43, 43, 43, 43, 2, 36, 36, 36, 64, 43, 43, 43, 43,
- 43, 78, 78, 78, 78, 78, 78, 97, 36, 64, 78, 43, 43, 78, 43, 78,
- 97, 2, 2, 2, 2, 2, 2, 80, 7, 7, 7, 7, 7, 7, 7, 2,
- 36, 36, 64, 63, 36, 36, 36, 36, 36, 36, 36, 36, 64, 43, 43, 77,
- 79, 77, 79, 43, 43, 43, 43, 43, 36, 64, 36, 36, 36, 36, 77, 78,
- 7, 7, 7, 7, 7, 7, 2, 2, 63, 36, 36, 71, 61, 82, 77, 36,
- 65, 43, 65, 64, 65, 36, 36, 43, 36, 36, 36, 36, 36, 36, 75, 2,
- 36, 36, 36, 36, 36, 82, 43, 78, 2, 75,151, 43, 43, 43, 43, 43,
- 16, 16, 16, 16, 16,103, 40, 40, 16, 16, 16, 16,100, 41, 41, 41,
- 36, 82, 79, 78, 77, 97, 79, 43,152,152,152,152,152,152,152,152,
- 153,153,153,153,153,153,153,153, 16, 16, 16, 16, 16, 16, 35, 65,
- 36, 36, 36, 36,154, 36, 36, 36, 36, 41, 41, 41, 41, 41, 41, 41,
- 41, 74, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,132,
- 36, 36, 36, 36, 36, 36, 36, 71, 36, 36, 36, 36, 36, 36,150, 61,
- 2, 2, 2,135,116, 2, 2, 2, 6,155,156,132,132,132,132,132,
- 132,132,116,135,116, 2,113,157, 2, 2, 2, 2,139,132,132,116,
- 2,158, 8, 8, 60, 2, 2, 2, 36, 36, 36, 36, 36, 36, 36,159,
- 2, 2, 3, 2, 4, 5, 6, 2, 16, 16, 16, 16, 16, 17, 18,115,
- 116, 4, 2, 36, 36, 36, 36, 36, 63, 36, 36, 36, 36, 36, 36, 36,
- 36, 36, 36, 36, 36, 36, 36, 40, 20,160, 53, 20, 26, 8,128, 61,
- 61, 61, 61, 61,161, 59, 61, 61, 2, 2, 2, 90, 27, 27, 27, 27,
- 27, 27, 27, 84, 61, 61, 61, 61, 95, 95,127, 27, 84, 61, 61, 61,
- 61, 61, 61, 61, 61, 27, 61, 61, 61, 61, 61, 61, 61, 61, 47, 43,
- 162,162,162,162,162,162,162,162,163, 27, 27, 27, 27, 27, 27, 27,
- 27, 27, 27, 27, 27, 27, 87, 36,138, 36, 36, 36, 36, 95, 95, 95,
- 36, 36, 36, 36, 36, 36, 36, 58,164, 95, 95, 95, 95, 95, 95, 95,
- 11, 11, 11, 32, 16, 16, 16, 16, 36, 36, 36, 58, 27, 27, 27, 27,
- 36, 36, 36, 71,145, 27, 27, 27, 36, 36, 36,165, 27, 27, 27, 27,
- 36, 36, 36, 36, 36,165, 27, 27, 36, 36, 36, 27, 27, 27, 27, 30,
- 36, 36, 36, 36, 36, 36, 27, 36, 64, 43, 43, 43, 43, 43, 43, 43,
- 36, 36, 36, 36, 43, 43, 43, 43, 36, 36, 36, 36, 36, 36,165, 30,
- 36, 36, 36, 36, 36, 36,165, 27, 36, 36, 36, 36, 72, 36, 36, 36,
- 36, 36, 64, 43, 43,163, 27, 27, 36, 36, 36, 36, 58, 2, 2, 2,
- 36, 36, 36, 36, 27, 27, 27, 27, 16, 16, 16, 16, 16, 27, 27, 27,
- 36, 36, 43, 43, 43, 43, 43, 43, 36, 36, 36, 36, 36, 64,166, 51,
- 27, 27, 27, 87, 36, 36, 36, 36,163, 27, 30, 2, 2, 2, 2, 2,
- 36, 43, 43, 2, 2, 2, 2, 2, 36, 36,165, 27, 27, 27, 27, 27,
- 79, 81, 36, 36, 36, 36, 36, 36, 43, 43, 43, 57, 2, 2, 2, 2,
- 2, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 7, 7, 7,
- 65, 64, 65, 36, 36, 36, 36, 64, 78, 79, 43, 77, 79, 57, 73, 2,
- 2, 43, 43, 43, 43, 43, 67, 59, 36, 36, 36, 64, 43, 43, 79, 43,
- 43, 43, 43, 7, 7, 7, 7, 7, 2, 2, 82, 81, 36, 36, 36, 36,
- 36, 64, 2, 36, 36, 36, 36, 36, 36, 82, 78, 43, 43, 43, 43, 77,
- 81, 36, 58, 2, 56, 43, 57, 79, 7, 7, 7, 7, 7, 58, 58, 2,
- 90, 27, 27, 27, 27, 27, 27, 27, 36, 36, 36, 36, 36, 36, 78, 79,
- 43, 78, 77, 43, 2, 2, 2, 65, 36, 36, 36, 36, 36, 36, 36, 64,
- 77, 78, 78, 78, 78, 78, 78, 78, 36, 36, 36, 82, 78, 78, 81, 36,
- 36, 78, 78, 43, 43, 43, 43, 43, 36, 36, 82, 78, 43, 43, 43, 43,
- 78, 43, 77, 65, 36, 58, 2, 2, 7, 7, 7, 7, 7, 2, 2, 65,
- 78, 79, 43, 43, 77, 77, 78, 79, 77, 43, 36, 66, 36, 36, 36, 36,
- 36, 36, 36, 36, 36, 36, 36, 82, 78, 43, 43, 43, 78, 78, 43, 79,
- 57, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 36, 36, 43, 43,
- 78, 79, 43, 43, 43, 77, 79, 79, 57, 2, 36, 36, 36, 36, 36, 36,
- 36, 36, 36, 36, 36, 64, 79, 78, 43, 43, 43, 79, 58, 2, 2, 2,
+ 16, 16, 16, 16, 34, 16, 16, 16, 43, 57, 43, 43, 43, 43, 43, 43,
+ 77, 43, 43, 43, 65, 36, 64, 36, 36, 36, 65, 82, 43, 36, 36, 36,
+ 16, 16, 16, 16, 16, 16, 40, 40, 40, 40, 40, 40, 40, 44, 16, 16,
+ 16, 16, 16, 16, 44, 16, 16, 16, 16, 16, 16, 16, 16,100, 40, 40,
+ 32, 32, 32, 16, 16, 16, 16, 32, 16, 16, 16, 16, 11, 11, 11, 11,
+ 16, 16, 16, 16, 34, 11, 11, 11, 16, 16, 16, 16,101,101,101,101,
+ 16, 16, 16, 16, 11, 11,102,103, 41, 16, 16, 16, 11, 11,102, 41,
+ 16, 16, 16, 16, 11, 11,104, 41,105,105,105,105,105,106, 59, 59,
+ 51, 51, 51, 2,107,108,107,108, 2, 2, 2, 2,109, 59, 59,110,
+ 2, 2, 2, 2,111,112, 2,113,114, 2,115,116, 2, 2, 2, 2,
+ 2, 9,114, 2, 2, 2, 2,117, 59, 59, 59, 59, 59, 59, 59, 59,
+ 118, 40, 27, 27, 27, 8,115,119, 27, 27, 27, 27, 27, 8,115, 94,
+ 20, 20, 20, 20, 20, 20, 20, 20, 43, 43, 43, 43, 43, 43,120, 48,
+ 99, 48, 99, 43, 43, 43, 43, 43, 61,121, 61,122, 61, 34, 11, 16,
+ 11, 32,122, 61, 46, 11, 11, 61, 61, 61,121,121,121, 11, 11,123,
+ 11, 11, 35, 36, 39, 61, 16, 11, 8, 8, 46, 16, 16, 26, 61,124,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95,125,126, 95,127, 61, 61, 61,
+ 8, 8,128, 61, 61, 8, 61, 61,128, 26, 61,128, 61, 61, 61,128,
+ 61, 61, 61, 61, 61, 61, 61, 8, 61,128,128, 61, 61, 61, 61, 61,
+ 61, 61, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 61, 61, 61, 61, 4, 4, 61, 61, 8, 61, 61, 61,129,130, 61, 61,
+ 61, 61, 61, 61, 61, 61,128, 61, 61, 61, 61, 61, 61, 26, 8, 8,
+ 8, 8, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 8, 8,
+ 8, 61, 61, 61, 61, 61, 61, 61, 27, 27, 27, 27, 27, 27, 61, 61,
+ 61, 61, 61, 61, 61, 27, 27, 27, 61, 61, 61, 26, 61, 61, 61, 61,
+ 26, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 8, 8, 8, 8,
+ 61, 61, 61, 61, 61, 61, 61, 26, 61, 61, 61, 61, 4, 4, 4, 4,
+ 4, 4, 4, 27, 27, 27, 27, 27, 27, 27, 61, 61, 61, 61, 61, 61,
+ 8, 8,115,131, 8, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 4,
+ 8,115,132,132,132,132,132,132,132,132,132,132,131, 8, 8, 8,
+ 8, 8, 8, 8, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 4, 8,
+ 8, 8,128, 26, 8, 8,128, 61, 32, 11, 32, 34, 34, 34, 34, 11,
+ 32, 32, 34, 16, 16, 16, 40, 11, 32, 32,124, 61, 61,122, 34,133,
+ 43, 32, 16, 16, 50, 2, 90, 2, 36, 36, 36, 36, 36, 36, 36, 76,
+ 2, 2, 2, 2, 2, 2, 2, 56, 2,107,107, 2,111,112,107, 2,
+ 2, 2, 2, 6, 2, 98,107, 2,107, 4, 4, 4, 4, 2, 2, 80,
+ 2, 2, 2, 2, 2, 51, 2, 2, 98,134, 2, 2, 2, 2, 2, 2,
+ 61, 2,135,132,132,132,136, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 1, 2,137,138, 4, 4, 4, 4, 4, 61, 4, 4, 4, 4,139, 94,
+ 140, 95, 95, 95, 95, 43, 43, 78,141, 40, 40, 61, 95,142, 58, 61,
+ 72, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 64,143,144, 63,
+ 36, 36, 36, 36, 36, 58, 40, 63, 61, 27, 27, 61, 61, 61, 61, 61,
+ 27, 27, 27, 27, 27, 61, 61, 61, 61, 61, 61, 61, 27, 27, 27, 27,
+ 145, 27, 27, 27, 27, 27, 27, 27, 36, 36, 76, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36,146, 2, 32, 32, 32, 32, 32, 32, 32, 64,
+ 48,147, 43, 43, 43, 43, 43, 80, 32, 32, 32, 32, 32, 32, 40, 43,
+ 36, 36, 36, 95, 95, 95, 95, 95, 43, 2, 2, 2, 2, 2, 2, 2,
+ 41, 41, 41,144, 40, 40, 40, 40, 41, 32, 32, 32, 32, 32, 32, 32,
+ 16, 32, 32, 32, 32, 32, 32, 32, 44, 16, 16, 16, 34, 34, 34, 32,
+ 32, 32, 32, 32, 42,148, 34, 35, 32, 32, 16, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 11, 11, 32, 11, 11, 32, 32, 32, 32, 32, 32,
+ 32, 32, 11, 11, 34, 34, 32, 16, 32, 16, 16, 32, 32, 32, 11, 11,
+ 11, 40,149, 35, 40, 35, 36, 36, 36, 65, 36, 65, 36, 64, 36, 36,
+ 36, 82, 79, 77, 61, 61, 43, 43, 27, 27, 27, 61,150, 61, 61, 61,
+ 36, 36, 2, 2, 2, 2, 2, 2, 78, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 78, 78, 78, 78, 78, 78, 78, 78, 43, 43, 43, 43, 43, 2,
+ 43, 36, 36, 36, 2, 66, 66, 64, 36, 36, 36, 43, 43, 43, 43, 2,
+ 36, 36, 36, 64, 43, 43, 43, 43, 43, 78, 78, 78, 78, 78, 78, 97,
+ 36, 64, 78, 43, 43, 78, 43, 78, 97, 2, 2, 2, 2, 2, 2, 80,
+ 7, 7, 7, 7, 7, 7, 7, 2, 36, 36, 64, 63, 36, 36, 36, 36,
+ 36, 36, 36, 36, 64, 43, 43, 77, 79, 77, 79, 43, 43, 43, 43, 43,
+ 36, 64, 36, 36, 36, 36, 77, 78, 7, 7, 7, 7, 7, 7, 2, 2,
+ 63, 36, 36, 71, 61, 82, 77, 36, 65, 43, 65, 64, 65, 36, 36, 43,
+ 36, 36, 36, 36, 36, 36, 76, 2, 36, 36, 36, 36, 36, 82, 43, 78,
+ 2, 76,151, 43, 43, 43, 43, 43, 16, 16, 16, 16, 16,103, 40, 40,
+ 16, 16, 16, 16,100, 41, 41, 41, 36, 82, 79, 78, 77, 97, 79, 43,
+ 152,152,152,152,152,152,152,152,153,153,153,153,153,153,153,153,
+ 16, 16, 16, 16, 16, 16, 35, 65, 36, 36, 36, 36,154, 36, 36, 36,
+ 36, 41, 41, 41, 41, 41, 41, 41, 41, 74, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36,132, 36, 36, 36, 36, 36, 36, 36, 71,
+ 36, 36, 36, 36, 36, 36,150, 61, 2, 2, 2,135,116, 2, 2, 2,
+ 6,155,156,132,132,132,132,132,132,132,116,135,116, 2,113,157,
+ 2, 2, 2, 2,139,132,132,116, 2,158, 8, 8, 60, 2, 2, 2,
+ 36, 36, 36, 36, 36, 36, 36,159, 2, 2, 3, 2, 4, 5, 6, 2,
+ 16, 16, 16, 16, 16, 17, 18,115,116, 4, 2, 36, 36, 36, 36, 36,
+ 63, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 40,
+ 20,160, 53, 20, 26, 8,128, 61, 61, 61, 61, 61,161, 59, 61, 61,
+ 2, 2, 2, 90, 27, 27, 27, 27, 27, 27, 27, 84, 61, 61, 61, 61,
+ 95, 95,127, 27, 84, 61, 61, 61, 61, 61, 61, 61, 61, 27, 61, 61,
+ 61, 61, 61, 61, 61, 61, 47, 43,162,162,162,162,162,162,162,162,
+ 163, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 87, 36,
+ 138, 36, 36, 36, 36, 95, 95, 95, 36, 36, 36, 36, 36, 36, 36, 58,
+ 164, 95, 95, 95, 95, 95, 95, 95, 11, 11, 11, 32, 16, 16, 16, 16,
+ 36, 36, 36, 58, 27, 27, 27, 27, 36, 36, 36, 71,145, 27, 27, 27,
+ 36, 36, 36,165, 27, 27, 27, 27, 36, 36, 36, 36, 36,165, 27, 27,
+ 36, 36, 36, 27, 27, 27, 27, 30, 36, 36, 36, 36, 36, 36, 27, 36,
+ 64, 43, 43, 43, 43, 43, 43, 43, 36, 36, 36, 36, 43, 43, 43, 43,
+ 36, 36, 36, 36, 36, 36,165, 30, 36, 36, 36, 36, 36, 36,165, 27,
+ 36, 36, 36, 36, 72, 36, 36, 36, 36, 36, 64, 43, 43,163, 27, 27,
+ 36, 36, 36, 36, 58, 2, 2, 2, 36, 36, 36, 36, 27, 27, 27, 27,
+ 16, 16, 16, 16, 16, 27, 27, 27, 36, 36, 43, 43, 43, 43, 43, 43,
+ 7, 7, 7, 7, 7, 36, 36, 63, 11, 11, 11, 11,166, 43, 43,141,
+ 16, 16, 16, 16, 16, 16, 16, 8, 36, 36, 36, 36, 36, 64,167, 51,
+ 36, 36, 36, 36, 36, 36, 43, 43, 27, 27, 27, 87, 36, 36, 36, 36,
+ 163, 27, 30, 2, 2, 2, 2, 2, 36, 43, 43, 2, 2, 2, 2, 2,
+ 36, 36,165, 27, 27, 27, 27, 27, 79, 81, 36, 36, 36, 36, 36, 36,
+ 43, 43, 43, 57, 2, 2, 2, 2, 2, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 7, 7, 7, 7, 7, 65, 64, 65, 36, 36, 36, 36, 64,
+ 78, 79, 43, 77, 79, 57, 73, 2, 2, 43, 43, 43, 43, 43, 67, 59,
+ 36, 36, 36, 64, 43, 43, 79, 43, 43, 43, 43, 7, 7, 7, 7, 7,
+ 2, 2, 82, 81, 36, 36, 36, 36, 36, 64, 2, 36, 36, 36, 36, 36,
+ 36, 82, 78, 43, 43, 43, 43, 77, 81, 36, 58, 2, 56, 43, 57, 79,
+ 7, 7, 7, 7, 7, 58, 58, 2, 90, 27, 27, 27, 27, 27, 27, 27,
+ 36, 36, 36, 36, 36, 36, 78, 79, 43, 78, 77, 43, 2, 2, 2, 65,
+ 36, 36, 36, 36, 36, 36, 36, 64, 77, 78, 78, 78, 78, 78, 78, 78,
+ 36, 36, 36, 82, 78, 78, 81, 36, 36, 78, 78, 43, 43, 43, 43, 43,
+ 36, 36, 36, 36, 78, 79, 43, 43, 43, 78, 78, 78, 78, 78, 78, 77,
+ 65, 65, 2, 2, 2, 2, 2, 2, 56, 43, 43, 43, 43, 43, 43, 43,
+ 36, 36, 82, 78, 43, 43, 43, 43, 78, 43, 77, 65, 36, 58, 2, 2,
+ 7, 7, 7, 7, 7, 2, 2, 65, 78, 79, 43, 43, 77, 77, 78, 79,
+ 77, 43, 36, 66, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 82,
+ 78, 43, 43, 43, 78, 78, 43, 79, 57, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 36, 36, 43, 43, 78, 79, 43, 43, 43, 77, 79, 79,
+ 57, 2, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 64, 79, 78,
+ 43, 43, 43, 79, 58, 2, 2, 2, 36, 36, 36, 36, 36, 36, 64, 79,
78, 43, 43, 79, 43, 43, 43, 43, 7, 7, 7, 7, 7, 27, 2, 89,
43, 43, 43, 43, 79, 57, 2, 2, 27, 27, 27, 27, 27, 27, 27, 87,
78, 78, 78, 78, 78, 79, 77, 65, 81, 79, 2, 2, 2, 2, 2, 2,
@@ -4721,39 +4938,41 @@ _hb_ucd_u8[13386] =
78, 78, 78, 78, 78, 78, 78, 78, 64, 43, 43, 43, 43, 65, 36, 36,
36, 64, 43, 43, 77, 64, 43, 57, 2, 2, 2, 56, 43, 43, 43, 43,
64, 43, 43, 77, 79, 43, 36, 36, 36, 36, 36, 36, 36, 43, 43, 43,
- 43, 43, 43, 77, 43, 2, 66, 2, 43, 43, 43, 43, 43, 43, 43, 79,
- 58, 2, 2, 2, 2, 2, 2, 2, 2, 36, 36, 36, 36, 36, 36, 36,
+ 43, 43, 43, 77, 43, 2, 66, 2, 58, 2, 2, 2, 2, 2, 2, 2,
+ 43, 43, 43, 43, 43, 43, 43, 79, 2, 36, 36, 36, 36, 36, 36, 36,
43, 43, 43, 43, 77, 43, 43, 43, 77, 43, 79, 43, 43, 43, 43, 43,
43, 43, 43, 64, 43, 43, 43, 43, 36, 36, 36, 36, 36, 78, 78, 78,
43, 77, 79, 79, 36, 36, 36, 36, 36, 64, 77, 97, 2, 2, 2, 2,
43, 82, 36, 36, 36, 36, 36, 36, 36, 36, 78, 43, 43, 43, 43, 78,
- 77, 57, 2, 2, 2, 2, 2, 2, 27, 27, 84, 61, 61, 61, 53, 20,
- 150, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 21,
- 65, 36, 36, 64, 43, 43, 43, 43, 43, 43, 57, 2, 2, 2, 2, 2,
- 43, 43, 43, 57, 2, 2, 61, 61, 40, 40, 89, 61, 61, 61, 61, 61,
- 7, 7, 7, 7, 7,167, 27, 27, 27, 87, 36, 36, 36, 36, 36, 36,
- 27, 27, 27, 30, 2, 2, 2, 2, 82, 78, 78, 78, 78, 78, 78, 78,
- 78, 78, 78, 78, 78, 78, 78, 79, 43, 68, 40, 40, 40, 40, 40, 40,
- 40, 80, 43, 43, 43, 43, 43, 43, 36, 36, 36, 36, 36, 36, 47, 57,
- 61, 61,168, 79, 43, 61,168, 78, 78,169, 59, 59, 59, 76, 43, 43,
- 43, 70, 47, 43, 43, 43, 61, 61, 61, 61, 61, 61, 61, 43, 43, 61,
- 61, 43, 70, 61, 61, 61, 61, 61, 11, 11, 11, 11, 11, 16, 16, 16,
- 16, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 16,
- 11, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 11, 11,
- 11, 11, 11, 16, 16, 16, 16, 16, 31, 16, 16, 16, 16, 16, 16, 16,
- 16, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 11, 11, 11,
- 11, 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11,
- 11, 11, 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 33,
- 16, 16, 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 31,
- 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 31, 16, 16, 16,
- 16, 33, 16, 16, 16, 32, 16, 7, 43, 43, 43, 70, 61, 47, 43, 43,
- 43, 43, 43, 43, 43, 43, 70, 61, 61, 61, 47, 61, 61, 61, 61, 61,
- 61, 61, 70, 21, 2, 2, 2, 2, 2, 2, 2, 2, 2, 56, 43, 43,
- 16, 16, 16, 16, 16, 39, 16, 16, 43, 43, 43, 68, 40, 40, 40, 40,
- 7, 7, 7, 7, 7, 7, 7, 71, 36, 36, 36, 36, 36, 36, 36, 43,
- 36, 36, 36, 36, 36, 36, 43, 43, 7, 7, 7, 7, 7, 7, 7,170,
- 36, 36, 36, 36, 36, 75, 43, 43, 16, 16, 43, 43, 43, 68, 40, 40,
- 27, 27, 27, 27, 27, 27,145, 27,171, 27, 27, 27, 27, 27, 27, 27,
+ 77, 57, 2, 2, 2, 2, 2, 2, 7, 7, 7, 7, 7, 43, 43, 43,
+ 27, 27, 84, 61, 61, 61, 53, 20,150, 61, 61, 61, 61, 61, 61, 61,
+ 61, 61, 61, 61, 61, 61, 61, 21, 65, 36, 36, 64, 43, 43, 43, 43,
+ 36, 36, 36, 36, 36, 36, 36, 43, 43, 43, 43, 43, 43, 78, 79, 43,
+ 43, 43, 57, 2, 2, 2, 2, 2, 43, 43, 43, 57, 2, 2, 61, 61,
+ 40, 40, 89, 61, 61, 61, 61, 61, 7, 7, 7, 7, 7,168, 27, 27,
+ 27, 87, 36, 36, 36, 36, 36, 36, 40, 63, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 76,146, 2, 27, 27, 27, 30, 2, 2, 2, 2,
+ 82, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 79,
+ 43, 68, 40, 40, 40, 40, 40, 40, 40, 80, 43, 43, 43, 43, 43, 43,
+ 36, 36, 36, 36, 36, 36, 47, 57, 61, 61,169, 79, 43, 61,169, 78,
+ 78,170, 59, 59, 59, 75, 43, 43, 43, 70, 47, 43, 43, 43, 61, 61,
+ 61, 61, 61, 61, 61, 43, 43, 61, 61, 43, 70, 61, 61, 61, 61, 61,
+ 11, 11, 11, 11, 11, 16, 16, 16, 16, 16, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 16, 11, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 11, 11, 11, 11, 11, 16, 16, 16, 16, 16,
+ 31, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 33, 16, 16,
+ 16, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 31, 16, 16,
+ 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 31, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 33, 16, 16, 16, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16,
+ 11, 11, 11, 11, 31, 16, 16, 16, 16, 33, 16, 16, 16, 32, 16, 7,
+ 43, 43, 43, 70, 61, 47, 43, 43, 43, 43, 43, 43, 43, 43, 70, 61,
+ 61, 61, 47, 61, 61, 61, 61, 61, 61, 61, 70, 21, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 56, 43, 43, 16, 16, 16, 16, 16, 39, 16, 16,
+ 43, 43, 43, 68, 40, 40, 40, 40, 7, 7, 7, 7, 7, 7, 7, 71,
+ 7, 7, 7, 7, 7, 7, 7,171, 36, 36, 36, 36, 36, 76, 43, 43,
+ 172, 7, 7, 7, 7, 7, 7, 85, 16, 16, 43, 43, 43, 68, 40, 40,
+ 27, 27, 27, 27, 27, 27,145, 27,173, 27, 27, 27, 27, 27, 27, 27,
27, 27, 27, 27, 27, 27, 27,145, 27, 27, 27, 27, 27, 27, 84, 61,
61, 61, 61, 61, 61, 25, 41, 41, 0, 0, 29, 21, 21, 21, 23, 21,
22, 18, 21, 25, 21, 17, 13, 13, 25, 25, 25, 21, 21, 9, 9, 9,
@@ -4764,8 +4983,8 @@ _hb_ucd_u8[13386] =
6, 5, 9, 21, 25, 9, 26, 12, 11, 11, 9, 6, 5, 21, 17, 17,
17, 26, 26, 23, 23, 12, 17, 12, 21, 12, 12, 21, 7, 21, 1, 1,
21, 23, 26, 26, 1, 21, 6, 7, 7, 12, 12, 7, 21, 7, 12, 1,
- 12, 6, 6, 12, 12, 26, 7, 26, 26, 7, 21, 1, 24, 7, 7, 6,
- 1, 12, 12, 10, 10, 10, 10, 12, 21, 6, 10, 7, 7, 10, 23, 7,
+ 12, 6, 6, 12, 12, 26, 7, 26, 26, 7, 21, 1, 24, 7, 1, 12,
+ 7, 6, 12, 10, 10, 10, 10, 12, 21, 6, 10, 7, 7, 10, 23, 7,
15, 26, 13, 21, 13, 7, 15, 7, 12, 23, 21, 26, 21, 15, 17, 7,
29, 7, 7, 22, 18, 18, 14, 14, 14, 7, 10, 21, 17, 21, 11, 12,
5, 6, 8, 8, 8, 24, 5, 24, 9, 24, 29, 29, 29, 1, 20, 19,
@@ -4776,247 +4995,250 @@ _hb_ucd_u8[13386] =
26, 14, 17, 6, 14, 6, 12, 24, 24, 6, 26, 15, 6, 21, 11, 21,
24, 9, 6, 9, 23, 26, 6, 10, 4, 4, 3, 3, 7, 25, 17, 16,
16, 22, 16, 16, 25, 17, 7, 1, 25, 24, 26, 1, 2, 2, 12, 15,
- 21, 14, 7, 15, 12, 17, 13, 15, 26, 10, 10, 1, 13, 23, 23, 15,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10, 11, 12, 13, 0,
- 14, 0, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0, 0, 0, 0, 0,
+ 21, 14, 7, 15, 9, 12, 12, 17, 13, 15, 26, 10, 10, 1, 13, 23,
+ 7, 13, 23, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10,
+ 11, 12, 13, 0, 14, 0, 0, 0, 0, 0, 15, 0, 16, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 17, 18, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 21,
- 22, 23, 0, 0, 0, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 0, 20, 0, 21, 22, 23, 0, 0, 0, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 35, 0, 0, 0, 0, 36, 0, 37, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 35, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 38, 39, 0, 0, 0, 0, 0, 0,
- 40, 41, 42, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 1, 2, 0, 0, 0, 0, 3, 0, 0, 0, 4, 5, 6, 7,
- 0, 8, 9, 10, 0, 11, 12, 13, 14, 15, 16, 17, 16, 18, 16, 19,
- 16, 19, 16, 19, 0, 19, 16, 20, 16, 19, 21, 19, 0, 22, 23, 24,
- 25, 26, 27, 28, 29, 30, 31, 0, 32, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, 35, 0, 0,
- 36, 0, 37, 0, 0, 0, 38, 39, 40, 41, 42, 43, 44, 45, 46, 0,
- 0, 47, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0,
- 0, 50, 0, 51, 0, 52, 53, 0, 54, 0, 0, 0, 0, 0, 0, 55,
- 56, 57, 0, 0, 0, 0, 58, 0, 0, 59, 60, 61, 62, 63, 0, 0,
- 64, 65, 0, 0, 0, 66, 0, 0, 0, 0, 67, 0, 0, 0, 68, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0,
- 0, 70, 0, 71, 0, 0, 72, 0, 0, 73, 0, 0, 0, 0, 0, 0,
- 0, 0, 74, 0, 0, 0, 0, 0, 75, 76, 0, 77, 78, 0, 0, 79,
- 80, 0, 81, 62, 0, 82, 83, 0, 0, 84, 85, 86, 0, 0, 0, 87,
- 0, 88, 0, 0, 51, 89, 51, 0, 90, 0, 91, 0, 0, 0, 80, 0,
- 0, 0, 92, 93, 0, 94, 95, 96, 97, 0, 0, 0, 0, 0, 51, 0,
- 0, 0, 0, 98, 99, 0, 0, 0, 0, 0, 0,100, 0, 0, 0, 0,
- 0,101,102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,103, 0, 0,
- 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,105,106, 0, 0,107,
- 0, 0, 0, 0, 0, 0,108, 0,109, 0,102, 0, 0, 0, 0, 0,
- 110,111, 0, 0, 0, 0, 0, 0, 0,112, 0, 0, 0, 0, 0, 0,
- 0,113, 0,114, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
- 7, 0, 8, 0, 0, 0, 0, 9, 10, 11, 12, 0, 0, 0, 0, 13,
- 0, 0, 14, 15, 0, 16, 0, 17, 18, 0, 0, 19, 0, 20, 21, 0,
- 0, 0, 0, 0, 22, 23, 0, 24, 25, 0, 0, 26, 0, 0, 0, 27,
- 0, 0, 28, 29, 30, 31, 0, 0, 0, 32, 33, 34, 0, 0, 33, 0,
- 0, 35, 33, 0, 0, 0, 33, 36, 0, 0, 0, 0, 0, 37, 38, 0,
- 0, 0, 0, 0, 0, 39, 40, 0, 0, 0, 0, 0, 0, 41, 42, 0,
- 0, 0, 0, 43, 0, 44, 0, 0, 0, 45, 46, 0, 0, 0, 47, 0,
- 0, 0, 0, 0, 0, 48, 49, 0, 0, 0, 0, 50, 0, 0, 0, 51,
- 0, 52, 0, 53, 0, 0, 0, 0, 54, 0, 0, 0, 0, 55, 0, 56,
- 0, 0, 0, 0, 57, 58, 0, 0, 0, 59, 60, 0, 0, 0, 0, 0,
- 0, 61, 52, 0, 62, 63, 0, 0, 64, 0, 0, 0, 65, 66, 0, 0,
- 0, 67, 0, 68, 69, 70, 71, 72, 1, 73, 0, 74, 75, 76, 0, 0,
- 77, 78, 0, 0, 0, 79, 0, 0, 1, 1, 0, 0, 80, 0, 0, 81,
- 0, 0, 0, 0, 77, 82, 0, 83, 0, 0, 0, 0, 0, 78, 84, 0,
- 85, 0, 52, 0, 1, 78, 0, 0, 86, 0, 0, 87, 0, 0, 0, 0,
- 0, 88, 57, 0, 0, 0, 0, 0, 0, 89, 90, 0, 0, 84, 0, 0,
- 33, 0, 0, 91, 0, 0, 0, 0, 92, 0, 0, 0, 0, 49, 0, 0,
- 93, 0, 0, 0, 0, 94, 95, 0, 0, 96, 0, 0, 97, 0, 0, 0,
- 98, 0, 0, 0, 99, 0, 0, 0, 0,100,101, 93, 0, 0,102, 0,
- 0, 0, 84, 0, 0,103, 0, 0, 0,104,105, 0, 0,106,107, 0,
- 0, 0, 0, 0, 0,108, 0, 0,109, 0, 0, 0, 0,110, 33, 0,
- 111,112,113, 35, 0, 0,114, 0, 0, 0,115, 0, 0, 0, 0, 0,
- 0,116, 0, 0,117, 0, 0, 0, 0,118, 88, 0, 0, 0, 0, 0,
- 57, 0, 0, 0, 0, 52,119, 0, 0, 0, 0,120, 0, 0,121, 0,
- 0, 0, 0,119, 0, 0,122, 0, 0, 0, 0, 0, 0,123, 0, 0,
- 0,124, 0, 0, 0,125, 0,126, 0, 0, 0, 0,127,128,129, 0,
- 130, 0,131, 0, 0, 0,132,133,134, 0, 77, 0, 0, 0, 0, 0,
- 35, 0, 0, 0,135, 0, 0, 0,136, 0, 0,137, 0, 0,138, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6,
- 7, 4, 4, 8, 9, 10, 1, 11, 12, 13, 14, 15, 16, 17, 18, 1,
- 1, 1, 19, 1, 0, 0, 20, 21, 22, 1, 23, 4, 21, 24, 25, 26,
- 27, 28, 29, 30, 0, 0, 1, 1, 31, 0, 0, 0, 32, 33, 34, 35,
- 1, 36, 37, 0, 0, 0, 0, 38, 1, 39, 14, 39, 40, 41, 42, 0,
- 0, 0, 43, 36, 44, 45, 21, 45, 46, 0, 0, 0, 19, 1, 21, 0,
- 0, 47, 0, 38, 48, 1, 1, 49, 49, 50, 0, 0, 51, 0, 0, 0,
- 52, 1, 0, 0, 38, 14, 4, 1, 1, 1, 53, 21, 43, 52, 54, 21,
- 35, 1, 0, 0, 0, 55, 0, 0, 0, 56, 57, 58, 0, 0, 0, 0,
- 0, 59, 0, 60, 0, 0, 0, 0, 61, 62, 0, 0, 63, 0, 0, 0,
- 64, 0, 0, 0, 65, 0, 0, 0, 66, 0, 0, 0, 67, 0, 0, 0,
- 68, 0, 0, 69, 70, 0, 71, 72, 73, 74, 75, 76, 0, 0, 0, 77,
- 0, 0, 0, 78, 79, 0, 0, 0, 0, 47, 0, 0, 0, 49, 0, 80,
- 0, 0, 0, 62, 0, 0, 63, 0, 0, 81, 0, 0, 82, 0, 0, 0,
- 83, 0, 0, 19, 84, 0, 62, 0, 0, 0, 0, 49, 1, 85, 1, 52,
- 15, 86, 36, 10, 21, 87, 0, 55, 0, 0, 0, 0, 19, 10, 1, 0,
- 0, 0, 0, 0, 88, 0, 0, 89, 0, 0, 88, 0, 0, 0, 0, 78,
- 0, 0, 87, 9, 12, 4, 90, 8, 91, 47, 0, 58, 50, 0, 21, 1,
- 21, 92, 93, 1, 1, 1, 1, 94, 95, 96, 97, 1, 98, 58, 81, 99,
- 100, 4, 58, 0, 0, 0, 0, 0, 0, 19, 50, 0, 0, 0, 0, 0,
- 0, 61, 0, 0,101,102, 0, 0,103, 0, 0, 1, 1, 50, 0, 0,
- 0, 38, 0, 63, 0, 0, 0, 0, 0, 62, 0, 0,104, 68, 61, 0,
- 0, 0, 78, 0, 0, 0,105,106, 58, 38, 81, 0, 0, 0, 0, 0,
- 0,107, 1, 14, 4, 12, 84, 0, 0, 0, 0, 38, 87, 0, 0, 0,
- 0,108, 0, 0,109, 61, 0,110, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 40, 0, 0,
+ 0, 0, 0, 0, 41, 42, 43, 0, 44, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0, 0, 0,
+ 4, 5, 6, 7, 0, 8, 9, 10, 0, 11, 12, 13, 14, 15, 16, 17,
+ 16, 18, 16, 19, 16, 19, 16, 19, 0, 19, 16, 20, 16, 19, 21, 19,
+ 0, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 32, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, 0,
+ 0, 35, 0, 0, 36, 0, 37, 0, 0, 0, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0,
+ 0, 0, 0, 0, 0, 50, 0, 51, 0, 52, 53, 0, 54, 0, 0, 0,
+ 0, 0, 0, 55, 56, 57, 0, 0, 0, 0, 58, 0, 0, 59, 60, 61,
+ 62, 63, 0, 0, 64, 65, 0, 0, 0, 66, 0, 0, 0, 0, 67, 0,
+ 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 69, 0, 0, 0, 70, 0, 71, 0, 0, 72, 0, 0, 73, 0, 0,
+ 0, 0, 0, 0, 0, 0, 74, 75, 0, 0, 0, 0, 76, 77, 0, 78,
+ 79, 0, 0, 80, 81, 0, 82, 62, 0, 83, 84, 0, 0, 85, 86, 87,
+ 0, 88, 0, 89, 0, 90, 0, 0, 51, 91, 51, 0, 92, 0, 93, 0,
+ 0, 0, 81, 0, 0, 0, 94, 95, 0, 96, 97, 98, 99, 0, 0, 0,
+ 0, 0, 51, 0, 0, 0, 0,100,101, 0, 0, 0, 0, 0, 0,102,
+ 0, 0, 0, 0, 0, 0,103, 0, 0, 0, 0, 0, 0,104,105, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,106, 0, 0,107, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,108,109, 0, 0,110, 0, 0, 0, 0,
+ 0, 0,111, 0,112, 0,105, 0, 0, 0, 0, 0,113,114, 0, 0,
+ 0, 0, 0, 0, 0,115, 0, 0, 0,116, 0, 0, 0,117, 0,118,
+ 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 0, 8, 0,
+ 0, 0, 0, 9, 10, 11, 12, 0, 0, 0, 0, 13, 0, 0, 14, 15,
+ 0, 16, 0, 17, 18, 0, 0, 19, 0, 20, 21, 0, 0, 0, 0, 0,
+ 22, 23, 0, 24, 25, 0, 0, 26, 0, 0, 0, 27, 0, 0, 28, 29,
+ 30, 31, 0, 0, 0, 32, 33, 34, 0, 0, 33, 0, 0, 35, 33, 0,
+ 0, 0, 33, 36, 0, 0, 0, 0, 0, 37, 38, 0, 0, 0, 0, 0,
+ 0, 39, 40, 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0, 0, 43,
+ 0, 44, 0, 0, 0, 45, 46, 0, 0, 0, 47, 0, 0, 0, 0, 0,
+ 0, 48, 49, 0, 0, 0, 0, 50, 0, 0, 0, 51, 0, 52, 0, 53,
+ 0, 0, 0, 0, 54, 0, 0, 0, 0, 55, 0, 56, 0, 0, 0, 0,
+ 57, 58, 0, 0, 0, 59, 60, 0, 0, 0, 0, 0, 0, 61, 52, 0,
+ 62, 63, 0, 0, 64, 0, 0, 0, 65, 66, 0, 0, 0, 67, 0, 68,
+ 69, 70, 71, 72, 1, 73, 0, 74, 75, 76, 0, 0, 77, 78, 0, 0,
+ 0, 79, 0, 0, 1, 1, 0, 0, 80, 0, 0, 81, 0, 0, 0, 0,
+ 77, 82, 0, 83, 0, 0, 0, 0, 0, 78, 84, 0, 85, 0, 52, 0,
+ 1, 78, 0, 0, 86, 0, 0, 87, 0, 0, 0, 0, 0, 88, 57, 0,
+ 0, 0, 0, 0, 0, 89, 90, 0, 0, 84, 0, 0, 33, 0, 0, 91,
+ 0, 0, 0, 0, 92, 0, 0, 0, 0, 49, 0, 0, 93, 0, 0, 0,
+ 0, 94, 95, 0, 0, 96, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0,
+ 99, 0, 0, 0,100, 0, 0, 0, 0,101,102, 93, 0, 0,103, 0,
+ 0, 0, 84, 0, 0,104, 0, 0, 0,105,106, 0, 0,107,108, 0,
+ 0, 0, 0, 0, 0,109, 0, 0,110, 0, 0, 0, 0,111, 33, 0,
+ 112,113,114, 57, 0, 0,115, 35, 0, 0,116, 0, 0, 0,117, 0,
+ 0, 0, 0, 0, 0,118, 0, 0,119, 0, 0, 0, 0,120, 88, 0,
+ 0, 0, 0, 0, 57, 0, 0, 0, 0, 52,121, 0, 0, 0, 0,122,
+ 0, 0,123, 0, 0, 0, 0,121, 0, 0,124, 0, 0, 0, 0, 0,
+ 79, 0, 0, 0, 0,125, 0, 0, 0,126, 0, 0, 0,127, 0,128,
+ 0, 0, 0, 0,129,130,131, 0,132, 0,133, 0, 0, 0,134,135,
+ 136, 0, 77, 0, 0, 0, 0, 0, 35, 0, 0, 0,137, 0, 0, 0,
+ 138, 0, 0, 0,139, 0, 0,140, 0, 0,141, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 4, 4, 8,
+ 9, 10, 1, 11, 12, 13, 14, 15, 16, 17, 18, 1, 1, 1, 19, 1,
+ 0, 0, 20, 21, 22, 1, 23, 4, 21, 24, 25, 26, 27, 28, 29, 30,
+ 0, 0, 1, 1, 31, 0, 0, 0, 32, 33, 34, 35, 1, 36, 37, 0,
+ 0, 0, 0, 38, 1, 39, 14, 39, 40, 41, 42, 0, 0, 0, 43, 36,
+ 44, 45, 21, 45, 46, 0, 0, 0, 19, 1, 21, 0, 0, 47, 0, 38,
+ 48, 1, 1, 49, 49, 50, 0, 0, 51, 0, 0, 19, 52, 1, 0, 0,
+ 38, 14, 4, 1, 1, 1, 53, 21, 43, 52, 54, 21, 35, 1, 0, 0,
+ 0, 55, 0, 0, 0, 56, 57, 58, 0, 0, 0, 0, 0, 59, 0, 60,
+ 0, 0, 0, 0, 61, 62, 0, 0, 63, 0, 0, 0, 64, 0, 0, 0,
+ 65, 0, 0, 0, 66, 0, 0, 0, 67, 0, 0, 0, 68, 0, 0, 69,
+ 70, 0, 71, 72, 73, 74, 75, 76, 0, 0, 0, 77, 0, 0, 0, 78,
+ 79, 0, 0, 0, 0, 47, 0, 0, 0, 49, 0, 80, 0, 0, 0, 62,
+ 0, 0, 63, 0, 0, 81, 0, 0, 82, 0, 0, 0, 83, 0, 0, 19,
+ 84, 0, 62, 0, 0, 0, 0, 49, 1, 85, 1, 52, 15, 86, 36, 10,
+ 21, 87, 0, 55, 0, 0, 0, 0, 19, 10, 1, 0, 0, 0, 0, 0,
+ 88, 0, 0, 89, 0, 0, 88, 0, 0, 0, 0, 78, 0, 0, 87, 9,
+ 12, 4, 90, 8, 91, 47, 0, 58, 50, 0, 21, 1, 21, 92, 93, 1,
+ 1, 1, 1, 94, 95, 96, 97, 1, 98, 58, 81, 99,100, 4, 58, 0,
+ 0, 0, 0, 0, 0, 19, 50, 0, 0, 0, 0, 0, 0, 61, 0, 0,
+ 101,102, 0, 0,103, 0, 0, 1, 1, 50, 0, 0, 0, 38, 0, 63,
+ 0, 0, 0, 0, 0, 62, 0, 0,104, 68, 61, 0, 0, 0, 78, 0,
+ 0, 0,105,106, 58, 38, 81, 0, 0, 0, 0, 0, 0,107, 1, 14,
+ 4, 12, 84, 0, 0, 0, 0, 38, 87, 0, 0, 0, 0,108, 0, 0,
+ 109, 61, 0,110, 0, 0, 0, 1, 0, 0, 0, 0, 49, 50, 0, 0,
19, 58, 0, 0, 0, 51, 0,111, 14, 52,112, 41, 0, 0, 62, 0,
0, 61, 0, 0,113, 0, 87, 0, 0, 0, 61, 62, 0, 0, 62, 0,
89, 0, 0,113, 0, 0, 0, 0,114, 0, 0, 0, 78, 55, 0, 38,
- 1, 58, 1, 58, 0, 0, 63, 89, 0, 0,115, 0, 0, 0, 55, 0,
- 0, 0, 0,115, 0, 0, 0, 0, 61, 0, 0, 0, 0, 79, 0, 61,
- 0, 0, 0, 0, 56, 0, 89, 80, 0, 0, 79, 0, 0, 0, 8, 91,
- 0, 0, 1, 87, 0, 0,116, 0, 0, 0, 0, 0, 0,117, 0,118,
- 119,120,121, 0,104, 4,122, 49, 23, 0, 0, 0, 38, 50, 38, 58,
- 0, 0, 1, 87, 1, 1, 1, 1, 39, 1, 48,105, 87, 0, 0, 0,
- 0, 1, 0, 0, 0,123, 4,122, 0, 0, 0, 1,124, 0, 0, 0,
- 0, 0,230,230,230,230,230,232,220,220,220,220,232,216,220,220,
- 220,220,220,202,202,220,220,220,220,202,202,220,220,220, 1, 1,
- 1, 1, 1,220,220,220,220,230,230,230,230,240,230,220,220,220,
- 230,230,230,220,220, 0,230,230,230,220,220,220,220,230,232,220,
- 220,230,233,234,234,233,234,234,233,230, 0, 0, 0,230, 0,220,
- 230,230,230,230,220,230,230,230,222,220,230,230,220,220,230,222,
- 228,230, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22,
- 0, 23, 0, 24, 25, 0,230,220, 0, 18, 30, 31, 32, 0, 0, 0,
- 0, 27, 28, 29, 30, 31, 32, 33, 34,230,230,220,220,230,220,230,
- 230,220, 35, 0, 0, 0, 0, 0,230,230,230, 0, 0,230,230, 0,
- 220,230,230,220, 0, 0, 0, 36, 0, 0,230,220,230,230,220,220,
- 230,220,220,230,220,230,220,230,230, 0, 0,220, 0, 0,230,230,
- 0,230, 0,230,230,230,230,230, 0, 0, 0,220,220,220,230,220,
- 220,220,230,230, 0,220, 27, 28, 29,230, 7, 0, 0, 0, 0, 9,
- 0, 0, 0,230,220,230,230, 0, 0, 0, 0, 0,230, 0, 0, 84,
- 91, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 9, 0,103,103,
- 9, 0,107,107,107,107,118,118, 9, 0,122,122,122,122,220,220,
- 0, 0, 0,220, 0,220, 0,216, 0, 0, 0,129,130, 0,132, 0,
- 0, 0, 0, 0,130,130,130,130, 0, 0,130, 0,230,230, 9, 0,
- 230,230, 0, 0,220, 0, 0, 0, 0, 7, 0, 9, 9, 0, 9, 9,
- 0, 0, 0,230, 0, 0, 0,228, 0, 0, 0,222,230,220,220, 0,
- 0, 0,230, 0, 0,220,230,220, 0,220,230,230,230, 0, 0, 0,
- 9, 9, 0, 0, 7, 0,230, 0, 1, 1, 1, 0, 0, 0,230,234,
- 214,220,202,230,230,230,230,230,232,228,228,220,218,230,233,220,
- 230,220,230,230, 1, 1, 1, 1, 1,230, 0, 1, 1,230,220,230,
- 1, 1, 0, 0,218,228,232,222,224,224, 0, 8, 8, 0, 0, 0,
- 0,220,230, 0,230,230,220, 0, 0,230, 0, 0, 26, 0, 0,220,
- 0,230,230, 1,220, 0, 0,230,220, 0, 0, 0,220,220, 0, 0,
- 230,220, 0, 9, 7, 0, 0, 7, 9, 0, 0, 0, 9, 7, 6, 6,
- 0, 0, 0, 0, 1, 0, 0,216,216, 1, 1, 1, 0, 0, 0,226,
- 216,216,216,216,216, 0,220,220,220, 0,232,232,220,230,230,230,
- 7, 0, 16, 17, 17, 33, 17, 49, 17, 17, 84, 97,135,145, 26, 17,
- 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 1, 58, 1, 58, 0, 0, 0, 0, 0, 88, 63, 89, 0, 0,115, 0,
+ 0, 0, 55, 0, 0, 0, 0,115, 0, 0, 0, 0, 61, 0, 0, 0,
+ 0, 79, 0, 61, 0, 0, 0, 0, 56, 0, 89, 80, 0, 0, 79, 0,
+ 0, 0, 8, 91, 0, 0, 1, 87, 0, 0,116, 0, 0, 0, 0, 0,
+ 0,117, 0,118,119,120,121, 0,104, 4,122, 49, 23, 0, 0, 0,
+ 38, 50, 38, 58, 0, 0, 1, 87, 1, 1, 1, 1, 39, 1, 48,105,
+ 87, 0, 0, 0, 0, 1, 0, 0, 0,123, 0, 0, 0,112, 4,122,
+ 0, 0, 0, 1,124, 0, 0, 0, 0, 0,230,230,230,230,230,232,
+ 220,220,220,220,232,216,220,220,220,220,220,202,202,220,220,220,
+ 220,202,202,220,220,220, 1, 1, 1, 1, 1,220,220,220,220,230,
+ 230,230,230,240,230,220,220,220,230,230,230,220,220, 0,230,230,
+ 230,220,220,220,220,230,232,220,220,230,233,234,234,233,234,234,
+ 233,230, 0, 0, 0,230, 0,220,230,230,230,230,220,230,230,230,
+ 222,220,230,230,220,220,230,222,228,230, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 19, 20, 21, 22, 0, 23, 0, 24, 25, 0,230,220,
+ 0, 18, 30, 31, 32, 0, 0, 0, 0, 27, 28, 29, 30, 31, 32, 33,
+ 34,230,230,220,220,230,220,230,230,220, 35, 0, 0, 0, 0, 0,
+ 230,230,230, 0, 0,230,230, 0,220,230,230,220, 0, 0, 0, 36,
+ 0, 0,230,220,230,230,220,220,230,220,220,230,220,230,220,230,
+ 230, 0, 0,220, 0, 0,230,230, 0,230, 0,230,230,230,230,230,
+ 0, 0, 0,220,220,220,230,220,220,220,230,230, 0,220, 27, 28,
+ 29,230, 7, 0, 0, 0, 0, 9, 0, 0, 0,230,220,230,230, 0,
+ 0, 0, 0, 0,230, 0, 0, 84, 91, 0, 0, 0, 0, 9, 9, 0,
+ 0, 0, 0, 0, 9, 0,103,103, 9, 0,107,107,107,107,118,118,
+ 9, 0,122,122,122,122,220,220, 0, 0, 0,220, 0,220, 0,216,
+ 0, 0, 0,129,130, 0,132, 0, 0, 0, 0, 0,130,130,130,130,
+ 0, 0,130, 0,230,230, 9, 0,230,230, 0, 0,220, 0, 0, 0,
+ 0, 7, 0, 9, 9, 0, 9, 9, 0, 0, 0,230, 0, 0, 0,228,
+ 0, 0, 0,222,230,220,220, 0, 0, 0,230, 0, 0,220,230,220,
+ 0,220,230,230,230, 0, 0, 0, 9, 9, 0, 0, 7, 0,230, 0,
+ 1, 1, 1, 0, 0, 0,230,234,214,220,202,230,230,230,230,230,
+ 232,228,228,220,218,230,233,220,230,220,230,230, 1, 1, 1, 1,
+ 1,230, 0, 1, 1,230,220,230, 1, 1, 0, 0,218,228,232,222,
+ 224,224, 0, 8, 8, 0, 0, 0, 0,220,230, 0,230,230,220, 0,
+ 0,230, 0, 0, 26, 0, 0,220, 0,230,230, 1,220, 0, 0,230,
+ 220, 0, 0, 0,220,220, 0, 0,230,220, 0, 9, 7, 0, 0, 7,
+ 9, 0, 0, 0, 9, 7, 6, 6, 0, 0, 0, 0, 1, 0, 0,216,
+ 216, 1, 1, 1, 0, 0, 0,226,216,216,216,216,216, 0,220,220,
+ 220, 0,232,232,220,230,230,230, 7, 0, 16, 17, 17, 33, 17, 49,
+ 17, 17, 84, 97,135,145, 26, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17,177, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 5, 3, 3, 3,
- 3, 3, 6, 7, 8, 3, 3, 3, 3, 3, 9, 10, 11, 12, 13, 3,
- 3, 3, 3, 3, 3, 3, 3, 14, 3, 15, 3, 3, 3, 3, 3, 3,
- 16, 17, 18, 19, 20, 21, 3, 3, 3, 22, 23, 24, 3, 3, 3, 3,
- 3, 3, 25, 3, 3, 3, 3, 3, 3, 3, 3, 26, 3, 3, 27, 28,
- 0, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0,
- 0, 3, 0, 0, 0, 0, 0, 4, 0, 5, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 0, 0,
- 0, 9, 0, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 0,
- 0, 14, 15, 16, 6, 0, 17, 18, 19, 19, 19, 20, 21, 22, 23, 24,
- 19, 25, 0, 26, 27, 19, 19, 28, 29, 30, 0, 31, 0, 0, 0, 8,
- 0, 0, 0, 0, 0, 0, 0, 19, 28, 0, 32, 33, 9, 34, 35, 19,
- 0, 0, 36, 37, 38, 39, 40, 19, 0, 41, 42, 43, 44, 31, 0, 1,
- 45, 42, 0, 0, 0, 0, 0, 32, 14, 14, 0, 0, 0, 0, 14, 0,
- 0, 46, 47, 47, 47, 47, 48, 49, 47, 47, 47, 47, 50, 51, 52, 53,
- 43, 21, 0, 0, 0, 0, 0, 0, 0, 54, 6, 55, 0, 14, 19, 1,
- 0, 0, 0, 0, 56, 57, 0, 0, 0, 0, 0, 19, 58, 31, 0, 0,
- 0, 0, 0, 0, 0, 59, 14, 0, 0, 0, 0, 1, 0, 2, 0, 0,
- 0, 3, 0, 0, 0, 60, 61, 0, 0, 0, 0, 0, 0, 0, 1, 0,
- 0, 0, 0, 0, 2, 3, 0, 4, 5, 0, 0, 6, 0, 0, 0, 7,
- 0, 0, 0, 1, 1, 0, 0, 8, 9, 0, 8, 9, 0, 0, 0, 0,
- 8, 9, 10, 11, 12, 0, 0, 0, 13, 0, 0, 0, 0, 14, 15, 16,
- 17, 0, 0, 0, 1, 0, 0, 18, 19, 0, 0, 0, 20, 0, 0, 0,
- 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 8, 21, 9,
- 0, 0, 22, 0, 0, 0, 0, 1, 0, 23, 24, 25, 0, 0, 26, 0,
- 0, 0, 8, 21, 27, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 28,
- 29, 30, 0, 31, 32, 20, 1, 1, 0, 0, 0, 8, 21, 9, 1, 4,
- 5, 0, 0, 0, 33, 9, 0, 1, 1, 1, 0, 8, 21, 21, 21, 21,
- 34, 1, 35, 21, 21, 21, 9, 36, 0, 0, 37, 38, 1, 0, 39, 0,
- 0, 0, 1, 0, 1, 0, 0, 0, 0, 8, 21, 9, 1, 0, 0, 0,
- 40, 0, 8, 21, 21, 21, 21, 21, 21, 21, 21, 9, 0, 1, 1, 1,
- 1, 8, 21, 21, 21, 9, 0, 0, 0, 41, 0, 42, 43, 0, 0, 0,
- 1, 44, 0, 0, 0, 45, 8, 9, 1, 0, 0, 0, 8, 21, 21, 21,
- 9, 0, 1, 0, 1, 1, 8, 21, 21, 9, 0, 4, 5, 8, 9, 1,
- 0, 0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
- 13, 14, 3, 3, 3, 3, 3, 3, 3, 15, 3, 16, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17,177, 0, 1, 2, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3,
+ 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 6, 7, 8, 3, 3, 3,
+ 3, 3, 9, 10, 11, 12, 13, 3, 3, 3, 3, 3, 3, 3, 3, 14,
+ 3, 15, 3, 3, 3, 3, 3, 3, 16, 17, 18, 19, 20, 21, 3, 3,
+ 3, 22, 23, 24, 3, 3, 3, 3, 3, 3, 25, 3, 3, 3, 3, 3,
+ 3, 3, 3, 26, 3, 3, 27, 28, 0, 1, 0, 0, 0, 0, 0, 1,
+ 0, 2, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 4,
+ 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 8, 9, 0, 0, 0, 0, 0, 0, 9, 0, 9, 0, 0, 0, 0,
+ 0, 0, 0, 10, 11, 12, 13, 0, 0, 14, 15, 16, 6, 0, 17, 18,
+ 19, 19, 19, 20, 21, 22, 23, 24, 19, 25, 0, 26, 27, 19, 19, 28,
+ 29, 30, 0, 31, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 19,
+ 28, 0, 32, 33, 9, 34, 35, 19, 0, 0, 36, 37, 38, 39, 40, 19,
+ 0, 41, 42, 43, 44, 31, 0, 1, 45, 42, 0, 0, 0, 0, 0, 32,
+ 14, 14, 0, 0, 0, 0, 14, 0, 0, 46, 47, 47, 47, 47, 48, 49,
+ 47, 47, 47, 47, 50, 51, 52, 53, 43, 21, 0, 0, 0, 0, 0, 0,
+ 0, 54, 6, 55, 0, 14, 19, 1, 0, 0, 0, 0, 56, 57, 0, 0,
+ 0, 0, 0, 19, 58, 31, 0, 0, 0, 0, 0, 0, 0, 59, 14, 0,
+ 0, 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 60, 61, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 3, 0, 4,
+ 5, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 1, 0, 0, 8,
+ 9, 0, 8, 9, 0, 0, 0, 0, 8, 9, 10, 11, 12, 0, 0, 0,
+ 13, 0, 0, 0, 0, 14, 15, 16, 17, 0, 0, 0, 1, 0, 0, 18,
+ 19, 0, 0, 0, 20, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1,
+ 1, 1, 1, 1, 0, 8, 21, 9, 0, 0, 22, 0, 0, 0, 0, 1,
+ 0, 23, 24, 25, 0, 0, 26, 0, 0, 0, 8, 21, 27, 0, 1, 0,
+ 0, 1, 1, 1, 1, 0, 1, 28, 29, 30, 0, 31, 32, 20, 1, 1,
+ 0, 0, 0, 8, 21, 9, 1, 4, 5, 0, 0, 0, 33, 9, 0, 1,
+ 1, 1, 0, 8, 21, 21, 21, 21, 34, 1, 35, 21, 21, 21, 9, 36,
+ 0, 0, 37, 38, 1, 0, 39, 0, 0, 0, 1, 0, 1, 0, 0, 0,
+ 0, 8, 21, 9, 1, 0, 0, 0, 40, 0, 8, 21, 21, 21, 21, 21,
+ 21, 21, 21, 9, 0, 1, 1, 1, 1, 8, 21, 21, 21, 9, 0, 0,
+ 0, 41, 0, 42, 43, 0, 0, 0, 1, 44, 0, 0, 0, 45, 8, 9,
+ 1, 0, 0, 0, 8, 21, 21, 21, 9, 0, 1, 0, 1, 1, 8, 21,
+ 21, 9, 0, 4, 5, 8, 9, 1, 0, 0, 0, 1, 2, 3, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 3, 3, 3, 3, 3, 3,
+ 3, 15, 3, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
- 17, 17, 18, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
- 12, 13, 14, 15, 16, 17, 17, 17, 18, 17, 19, 20, 21, 22, 23, 23,
- 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 23, 23, 23, 23, 23,
- 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
- 23, 23, 23, 23, 25, 25, 26, 27, 28, 29, 30, 30, 30, 30, 30, 30,
- 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
- 48, 49, 50, 51, 52, 52, 53, 31, 31, 31, 31, 54, 55, 55, 56, 31,
- 31, 31, 31, 31, 31, 31, 57, 58, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 59, 60, 31, 61, 62, 62, 62, 62,
- 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 64, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 65, 66, 67, 31, 31,
- 31, 31, 68, 31, 31, 31, 31, 31, 31, 31, 31, 69, 70, 71, 17, 17,
- 72, 73, 31, 74, 75, 76, 77, 78, 79, 31, 80, 81, 17, 82, 17, 17,
- 17, 17, 31, 31, 23, 23, 23, 23, 23, 23, 23, 83, 31, 31, 31, 31,
- 23, 83, 31, 31, 23, 23, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 0, 0, 1, 2, 3,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 17, 17,
+ 18, 17, 19, 20, 21, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 24, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 25, 25, 26, 27,
+ 28, 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 52, 53, 31,
+ 31, 31, 31, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 57,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 58, 31, 31, 31,
+ 59, 60, 61, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 64, 65, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 66, 67, 68, 31, 31, 31, 31, 69, 31, 31, 31, 31, 31,
+ 31, 31, 17, 70, 71, 72, 17, 17, 73, 74, 31, 75, 76, 77, 78, 79,
+ 80, 31, 81, 82, 17, 83, 17, 17, 17, 17, 31, 31, 23, 23, 23, 23,
+ 23, 23, 23, 84, 31, 31, 31, 31, 23, 84, 31, 31, 23, 23, 31, 31,
31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
- 31, 31, 31, 31, 84, 0, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3,
- 4, 5, 6, 7, 0, 1, 2, 3, 4, 4, 4, 4, 4, 4, 5, 6,
- 7, 8, 9, 10, 11, 11, 12, 11, 13, 14, 15, 16, 17, 18, 19, 20,
- 21, 22, 23, 24, 25, 26, 19, 27, 28, 29, 30, 30, 31, 31, 32, 32,
- 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 40, 41, 41,
- 42, 42, 42, 43, 44, 44, 45, 46, 47, 47, 47, 47, 48, 48, 48, 48,
- 48, 48, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 53,
- 54, 55, 56, 56, 57, 58, 59, 51, 60, 61, 62, 63, 64, 65, 66, 7,
- 67, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 7, 4, 4, 4, 4,
- 77, 77, 77, 77, 78, 79, 80, 81, 82, 83, 84, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 85, 85, 85, 85, 0, 0, 0, 0, 86, 87, 88, 88,
- 89, 90, 48, 91, 0, 0, 92, 92, 92, 92, 92, 93, 94, 95, 96, 97,
- 98, 47, 99,100,101,102, 0,103,104,105, 0, 0, 92, 92, 92, 92,
- 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 0,106,106,106,106,
- 106,106,106,106,106,106,106,107,108,108,108,108,108, 11,109,110,
- 111, 4,112, 4,113,114,115,116,117,118,119,120,121,122,123,124,
- 125,126, 50,127, 47, 47, 47, 47, 47, 47, 47, 47,128,128,128,128,
- 128,128,128,128,128,128,128,128, 92, 92, 92, 92, 92, 92, 92, 92,
- 129,130, 19, 19, 19, 19, 19, 19,131, 19, 19, 19,132,133, 19,134,
- 135,136,137,101,138,138,138,138, 0, 77,139,140,128,128,141,142,
- 143,144,145,146,147,148,149,150,151,152,153,153,154,154,154,154,
- 154,154, 4, 4,155,156,157,158,159,160,161,162,163,164,165,166,
- 167,168,169,169,170,170,171,171,172,172,128,128, 19, 19,173,174,
- 175,176,177,178,179,179,180,181,182,183,184,185,186,186,187,188,
- 189,190,128,128,191,191,192,192,128,128,193,193,194,195,196,196,
- 197,197,128,128,198,198,199,199,200,200,201,201,202,203,204,205,
- 28, 28,128,128,206,207,208,208,209,210,211,211,128,128,212,212,
- 213,213,214, 34,215,215,215,215,215,215,215,215,215,215,215,215,
- 215,215,128,128,128,128,128,128,128,128,216,216,217,217,217,217,
- 217,217,217,217,217,217,128,128,128,128,128,128,218,218,218,218,
- 218,218,218,218,218,218,128,128,128,128,128,128,110,110,110,110,
- 110,110,110,110,110,219,220,221,222,222,222,222,223,223,223,223,
- 224,224,224,225,226,226,226,226,226,226,226,226,226,226,226,226,
- 227,227,227,227,227,227,227,227,226,226,128,128,128,128,128,128,
- 128,128,104,104,228,229,229,229,230,231,232,232,232,232,232,232,
- 128,128,128,128,233,233,234, 0,128,128,128,128,128,128,128,128,
- 7,235, 0, 0, 0, 0, 0, 0, 0,236,237, 0, 77, 77, 0, 0,
- 0, 0,128,128,238,238,238,238,238,238,238,238,238,238,238,238,
- 128,128,128,128,128,128,128,128, 4, 4,128,128,239, 11, 11, 11,
- 240,240,128,128,128,128,241,242,128,128,128,128,128,128,243,243,
- 128,128,128,128,128,128,128,128,128,128, 48, 48,244,244,244,244,
- 245,245,128,128, 0, 0, 0, 0, 0, 0,128,128, 19, 19, 19, 19,
- 128,128,128,128,246, 0,128,128, 0, 0, 0, 0, 92, 92,128,128,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 85, 0, 0, 1,
+ 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3,
+ 4, 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10, 11, 11, 12, 11,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 19, 27,
+ 28, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36,
+ 37, 37, 38, 38, 39, 40, 41, 41, 42, 42, 42, 43, 44, 44, 45, 46,
+ 47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 49, 50, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, 51,
+ 60, 61, 62, 63, 64, 65, 66, 7, 67, 67, 68, 69, 70, 71, 72, 73,
+ 74, 75, 76, 7, 4, 4, 4, 4, 77, 77, 77, 77, 78, 79, 80, 81,
+ 82, 83, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85,
+ 0, 0, 0, 0, 86, 87, 88, 88, 89, 90, 48, 91, 0, 0, 92, 92,
+ 92, 92, 92, 93, 94, 95, 96, 97, 98, 47, 99,100,101,102, 0,103,
+ 104,105, 0, 0, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
+ 92, 92, 92, 0,106,106,106,106,106,106,106,106,106,106,106,107,
+ 108,108,108,108,108, 11,109,110,111, 4,112, 4,113,114,115,116,
+ 117,118,119,120,121,122,123,124,125,126, 50,127, 47, 47, 47, 47,
+ 47, 47, 47, 47,128,128,128,128,128,128,128,128,128,128,128,128,
+ 92, 92, 92, 92, 92, 92, 92, 92,129,130, 19, 19, 19, 19, 19, 19,
+ 131, 19, 19, 19,132,133, 19,134,135,136,137,101,138,138,138,138,
+ 0, 77,139,140,128,128,141,142,143,144,145,146,147,148,149,150,
+ 151,152,153,154,155,155,155,155,155,155, 4, 4,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,170,171,171,172,172,
+ 173,174,174,174, 19, 19,175,176,177,178,179,180,181,181,182,183,
+ 184,185,186,187,188,188,189,190,191,192,193,193,194,194,195,195,
+ 128,128,196,196,197,198,199,200,201,201,128,128,202,202,203,203,
+ 204,204,205,205,206,207,208,209, 28, 28,210,210,211,212,213,213,
+ 214,215,216,216,128,128,217,217,218,218,219, 34,220,220,220,220,
+ 220,220,220,220,220,220,220,220,220,220,128,128,128,128,128,128,
+ 128,128,221,221,222,222,222,222,222,222,222,222,223,223,223,223,
+ 223,223,223,223,223,223,128,128,128,128,128,128,128,128,128,128,
+ 224,224,128,128,110,110,110,110,110,110,110,110,110,225,226,227,
+ 228,228,228,228,128,128,128,128,229,229,128,128,230,230,230,230,
+ 231,231,231,232,233,233,233,233,233,233,233,233,233,233,233,233,
+ 234,234,234,234,234,234,234,234,233,233,128,128,128,128,128,128,
+ 128,128,104,104,235,236,236,236,237,238,239,239,239,239,239,239,
+ 128,128,128,128,240,240,241, 0,128,128,128,128, 0, 0, 0, 0,
+ 7,242, 0, 0, 0, 0, 0, 0, 0,243,244, 0, 77, 77, 0, 0,
+ 0, 0,128,128,245,245,245,245,245,245,245,245,245,245,245,245,
+ 128,128,128,128,128,128,128,128, 4, 4,128,128,246, 11, 11, 11,
+ 247,247,128,128,128,128,248,249,128,128,128,128,128,128,250,250,
+ 128,128,251,251,128,128,128,128,128,128, 48, 48,252,252,252,252,
+ 253,253,128,128, 0, 0, 0, 0, 0, 0,128,128, 19, 19, 19, 19,
+ 128,128,128,128,254, 0,128,128, 0, 0, 0, 0, 92, 92,128,128,
128,128,128,128, 0, 0,128,128, 7, 7, 7, 7, 0, 0, 0, 0,
1, 2, 1, 2, 0, 0, 3, 3, 4, 5, 4, 5, 4, 4, 4, 4,
4, 4, 4, 6, 0, 0, 7, 0, 8, 8, 8, 8, 8, 8, 8, 9,
@@ -5056,30 +5278,32 @@ _hb_ucd_u8[13386] =
137,137,138,138,138,138,139, 0,140,140,140,141,141,142,142,142,
143,143,144,144,144,144,144,144,145,145,145,145,145,146,146,146,
147,147,147,148,148,148,148,148,149,149,149,150,150,150,150,151,
- 151,151,151,151,152,152,152,152,153,153,153,153,154,154,155,155,
- 156,156,156,156,156,156,157,157,158,158,159,159,159,159,159,159,
- 160,160,161,161,161,161,161,161,162,162,162,162,162,162,163,163,
- 164,164,164,164,165,165,165,165,166,166,166,166,167,167,168,168,
- 169,169,169,169,170,170,170,170,171,171,171,171,172,172,172,172,
- 173,173,173,173,173,173,173,174,175,175,175,176,176,176,176,177,
- 177,177,177,178,178,178,179,179,180,180,180,180,181,181,181,181,
- 181,182,182,182,183,183,183,183,183,184,184,184,185,185,185,185,
- 185,185,186, 43,187,187,187,187,188,188,188,189,189,189,189,189,
- 190,190,190,191,190,190,190,190,192,192,192,192,193,193,193,193,
- 194,194,194,194,195,195,195,195,195,195, 66, 66,196,196,196,196,
- 197,197,197,197,198,198,198,198,199,199,199,199,200,200,200,200,
- 201,201,201,201,202,202,202,202,202,203,203,203,203,203,203, 55,
- 204,204,204,204,205,205,205,205,205,205,205,206,206,206,206,206,
- 207,207,207,207,207,207,208,208,208,208,208,208,209,209,209,209,
- 210,210,210,210,110,110,110,110,211,211,211,211,212,212,212,212,
- 213,213,213,213,214,214,214,214,215,215,215,216,216,216,216,216,
- 216,217,217,217,218,218,218,218,219,219,219,219,220,220,220,220,
- 220,220,221, 94,222,222,222,222,223,223,223,223,224, 99, 99, 99,
- 99, 99, 99, 99, 99, 99,102,225, 99,226,102,227,227,227,227,227,
- 228,228,228,228,228,228, 0, 0, 8, 0, 0, 0, 0, 0,229,230,
- 231, 0,232, 0,233,233,233,233, 91, 91, 91, 13,234,234,234,234,
- 235,235,235,235,236,236,236,236,237,237,237,237,238,238,238,238,
- 239,239,239,239,240, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2,
+ 151,151,151,151,152,152,152,152,153,153,153,153,154,154,154,154,
+ 155,155,156,156,157,157,157,157,157,157,158,158,159,159,160,160,
+ 160,160,160,160,161,161,162,162,162,162,162,162,163,163,163,163,
+ 163,163,164,164,165,165,165,165,166,166,166,166,167,167,167,167,
+ 168,168,169,169,170,170,170,170,171,171,171,171,172,172,172,172,
+ 173,173,173,173,174,174,174,174,175,175,175,175,176, 21, 21, 21,
+ 177,177,177,178,178,178,178,179,179,179,179,180,180,180,181,181,
+ 182,182,182,182,183,183,183,183,183,184,184,184,185,185,185,185,
+ 185,186,186,186,187,187,187,187,187,187,188, 43,189,189,189,189,
+ 190,190,190,191,191,191,191,191,192,192,192,193,192,192,192,192,
+ 194,194,194,194,195,195,195,195,196,196,196,196,197,197,197,197,
+ 198,198,198,198,198,198, 66, 66,199,199,199,199,199, 49, 49, 49,
+ 200,200,200,200,201,201,201,201,202,202,202,202,203,203,203,203,
+ 204,204,204,204,205,205,205,205,205,206,206,206,206,206,206, 55,
+ 207,207,207,207,208,208,208,208,209,209,209,209,209,209,209,210,
+ 210,210,210,210,211,211,211,211,211,211,212,212,212,212,212,212,
+ 213,213,213,213,214,214,214,214,110,110,110,110,215,215,215,215,
+ 216,216,216,216,217,217,217,217,218,218,218,218,219,219,219,219,
+ 220,220,220,221,221,221,221,221,221,222,222,222,223,223,223,223,
+ 224,224,224,224,225,225,225,225,226,226,226,226,226,226,227, 94,
+ 228,228,228,228,229,229,229,229,230, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99,102,231, 99,232,102,233,233,233,233,233,234,234,234,234,
+ 234,234, 0, 0, 8, 0, 0, 0, 0, 0,235,236,237, 0,238, 0,
+ 239,239,239,239, 91, 91, 91, 13,240,240,240,240,241,241,241,241,
+ 242,242,242,242,243,243,243,243,244,244,244,244,245,245,245,245,
+ 246,246,246,246,247, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2,
2, 2, 3, 0, 0, 0, 4, 0, 2, 2, 2, 2, 2, 3, 2, 2,
2, 2, 5, 0, 2, 5, 6, 0, 7, 7, 7, 7, 8, 9, 8, 10,
8, 11, 8, 8, 8, 8, 8, 8, 12, 13, 13, 13, 14, 14, 14, 14,
@@ -5123,98 +5347,102 @@ _hb_ucd_u8[13386] =
163,163,163,163,164,164,164,164,165,165,165,165,166,166,166,166,
167,167,167,167,168,168,168,168,169,169,169,169,170,170,170,170,
171,171,171,171,172,172,172,172,173,173,173,173,174,174,174,174,
- 174,174,174,175,176,176,176,176,177,177,177,177,178,178,178,178,
+ 175,175,175,175,176,176,176,176,177, 20, 20, 20,178,178,178,178,
179,179,179,179,180,180,180,180,181,181,181,181,182,182,182,182,
183,183,183,183,184,184,184,184,185,185,185,185,186,186,186,186,
- 187, 45, 45, 45,188,188,188,188,189,189,189,189,190,190,190,190,
- 191,191,191,191,191,191,192,191,193,193,193,193,194,194,194,194,
+ 187,187,187,187,188,188,188,188,189, 45, 45, 45,190,190,190,190,
+ 191,191,191,191,192,192,192,192,193,193,193,193,193,193,194,193,
195,195,195,195,196,196,196,196,197,197,197,197,198,198,198,198,
199,199,199,199,200,200,200,200,201,201,201,201,202,202,202,202,
203,203,203,203,204,204,204,204,205,205,205,205,206,206,206,206,
207,207,207,207,208,208,208,208,209,209,209,209,210,210,210,210,
211,211,211,211,212,212,212,212,213,213,213,213,214,214,214,214,
215,215,215,215,216,216,216,216,217,217,217,217,218,218,218,218,
- 219,219,219,219,220,220,220,220,221,221,221,221,222,223,223,223,
- 224,224,224,224,223,223,223,223,225,106,106,106,226,106,106,106,
- 106,227,109,109,228,228,228,228,229,229,229,229, 0,230, 86, 0,
- 0, 0,230, 7, 82,138, 7, 0, 0, 0,231, 86,232,232,232,232,
- 233,233,233,233,234,234,234,234,235,235,235,235,236,236,236,236,
- 237,237,237,237,238,238,238,238,239, 0, 0, 0, 0, 0, 0, 0,
- 0, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 0, 0, 0, 19, 0,
- 19, 0, 0, 0, 0, 0, 26, 26, 1, 1, 1, 1, 9, 9, 9, 9,
- 0, 9, 9, 9, 9, 9, 0, 9, 9, 0, 9, 0, 9, 9, 55, 55,
- 55, 55, 55, 55, 6, 6, 6, 6, 6, 1, 1, 6, 6, 4, 4, 4,
- 4, 4, 4, 4, 4, 14, 14, 14, 14, 14, 14, 14, 3, 3, 3, 3,
- 3, 0, 3, 3, 0, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 1,
- 1, 1, 3, 3, 1, 3, 3, 3, 37, 37, 37, 37, 38, 38, 38, 38,
- 64, 64, 64, 64, 90, 90, 90, 90, 95, 95, 95, 95, 3, 3, 0, 3,
- 7, 7, 7, 7, 7, 1, 1, 1, 1, 7, 7, 7, 0, 0, 7, 7,
- 5, 5, 5, 5, 11, 11, 11, 11, 10, 10, 10, 10, 21, 21, 21, 21,
- 22, 22, 22, 22, 23, 23, 23, 23, 16, 16, 16, 16, 20, 20, 20, 20,
- 36, 36, 36, 36, 24, 24, 24, 24, 24, 24, 24, 0, 18, 18, 18, 18,
- 25, 25, 25, 25, 25, 0, 0, 0, 0, 25, 25, 25, 33, 33, 33, 33,
- 8, 8, 8, 8, 8, 8, 8, 0, 12, 12, 12, 12, 30, 30, 30, 30,
- 29, 29, 29, 29, 28, 28, 28, 28, 34, 34, 34, 34, 35, 35, 35, 35,
- 35, 35, 35, 0, 0, 0, 35, 35, 45, 45, 45, 45, 44, 44, 44, 44,
- 44, 0, 0, 0, 43, 43, 43, 43, 46, 46, 46, 46, 31, 31, 31, 31,
- 32, 32, 0, 0, 32, 0, 32, 32, 32, 32, 32, 32, 48, 48, 48, 48,
- 52, 52, 52, 52, 58, 58, 58, 58, 54, 54, 54, 54, 91, 91, 91, 91,
- 62, 62, 62, 62, 76, 76, 76, 76, 93, 93, 93, 93, 70, 70, 70, 70,
- 73, 73, 73, 73, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0,
- 0, 1, 0, 0, 1, 1, 0, 0, 19, 19, 9, 9, 9, 9, 9, 6,
- 19, 9, 9, 9, 9, 9, 19, 19, 9, 9, 9, 19, 6, 19, 19, 19,
- 19, 19, 19, 9, 0, 0, 0, 19, 0, 0, 9, 0, 0, 0, 19, 19,
- 27, 27, 27, 27, 56, 56, 56, 56, 61, 61, 61, 61, 13, 13, 13, 13,
- 0, 13, 0, 13, 0, 13, 13, 13, 13, 13, 1, 1, 1, 1, 12, 12,
- 0, 15, 15, 15, 15, 15, 15, 15, 15, 1, 1, 0, 0, 17, 17, 17,
- 17, 17, 17, 17, 17, 17, 17, 0, 26, 26, 26, 26, 26, 12, 12, 12,
- 12, 12, 12, 0, 39, 39, 39, 39, 86, 86, 86, 86, 77, 77, 77, 77,
- 79, 79, 79, 79, 60, 60, 60, 60, 65, 65, 65, 65, 75, 75, 75, 75,
- 69, 69, 69, 69, 69, 69, 0, 69, 74, 74, 74, 74, 84, 84, 84, 84,
- 84, 84, 84, 0, 68, 68, 68, 68, 92, 92, 92, 92, 87, 87, 87, 87,
- 19, 9, 19, 19, 19, 19, 0, 0, 2, 2, 2, 2, 19, 19, 19, 4,
- 3, 3, 0, 0, 1, 1, 6, 6, 0, 0, 17, 17, 17, 17, 0, 0,
- 49, 49, 49, 49, 0, 1, 1, 1, 71, 71, 71, 71, 67, 67, 67, 67,
- 42, 42, 42, 42, 41, 41, 41, 41,118,118,118,118, 53, 53, 53, 53,
- 59, 59, 59, 59, 40, 40, 40, 40, 51, 51, 51, 51, 50, 50, 50, 50,
- 135,135,135,135,106,106,106,106,104,104,104,104,161,161,161,161,
+ 219,219,219,219,220,220,220,220,221,221,221,221,222,222,222,222,
+ 223,223,223,223,224,224,224,224,225,225,225,225,226,226,226,226,
+ 227,227,227,227,228,229,229,229,230,230,230,230,229,229,229,229,
+ 231,106,106,106,232,106,106,106,106,233,109,109,234,234,234,234,
+ 235,235,235,235, 0,236, 86, 0, 0, 0,236, 7, 82,138, 7, 0,
+ 0, 0,237, 86,238,238,238,238,239,239,239,239,240,240,240,240,
+ 241,241,241,241,242,242,242,242,243,243,243,243,244,244,244,244,
+ 245,245,245,245,246, 0, 0, 0, 0, 0, 0, 0, 0, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 0, 0, 0, 19, 0, 19, 0, 0, 0,
+ 0, 0, 26, 26, 1, 1, 1, 1, 9, 9, 9, 9, 0, 9, 9, 9,
+ 9, 9, 0, 9, 9, 0, 9, 0, 9, 9, 55, 55, 55, 55, 55, 55,
+ 6, 6, 6, 6, 6, 1, 1, 6, 6, 4, 4, 4, 4, 4, 4, 4,
+ 4, 14, 14, 14, 14, 14, 14, 14, 3, 3, 3, 3, 3, 0, 3, 3,
+ 0, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 1, 1, 1, 3, 3,
+ 1, 3, 3, 3, 37, 37, 37, 37, 38, 38, 38, 38, 64, 64, 64, 64,
+ 90, 90, 90, 90, 95, 95, 95, 95, 3, 3, 0, 3, 7, 7, 7, 7,
+ 7, 1, 1, 1, 1, 7, 7, 7, 0, 0, 7, 7, 5, 5, 5, 5,
+ 11, 11, 11, 11, 10, 10, 10, 10, 21, 21, 21, 21, 22, 22, 22, 22,
+ 23, 23, 23, 23, 16, 16, 16, 16, 20, 20, 20, 20, 36, 36, 36, 36,
+ 24, 24, 24, 24, 24, 24, 24, 0, 18, 18, 18, 18, 25, 25, 25, 25,
+ 25, 0, 0, 0, 0, 25, 25, 25, 33, 33, 33, 33, 8, 8, 8, 8,
+ 8, 8, 8, 0, 12, 12, 12, 12, 30, 30, 30, 30, 29, 29, 29, 29,
+ 28, 28, 28, 28, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35, 0,
+ 0, 0, 35, 35, 45, 45, 45, 45, 44, 44, 44, 44, 44, 0, 0, 0,
+ 43, 43, 43, 43, 46, 46, 46, 46, 31, 31, 31, 31, 32, 32, 0, 0,
+ 32, 0, 32, 32, 32, 32, 32, 32, 48, 48, 48, 48, 52, 52, 52, 52,
+ 58, 58, 58, 58, 54, 54, 54, 54, 91, 91, 91, 91, 62, 62, 62, 62,
+ 76, 76, 76, 76, 93, 93, 93, 93, 70, 70, 70, 70, 73, 73, 73, 73,
+ 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0,
+ 1, 1, 0, 0, 19, 19, 9, 9, 9, 9, 9, 6, 19, 9, 9, 9,
+ 9, 9, 19, 19, 9, 9, 9, 19, 6, 19, 19, 19, 19, 19, 19, 9,
+ 0, 0, 0, 19, 0, 0, 9, 0, 0, 0, 19, 19, 27, 27, 27, 27,
+ 56, 56, 56, 56, 61, 61, 61, 61, 13, 13, 13, 13, 0, 13, 0, 13,
+ 0, 13, 13, 13, 13, 13, 1, 1, 1, 1, 12, 12, 0, 15, 15, 15,
+ 15, 15, 15, 15, 15, 1, 1, 0, 0, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 0, 26, 26, 26, 26, 26, 12, 12, 12, 12, 12, 12, 0,
+ 39, 39, 39, 39, 86, 86, 86, 86, 77, 77, 77, 77, 79, 79, 79, 79,
+ 60, 60, 60, 60, 65, 65, 65, 65, 75, 75, 75, 75, 69, 69, 69, 69,
+ 69, 69, 0, 69, 74, 74, 74, 74, 84, 84, 84, 84, 84, 84, 84, 0,
+ 68, 68, 68, 68, 92, 92, 92, 92, 87, 87, 87, 87, 19, 9, 19, 19,
+ 19, 19, 0, 0, 2, 2, 2, 2, 19, 19, 19, 4, 3, 3, 0, 0,
+ 1, 1, 6, 6, 0, 0, 17, 17, 17, 17, 0, 0, 49, 49, 49, 49,
+ 0, 1, 1, 1, 71, 71, 71, 71, 67, 67, 67, 67, 42, 42, 42, 42,
+ 41, 41, 41, 41,118,118,118,118, 53, 53, 53, 53, 59, 59, 59, 59,
+ 40, 40, 40, 40, 51, 51, 51, 51, 50, 50, 50, 50,135,135,135,135,
+ 106,106,106,106,104,104,104,104,161,161,161,161,170,170,170,170,
110,110,110,110, 47, 47, 47, 47, 81, 81, 81, 81,120,120,120,120,
116,116,116,116,128,128,128,128, 66, 66, 66, 66, 72, 72, 72, 72,
98, 98, 98, 98, 97, 97, 97, 97, 57, 57, 57, 57, 88, 88, 88, 88,
117,117,117,117,112,112,112,112, 78, 78, 78, 78, 83, 83, 83, 83,
82, 82, 82, 82,122,122,122,122, 89, 89, 89, 89,130,130,130,130,
- 144,144,144,144,156,156,156,156,156, 3, 3, 3,147,147,147,147,
- 148,148,148,148,158,158,158,158,153,153,153,153,149,149,149,149,
- 94, 94, 94, 94, 85, 85, 85, 85,101,101,101,101, 96, 96, 96, 96,
- 111,111,111,111,100,100,100,100,100, 36, 36, 36,108,108,108,108,
- 129,129,129,129,109,109,109,109,107,107,107,107,107,107,107, 1,
- 137,137,137,137,124,124,124,124,123,123,123,123,114,114,114,114,
- 102,102,102,102,126,126,126,126,142,142,142,142,125,125,125,125,
- 154,154,154,154,150,150,150,150,141,141,141,141,140,140,140,140,
- 121,121,121,121,133,133,133,133,134,134,134,134,138,138,138,138,
- 143,143,143,143,145,145,145,145,163,163,163,163, 63, 63, 63, 63,
- 157,157,157,157, 80, 80, 80, 80,127,127,127,127,115,115,115,115,
- 159,159,159,159,103,103,103,103,119,119,119,119,146,146,146,146,
- 99, 99, 99, 99,136,139, 13, 13,155,155,155,155,136,136,136,136,
- 17, 15, 15, 15, 17, 17, 15, 15, 15, 17, 17, 17,139,139,139,139,
- 105,105,105,105, 0, 0, 0, 1, 0, 0, 1, 1,131,131,131,131,
- 151,151,151,151,160,160,160,160,152,152,152,152,164,164,164,164,
- 113,113,113,113,132,132,132,132, 15, 0, 0, 0, 0, 1, 2, 3,
- 4, 5, 6, 7, 8, 9, 9, 9, 9, 10, 9, 11, 12, 13, 9, 9,
- 9, 14, 9, 9, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 144,144,144,144,165,165,165,165,156,156,156,156,156,156, 3, 3,
+ 147,147,147,147,148,148,148,148,158,158,158,158,153,153,153,153,
+ 149,149,149,149, 94, 94, 94, 94, 85, 85, 85, 85,101,101,101,101,
+ 96, 96, 96, 96,111,111,111,111,100,100,100,100,100, 36, 36, 36,
+ 108,108,108,108,129,129,129,129,109,109,109,109,107,107,107,107,
+ 107,107,107, 1,171,171,171,171,137,137,137,137,124,124,124,124,
+ 123,123,123,123,114,114,114,114,102,102,102,102,126,126,126,126,
+ 142,142,142,142,125,125,125,125,154,154,154,154,150,150,150,150,
+ 141,141,141,141,140,140,140,140,121,121,121,121,169,169,169,169,
+ 133,133,133,133,134,134,134,134,138,138,138,138,143,143,143,143,
+ 145,145,145,145,163,163,163,163, 63, 63, 63, 63,157,157,157,157,
+ 80, 80, 80, 80,127,127,127,127,166,166,166,166,115,115,115,115,
+ 159,159,159,159,103,103,103,103,119,119,119,119,167,167,167,167,
+ 146,146,146,146, 99, 99, 99, 99,136,139, 13, 13,155,155,155,155,
+ 136,136,136,136, 17, 15, 15, 15, 17, 17, 15, 15, 15, 17, 17, 17,
+ 139,139,139,139,105,105,105,105, 0, 0, 0, 1, 0, 0, 1, 1,
+ 131,131,131,131,151,151,151,151,160,160,160,160,152,152,152,152,
+ 164,164,164,164,168,168,168,168,113,113,113,113,132,132,132,132,
+ 15, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9,
+ 9, 10, 9, 11, 12, 13, 9, 9, 9, 14, 9, 9, 15, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 16, 17, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 18, 19, 20, 9, 21, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 16, 17, 9, 9, 9, 9, 18, 9, 9, 9, 9, 9, 19, 20, 21, 9,
+ 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 23, 9, 9, 9, 9, 9, 24, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 25, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
@@ -5223,60 +5451,66 @@ _hb_ucd_u8[13386] =
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 23, 24, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
- 11, 12, 0, 0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 0,
- 0, 24, 25, 26, 27, 28, 29, 30, 0, 0, 31, 32, 0, 33, 0, 34,
- 0, 35, 0, 0, 0, 0, 36, 37, 38, 39, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 41, 42, 0, 0, 0, 0, 0, 0, 0, 0,
+ 26, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 23, 0, 0, 24, 25, 26, 27, 28, 29, 30,
+ 0, 0, 31, 32, 0, 33, 0, 34, 0, 35, 0, 0, 0, 0, 36, 37,
+ 38, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 42,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 43, 44, 0, 45, 0, 0, 0, 0, 0, 0, 46, 47,
+ 0, 0, 0, 0, 0, 48, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 50, 51, 0, 0, 0, 52, 0, 0, 53, 0,
+ 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0, 55, 0,
+ 0, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 57,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 44, 0, 45,
- 0, 0, 0, 0, 0, 0, 46, 47, 0, 0, 0, 0, 0, 48, 0, 49,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 51,
- 0, 0, 0, 52, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 54, 0,
- 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 56, 0,
- 0, 0, 0, 0, 0, 0, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59,
- 60, 61, 62, 63, 64, 65, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 58, 59, 60, 61, 62, 63, 64, 65, 0, 0,
+ 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, 0, 69, 70, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 72, 73, 74, 75, 76,
- 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92,
- 93, 94, 95, 96, 97, 98, 99,100,101,102,103, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,104, 0, 0, 0,
- 0, 0, 0,105,106, 0,107, 0, 0, 0,108, 0,109, 0,110, 0,
- 111,112,113, 0,114, 0, 0, 0,115, 0, 0, 0,116, 0, 0, 0,
+ 0, 0, 67, 68, 0, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,
+ 101,102,103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,104, 0, 0, 0, 0, 0, 0,105,106, 0,107, 0,
+ 0, 0,108, 0,109, 0,110, 0,111,112,113, 0,114, 0, 0, 0,
+ 115, 0, 0, 0,116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,117,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,117, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,118,119,
- 120,121, 0,122,123,124,125,126, 0,127, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,118,119,120,121, 0,122,123,124,125,126,
+ 0,127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,128,129,130,131,132,133,
- 134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,
- 150,151,152,153,154,155,156,157, 0, 0, 0,158,159,160,161, 0,
+ 0, 0,128,129,130,131,132,133,134,135,136,137,138,139,140,141,
+ 142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,
+ 0, 0, 0,158,159,160,161, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0,162,163, 0, 0, 0, 0, 0, 0, 0,164, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,162, 0,
+ 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,164,165, 0, 0, 0,
+ 0, 0, 0, 0,166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,165, 0,
+ 0, 0, 0, 0, 0, 0,167, 0, 0, 0,168,169, 0, 0,170, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,171, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,172, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0,167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,168, 0, 0,
+ 0, 0, 0, 0, 0,173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,169,
- 170, 0, 0, 0, 0,171,172, 0, 0, 0,173,174,175,176,177,178,
- 179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,
- 195,196,197,198,199,200,201,202,203,204,205,206, 0, 0, 0, 0,
+ 0, 0, 0, 0,174, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 2, 3, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0,175, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,176,177, 0, 0, 0, 0,178,179, 0,
+ 0, 0,180,181,182,183,184,185,186,187,188,189,190,191,192,193,
+ 194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,
+ 210,211,212,213, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
+ 3, 4,
};
static const uint16_t
-_hb_ucd_u16[4920] =
+_hb_ucd_u16[5080] =
{
0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 7, 8, 9, 10, 11, 12,
13, 13, 13, 14, 15, 13, 13, 16, 17, 18, 19, 20, 21, 22, 13, 23,
@@ -5303,82 +5537,85 @@ _hb_ucd_u16[4920] =
47, 47, 165, 166, 167, 47, 47, 47, 47, 47, 47, 47, 47, 168, 146, 146,
47, 169, 47, 47, 47, 170, 171, 172, 160, 160, 173, 174, 32, 32, 32, 32,
175, 47, 47, 176, 177, 122, 178, 179, 180, 47, 181, 61, 47, 47, 182, 183,
- 47, 47, 184, 185, 186, 61, 47, 187, 11, 9, 9, 9, 66, 188, 189, 190,
- 11, 11, 191, 27, 27, 27, 192, 193, 11, 194, 27, 27, 32, 32, 32, 32,
- 13, 13, 13, 13, 13, 13, 13, 13, 13, 195, 13, 13, 13, 13, 13, 13,
- 196, 196, 196, 196, 196, 197, 196, 11, 198, 198, 198, 199, 200, 201, 201, 200,
- 202, 203, 204, 205, 206, 207, 208, 209, 210, 27, 211, 211, 211, 212, 213, 32,
- 214, 215, 216, 217, 218, 145, 219, 219, 220, 221, 222, 146, 223, 224, 146, 225,
- 226, 226, 226, 226, 226, 226, 226, 226, 227, 146, 228, 146, 146, 146, 146, 229,
- 146, 230, 226, 231, 146, 232, 233, 146, 146, 146, 146, 146, 146, 146, 145, 145,
- 145, 234, 146, 146, 146, 146, 235, 145, 146, 146, 146, 146, 146, 146, 146, 146,
- 146, 146, 146, 236, 237, 146, 146, 238, 146, 146, 146, 146, 146, 146, 239, 146,
- 146, 146, 146, 146, 146, 146, 240, 241, 145, 242, 146, 146, 243, 226, 244, 226,
- 245, 246, 226, 226, 226, 247, 226, 248, 146, 146, 146, 226, 249, 146, 146, 146,
- 9, 9, 9, 11, 11, 11, 250, 251, 13, 13, 13, 13, 13, 13, 252, 253,
- 11, 11, 11, 47, 47, 47, 254, 255, 47, 47, 47, 47, 47, 47, 32, 32,
- 256, 257, 258, 259, 260, 261, 262, 262, 263, 264, 265, 266, 267, 47, 47, 47,
- 47, 268, 148, 47, 47, 47, 47, 269, 47, 270, 47, 47, 146, 146, 146, 47,
- 146, 146, 271, 146, 272, 273, 146, 146, 271, 146, 146, 273, 146, 146, 146, 146,
- 47, 47, 47, 47, 146, 146, 146, 146, 47, 274, 47, 47, 47, 47, 47, 47,
- 47, 146, 146, 146, 146, 47, 47, 187, 275, 47, 61, 47, 13, 13, 276, 277,
- 13, 278, 47, 47, 47, 47, 279, 280, 31, 281, 282, 283, 13, 13, 13, 284,
- 285, 286, 287, 288, 289, 290, 11, 291, 292, 47, 293, 294, 47, 47, 47, 295,
- 296, 47, 47, 297, 298, 160, 32, 299, 61, 47, 300, 47, 301, 302, 47, 47,
- 72, 47, 47, 303, 304, 305, 306, 61, 47, 47, 307, 308, 309, 310, 47, 311,
- 47, 47, 47, 312, 58, 313, 314, 315, 47, 47, 47, 11, 11, 316, 317, 11,
- 11, 11, 11, 11, 47, 47, 318, 160, 319, 319, 319, 319, 319, 319, 319, 319,
- 320, 320, 320, 320, 320, 320, 320, 320, 11, 321, 322, 47, 47, 47, 47, 47,
- 47, 47, 47, 323, 31, 324, 47, 47, 47, 47, 47, 325, 146, 47, 47, 47,
- 47, 47, 47, 47, 326, 146, 146, 327, 32, 328, 32, 329, 330, 331, 332, 47,
- 47, 47, 47, 47, 47, 47, 47, 333, 334, 2, 3, 4, 5, 335, 336, 337,
- 47, 338, 47, 47, 47, 47, 339, 340, 341, 145, 145, 342, 219, 219, 219, 343,
- 344, 146, 146, 146, 146, 146, 146, 345, 346, 346, 346, 346, 346, 346, 346, 346,
- 47, 47, 47, 47, 47, 47, 347, 145, 47, 47, 348, 47, 349, 47, 47, 60,
- 47, 350, 47, 47, 47, 351, 219, 219, 9, 9, 147, 11, 11, 47, 47, 47,
- 47, 47, 160, 9, 9, 147, 11, 11, 47, 47, 47, 47, 47, 47, 350, 9,
- 9, 352, 11, 11, 11, 11, 11, 11, 27, 27, 27, 27, 27, 27, 27, 27,
- 47, 47, 47, 47, 47, 353, 47, 354, 47, 47, 355, 145, 145, 145, 47, 356,
- 47, 357, 47, 350, 66, 66, 66, 66, 47, 47, 47, 358, 145, 145, 145, 145,
- 359, 47, 47, 360, 145, 66, 47, 361, 47, 362, 145, 145, 363, 47, 364, 66,
- 47, 47, 47, 365, 47, 366, 47, 366, 47, 365, 144, 145, 145, 145, 145, 145,
- 9, 9, 9, 9, 11, 11, 11, 367, 47, 47, 368, 160, 160, 160, 160, 160,
- 145, 145, 145, 145, 145, 145, 145, 145, 47, 47, 369, 47, 47, 47, 47, 143,
- 47, 362, 370, 47, 60, 371, 66, 47, 372, 66, 66, 47, 373, 145, 47, 47,
- 374, 47, 47, 360, 375, 376, 377, 378, 180, 47, 47, 379, 380, 47, 47, 160,
- 97, 47, 381, 382, 383, 47, 47, 384, 180, 47, 47, 385, 386, 387, 388, 145,
- 47, 47, 389, 390, 359, 32, 32, 32, 47, 47, 365, 47, 47, 391, 172, 160,
- 92, 47, 47, 113, 392, 393, 394, 32, 47, 47, 47, 395, 396, 397, 47, 47,
- 47, 47, 47, 398, 399, 160, 160, 160, 47, 47, 400, 401, 402, 403, 32, 32,
- 47, 47, 47, 404, 405, 160, 66, 66, 47, 47, 406, 407, 160, 160, 160, 160,
- 47, 143, 408, 409, 47, 47, 47, 47, 47, 47, 389, 410, 66, 66, 66, 66,
- 9, 9, 9, 9, 11, 11, 128, 411, 47, 47, 47, 412, 413, 160, 160, 160,
- 47, 47, 47, 47, 47, 414, 415, 416, 417, 47, 47, 418, 419, 420, 47, 47,
- 421, 422, 66, 47, 47, 47, 47, 47, 66, 66, 66, 66, 66, 66, 66, 66,
- 47, 47, 400, 423, 424, 128, 145, 425, 47, 156, 426, 427, 32, 32, 32, 32,
- 47, 47, 47, 359, 428, 160, 47, 47, 429, 430, 160, 160, 160, 160, 160, 160,
- 47, 47, 47, 47, 47, 47, 47, 431, 432, 47, 47, 433, 434, 160, 160, 160,
- 47, 47, 47, 47, 145, 435, 436, 437, 219, 219, 219, 219, 219, 219, 219, 66,
- 47, 47, 47, 47, 47, 47, 47, 424, 47, 47, 47, 208, 438, 32, 32, 32,
- 47, 47, 47, 47, 47, 47, 305, 47, 47, 47, 47, 47, 160, 47, 47, 439,
- 47, 47, 47, 440, 441, 442, 443, 47, 9, 9, 9, 9, 9, 9, 11, 11,
- 145, 444, 66, 66, 66, 66, 66, 66, 47, 47, 47, 47, 391, 445, 416, 416,
- 446, 447, 27, 27, 27, 27, 448, 416, 47, 449, 208, 208, 208, 208, 208, 208,
- 32, 32, 32, 32, 32, 146, 146, 146, 146, 146, 146, 146, 146, 146, 450, 451,
- 452, 146, 453, 146, 146, 146, 146, 146, 146, 146, 146, 146, 454, 146, 146, 146,
- 9, 455, 11, 456, 457, 11, 196, 9, 458, 459, 9, 460, 11, 9, 455, 11,
- 456, 457, 11, 196, 9, 458, 459, 9, 460, 11, 9, 455, 11, 456, 457, 11,
- 196, 9, 458, 459, 9, 460, 11, 9, 455, 11, 196, 9, 461, 462, 463, 464,
- 11, 465, 9, 466, 467, 468, 469, 11, 470, 9, 471, 11, 472, 160, 160, 160,
- 32, 32, 32, 473, 32, 32, 474, 475, 476, 477, 32, 32, 32, 32, 32, 32,
- 478, 11, 11, 11, 11, 11, 11, 11, 32, 32, 32, 27, 27, 27, 27, 27,
- 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 47, 479, 480, 146, 146, 146,
- 47, 47, 481, 32, 47, 47, 482, 483, 47, 47, 47, 47, 47, 47, 484, 160,
- 47, 47, 47, 47, 355, 32, 32, 32, 9, 9, 458, 11, 485, 305, 66, 66,
- 145, 145, 486, 487, 145, 145, 145, 145, 145, 145, 488, 145, 145, 145, 145, 145,
- 47, 47, 47, 47, 47, 47, 47, 226, 489, 146, 146, 146, 146, 146, 146, 146,
- 146, 146, 146, 146, 146, 146, 146, 490, 146, 146, 146, 146, 146, 146, 146, 160,
- 208, 208, 208, 208, 208, 208, 208, 208, 0, 0, 0, 0, 0, 0, 0, 0,
+ 47, 47, 184, 185, 186, 61, 47, 187, 188, 9, 9, 9, 66, 189, 190, 191,
+ 11, 11, 192, 27, 27, 27, 193, 194, 11, 195, 27, 27, 32, 32, 32, 32,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 196, 13, 13, 13, 13, 13, 13,
+ 197, 197, 197, 197, 197, 198, 197, 11, 199, 199, 199, 200, 201, 202, 202, 201,
+ 203, 204, 205, 206, 207, 208, 209, 210, 211, 27, 212, 212, 212, 213, 214, 32,
+ 215, 216, 217, 218, 219, 145, 220, 220, 221, 222, 223, 146, 224, 225, 146, 226,
+ 227, 227, 227, 227, 227, 227, 227, 227, 228, 146, 229, 146, 146, 146, 146, 230,
+ 146, 231, 227, 232, 146, 233, 234, 146, 146, 146, 146, 146, 146, 146, 145, 145,
+ 145, 235, 146, 146, 146, 146, 236, 145, 146, 146, 146, 146, 146, 146, 146, 146,
+ 146, 146, 146, 237, 238, 146, 146, 239, 146, 146, 146, 146, 146, 146, 240, 146,
+ 146, 146, 146, 146, 146, 146, 241, 242, 145, 243, 146, 146, 244, 227, 245, 227,
+ 246, 247, 227, 227, 227, 248, 227, 249, 146, 146, 146, 227, 250, 146, 146, 146,
+ 9, 9, 9, 11, 11, 11, 251, 252, 13, 13, 13, 13, 13, 13, 253, 254,
+ 11, 11, 11, 47, 47, 47, 255, 256, 47, 47, 47, 47, 47, 47, 32, 32,
+ 257, 258, 259, 260, 261, 262, 263, 263, 264, 265, 266, 267, 268, 47, 47, 47,
+ 47, 269, 148, 47, 47, 47, 47, 270, 47, 271, 47, 47, 146, 146, 146, 47,
+ 146, 146, 272, 146, 273, 274, 146, 146, 272, 146, 146, 274, 146, 146, 146, 146,
+ 47, 47, 47, 47, 146, 146, 146, 146, 47, 275, 47, 47, 47, 47, 47, 47,
+ 47, 146, 146, 146, 146, 47, 47, 187, 276, 47, 61, 47, 13, 13, 277, 278,
+ 13, 279, 47, 47, 47, 47, 280, 281, 31, 282, 283, 284, 13, 13, 13, 285,
+ 286, 287, 288, 289, 290, 291, 9, 292, 293, 47, 294, 295, 47, 47, 47, 296,
+ 297, 47, 47, 298, 299, 160, 32, 300, 61, 47, 301, 47, 302, 303, 47, 47,
+ 72, 47, 47, 304, 305, 306, 307, 61, 47, 47, 308, 309, 310, 311, 47, 312,
+ 47, 47, 47, 313, 58, 314, 315, 316, 47, 47, 47, 11, 11, 317, 318, 11,
+ 11, 11, 11, 11, 47, 47, 319, 160, 320, 320, 320, 320, 320, 320, 320, 320,
+ 321, 321, 321, 321, 321, 321, 321, 321, 11, 322, 323, 47, 47, 47, 47, 47,
+ 47, 47, 47, 324, 31, 325, 47, 47, 47, 47, 47, 326, 146, 47, 47, 47,
+ 47, 47, 47, 47, 327, 146, 146, 328, 32, 329, 32, 330, 331, 332, 333, 47,
+ 47, 47, 47, 47, 47, 47, 47, 334, 335, 2, 3, 4, 5, 336, 337, 338,
+ 47, 339, 47, 47, 47, 47, 340, 341, 342, 145, 145, 343, 220, 220, 220, 344,
+ 345, 146, 146, 146, 146, 146, 146, 346, 347, 347, 347, 347, 347, 347, 347, 347,
+ 47, 47, 47, 47, 47, 47, 348, 145, 47, 47, 349, 47, 350, 47, 47, 60,
+ 47, 351, 47, 47, 47, 352, 220, 220, 9, 9, 147, 11, 11, 47, 47, 47,
+ 47, 47, 160, 9, 9, 147, 11, 11, 47, 47, 47, 47, 47, 47, 351, 9,
+ 9, 353, 11, 11, 47, 47, 47, 47, 27, 27, 27, 27, 27, 27, 27, 27,
+ 47, 47, 47, 47, 47, 354, 47, 355, 47, 47, 356, 145, 145, 145, 47, 357,
+ 47, 358, 47, 351, 66, 66, 66, 66, 47, 47, 47, 359, 145, 145, 145, 145,
+ 360, 47, 47, 361, 145, 66, 47, 362, 47, 363, 145, 145, 364, 47, 365, 66,
+ 47, 47, 47, 366, 47, 367, 47, 367, 47, 366, 144, 145, 145, 145, 145, 145,
+ 9, 9, 9, 9, 11, 11, 11, 368, 47, 47, 369, 160, 370, 9, 371, 11,
+ 372, 227, 227, 227, 227, 227, 227, 227, 145, 145, 145, 145, 145, 145, 145, 145,
+ 47, 47, 373, 47, 47, 47, 47, 374, 47, 363, 375, 47, 60, 376, 66, 47,
+ 377, 66, 66, 47, 378, 145, 47, 47, 379, 47, 47, 361, 380, 381, 382, 383,
+ 180, 47, 47, 384, 385, 47, 47, 160, 97, 47, 386, 387, 388, 47, 47, 389,
+ 180, 47, 47, 390, 391, 392, 393, 145, 47, 47, 394, 395, 360, 32, 32, 32,
+ 47, 47, 366, 47, 47, 396, 172, 160, 92, 47, 47, 113, 397, 398, 399, 32,
+ 47, 47, 47, 400, 401, 402, 403, 32, 47, 47, 47, 404, 405, 406, 47, 47,
+ 47, 47, 47, 407, 408, 160, 160, 160, 47, 47, 409, 410, 411, 412, 32, 32,
+ 47, 47, 47, 413, 414, 160, 66, 66, 47, 47, 415, 416, 160, 160, 160, 160,
+ 47, 417, 418, 419, 47, 47, 47, 47, 47, 47, 394, 420, 66, 66, 66, 66,
+ 9, 9, 9, 9, 11, 11, 128, 421, 47, 47, 47, 422, 423, 160, 160, 160,
+ 47, 47, 47, 47, 47, 424, 425, 426, 427, 47, 47, 428, 429, 430, 47, 47,
+ 431, 432, 66, 47, 47, 47, 47, 47, 66, 66, 66, 66, 66, 66, 66, 66,
+ 47, 47, 47, 47, 47, 47, 433, 160, 47, 47, 409, 434, 433, 128, 145, 435,
+ 47, 156, 436, 437, 32, 32, 32, 32, 47, 47, 47, 360, 438, 160, 47, 47,
+ 439, 440, 160, 160, 160, 160, 160, 160, 47, 47, 47, 47, 47, 47, 47, 441,
+ 442, 47, 47, 443, 444, 445, 32, 32, 47, 47, 47, 47, 145, 446, 447, 448,
+ 220, 220, 220, 220, 220, 220, 220, 66, 47, 47, 47, 47, 47, 47, 47, 433,
+ 47, 47, 47, 209, 449, 32, 47, 47, 47, 450, 451, 160, 160, 160, 160, 160,
+ 47, 47, 47, 47, 47, 47, 306, 47, 47, 47, 47, 47, 160, 47, 47, 452,
+ 47, 47, 47, 453, 454, 455, 456, 47, 27, 27, 27, 27, 457, 47, 458, 160,
+ 9, 9, 9, 9, 9, 9, 11, 11, 145, 459, 66, 66, 66, 66, 66, 66,
+ 47, 47, 47, 47, 396, 460, 426, 426, 461, 462, 27, 27, 27, 27, 463, 426,
+ 47, 464, 209, 209, 209, 209, 209, 209, 146, 146, 146, 146, 146, 146, 146, 160,
+ 32, 32, 32, 32, 32, 146, 146, 146, 146, 146, 146, 146, 146, 146, 465, 466,
+ 467, 146, 468, 146, 146, 146, 146, 146, 146, 146, 146, 146, 469, 146, 146, 146,
+ 9, 470, 11, 471, 472, 11, 197, 9, 473, 474, 9, 475, 11, 9, 470, 11,
+ 471, 472, 11, 197, 9, 473, 474, 9, 475, 11, 9, 470, 11, 471, 472, 11,
+ 197, 9, 473, 474, 9, 475, 11, 9, 470, 11, 197, 9, 476, 477, 478, 479,
+ 11, 480, 9, 481, 482, 483, 484, 11, 485, 9, 486, 11, 487, 160, 160, 160,
+ 32, 32, 32, 488, 32, 32, 489, 490, 491, 492, 32, 32, 32, 32, 32, 32,
+ 493, 11, 11, 11, 11, 11, 11, 11, 32, 32, 32, 27, 27, 27, 27, 27,
+ 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 47, 494, 495, 146, 146, 146,
+ 47, 47, 450, 32, 47, 47, 374, 496, 47, 47, 47, 47, 47, 47, 497, 160,
+ 47, 47, 47, 47, 47, 47, 450, 498, 47, 47, 47, 47, 356, 32, 32, 32,
+ 9, 9, 473, 11, 499, 306, 66, 66, 145, 145, 500, 501, 145, 145, 145, 145,
+ 145, 145, 502, 145, 145, 145, 145, 145, 47, 47, 47, 47, 47, 47, 47, 227,
+ 503, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 504,
+ 209, 209, 209, 209, 209, 209, 209, 209, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 939, 940, 941, 942, 946, 948, 0, 962,
969, 970, 971, 976,1001,1002,1003,1008, 0,1033,1040,1041,1042,1043,1047, 0,
0,1080,1081,1082,1086,1110, 0, 0,1124,1125,1126,1127,1131,1133, 0,1147,
@@ -5541,16 +5778,23 @@ _hb_ucd_u16[4920] =
0, 0,1602,1603,1934,1935,1574,1575,1576,1577,1579,1580,1581,1583,1584, 0,
1585,1587,1588,1589,1591, 0,1592, 0,1593,1594, 0,1595,1596, 0,1598,1599,
1600,1601,1604,1582,1578,1590,1597, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0,1936, 0,1937, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0,1938, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1939,1940, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0,1941,1942, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0,1944,1943, 0,1945, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0,1946,1947, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 1948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0,1949,1950,1951,1952,1953,1954,1955, 0, 0, 0,
+ 0,1936, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1937, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0,1938, 0,1939, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,1940, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,1941,1942, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,1943,1944, 0, 0, 0, 0, 0, 0,1945, 0,1946, 0, 0,
+ 0, 0, 0, 0, 0, 0,1947, 0, 0,1948, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,1950, 0,1949,
+ 1951, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,1953,1952, 0,1954, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0,1955,1956, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1957, 0, 0, 0, 0, 0, 0, 0, 0,1958,1961,1959,1965,1960,1962,1964,
+ 1963, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1967,1966,1968, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,1969,1970,1971,1972,1973,1974,1975, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0,1956,1957,1958,1960,1959,1961, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,1976,1977,1978,1980,1979,1981, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 106, 104, 107, 826, 114, 118, 119, 121,
123, 124, 127, 125, 34, 830, 130, 131, 132, 137, 827, 35, 133, 139, 829, 142,
143, 112, 144, 145, 924, 151, 152, 37, 157, 158, 159, 160, 38, 165, 166, 169,
@@ -5601,12 +5845,12 @@ _hb_ucd_i16[92] =
static inline uint_fast8_t
_hb_ucd_gc (unsigned u)
{
- return u<1114112u?_hb_ucd_u8[5096+(((_hb_ucd_u8[1168+(((_hb_ucd_u16[((_hb_ucd_u8[544+(((_hb_ucd_u8[u>>1>>3>>3>>4])<<4)+((u>>1>>3>>3)&15u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:2;
+ return u<1114112u?_hb_ucd_u8[5208+(((_hb_ucd_u8[1168+(((_hb_ucd_u16[((_hb_ucd_u8[544+(((_hb_ucd_u8[u>>1>>3>>3>>4])<<4)+((u>>1>>3>>3)&15u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:2;
}
static inline uint_fast8_t
_hb_ucd_ccc (unsigned u)
{
- return u<125259u?_hb_ucd_u8[7054+(((_hb_ucd_u8[6498+(((_hb_ucd_u8[6038+(((_hb_ucd_u8[5686+(((_hb_ucd_u8[5440+(u>>2>>2>>2>>3)])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:0;
+ return u<125259u?_hb_ucd_u8[7206+(((_hb_ucd_u8[6638+(((_hb_ucd_u8[6162+(((_hb_ucd_u8[5802+(((_hb_ucd_u8[5556+(u>>2>>2>>2>>3)])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:0;
}
static inline unsigned
_hb_ucd_b4 (const uint8_t* a, unsigned i)
@@ -5616,17 +5860,17 @@ _hb_ucd_b4 (const uint8_t* a, unsigned i)
static inline int_fast16_t
_hb_ucd_bmg (unsigned u)
{
- return u<65380u?_hb_ucd_i16[((_hb_ucd_u8[7946+(((_hb_ucd_u8[7714+(((_hb_ucd_u8[7618+(((_hb_ucd_b4(7554+_hb_ucd_u8,u>>1>>2>>3>>3))<<3)+((u>>1>>2>>3)&7u))])<<3)+((u>>1>>2)&7u))])<<2)+((u>>1)&3u))])<<1)+((u)&1u)]:0;
+ return u<65380u?_hb_ucd_i16[((_hb_ucd_u8[8098+(((_hb_ucd_u8[7866+(((_hb_ucd_u8[7770+(((_hb_ucd_b4(7706+_hb_ucd_u8,u>>1>>2>>3>>3))<<3)+((u>>1>>2>>3)&7u))])<<3)+((u>>1>>2)&7u))])<<2)+((u>>1)&3u))])<<1)+((u)&1u)]:0;
}
static inline uint_fast8_t
_hb_ucd_sc (unsigned u)
{
- return u<918016u?_hb_ucd_u8[11244+(((_hb_ucd_u8[10280+(((_hb_ucd_u8[9292+(((_hb_ucd_u8[8612+(((_hb_ucd_u8[8308+(((_hb_ucd_u8[8194+(u>>2>>2>>2>>3>>4)])<<4)+((u>>2>>2>>2>>3)&15u))])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:2;
+ return u<918016u?_hb_ucd_u8[11464+(((_hb_ucd_u8[10472+(((_hb_ucd_u8[9452+(((_hb_ucd_u8[8764+(((_hb_ucd_u8[8460+(((_hb_ucd_u8[8346+(u>>2>>2>>2>>3>>4)])<<4)+((u>>2>>2>>2>>3)&15u))])<<3)+((u>>2>>2>>2)&7u))])<<2)+((u>>2>>2)&3u))])<<2)+((u>>2)&3u))])<<2)+((u)&3u))]:2;
}
static inline uint_fast16_t
_hb_ucd_dm (unsigned u)
{
- return u<195102u?_hb_ucd_u16[1608+(((_hb_ucd_u8[12586+(((_hb_ucd_u8[12204+(u>>4>>5)])<<5)+((u>>4)&31u))])<<4)+((u)&15u))]:0;
+ return u<195102u?_hb_ucd_u16[1656+(((_hb_ucd_u8[12834+(((_hb_ucd_u8[12452+(u>>4>>5)])<<5)+((u>>4)&31u))])<<4)+((u)&15u))]:0;
}
#endif
diff --git a/thirdparty/harfbuzz/src/hb-unicode-emoji-table.hh b/thirdparty/harfbuzz/src/hb-unicode-emoji-table.hh
index e607e8ca82..4bc8d64c28 100644
--- a/thirdparty/harfbuzz/src/hb-unicode-emoji-table.hh
+++ b/thirdparty/harfbuzz/src/hb-unicode-emoji-table.hh
@@ -7,13 +7,13 @@
* on file with this header:
*
* # emoji-data.txt
- * # Date: 2023-02-01, 02:22:54 GMT
- * # © 2023 Unicode®, Inc.
+ * # Date: 2024-05-01, 21:25:24 GMT
+ * # © 2024 Unicode®, Inc.
* # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
- * # For terms of use, see https://www.unicode.org/terms_of_use.html
+ * # For terms of use and license, see https://www.unicode.org/terms_of_use.html
* #
* # Emoji Data for UTS #51
- * # Used with Emoji Version 15.1 and subsequent minor revisions (if any)
+ * # Used with Emoji Version 16.0 and subsequent minor revisions (if any)
* #
* # For documentation and usage, see https://www.unicode.org/reports/tr51
*/
diff --git a/thirdparty/harfbuzz/src/hb-unicode.hh b/thirdparty/harfbuzz/src/hb-unicode.hh
index 39aaee5baa..2b1921c28f 100644
--- a/thirdparty/harfbuzz/src/hb-unicode.hh
+++ b/thirdparty/harfbuzz/src/hb-unicode.hh
@@ -34,6 +34,9 @@
#include "hb.hh"
+// Hack. See: https://github.com/harfbuzz/harfbuzz/pull/4529#discussion_r1769638033
+#define _HB_UNICODE_GENERAL_CATEGORY_VARIATION_SELECTOR ((hb_unicode_general_category_t) 30)
+
extern HB_INTERNAL const uint8_t _hb_modified_combining_class[256];
/*
diff --git a/thirdparty/harfbuzz/src/hb-version.h b/thirdparty/harfbuzz/src/hb-version.h
index abffbdae9c..404c515d24 100644
--- a/thirdparty/harfbuzz/src/hb-version.h
+++ b/thirdparty/harfbuzz/src/hb-version.h
@@ -41,26 +41,26 @@ HB_BEGIN_DECLS
*
* The major component of the library version available at compile-time.
*/
-#define HB_VERSION_MAJOR 8
+#define HB_VERSION_MAJOR 10
/**
* HB_VERSION_MINOR:
*
* The minor component of the library version available at compile-time.
*/
-#define HB_VERSION_MINOR 5
+#define HB_VERSION_MINOR 0
/**
* HB_VERSION_MICRO:
*
* The micro component of the library version available at compile-time.
*/
-#define HB_VERSION_MICRO 0
+#define HB_VERSION_MICRO 1
/**
* HB_VERSION_STRING:
*
* A string literal containing the library version available at compile-time.
*/
-#define HB_VERSION_STRING "8.5.0"
+#define HB_VERSION_STRING "10.0.1"
/**
* HB_VERSION_ATLEAST:
diff --git a/thirdparty/harfbuzz/src/hb.hh b/thirdparty/harfbuzz/src/hb.hh
index 0ceeb99f50..fe466fe1f8 100644
--- a/thirdparty/harfbuzz/src/hb.hh
+++ b/thirdparty/harfbuzz/src/hb.hh
@@ -84,6 +84,7 @@
#pragma GCC diagnostic error "-Wredundant-decls"
#pragma GCC diagnostic error "-Wreorder"
#pragma GCC diagnostic error "-Wsign-compare"
+#pragma GCC diagnostic error "-Wstrict-flex-arrays"
#pragma GCC diagnostic error "-Wstrict-prototypes"
#pragma GCC diagnostic error "-Wstring-conversion"
#pragma GCC diagnostic error "-Wswitch-enum"
diff --git a/thirdparty/misc/bcdec.h b/thirdparty/misc/bcdec.h
new file mode 100644
index 0000000000..275ee05d94
--- /dev/null
+++ b/thirdparty/misc/bcdec.h
@@ -0,0 +1,1345 @@
+/* bcdec.h - v0.97
+ provides functions to decompress blocks of BC compressed images
+ written by Sergii "iOrange" Kudlai in 2022
+
+ This library does not allocate memory and is trying to use as less stack as possible
+
+ The library was never optimized specifically for speed but for the overall size
+ it has zero external dependencies and is not using any runtime functions
+
+ Supported BC formats:
+ BC1 (also known as DXT1) + it's "binary alpha" variant BC1A (DXT1A)
+ BC2 (also known as DXT3)
+ BC3 (also known as DXT5)
+ BC4 (also known as ATI1N)
+ BC5 (also known as ATI2N)
+ BC6H (HDR format)
+ BC7
+
+ BC1/BC2/BC3/BC7 are expected to decompress into 4*4 RGBA blocks 8bit per component (32bit pixel)
+ BC4/BC5 are expected to decompress into 4*4 R/RG blocks 8bit per component (8bit and 16bit pixel)
+ BC6H is expected to decompress into 4*4 RGB blocks of either 32bit float or 16bit "half" per
+ component (96bit or 48bit pixel)
+
+ For more info, issues and suggestions please visit https://github.com/iOrange/bcdec
+
+ CREDITS:
+ Aras Pranckevicius (@aras-p) - BC1/BC3 decoders optimizations (up to 3x the speed)
+ - BC6H/BC7 bits pulling routines optimizations
+ - optimized BC6H by moving unquantize out of the loop
+ - Split BC6H decompression function into 'half' and
+ 'float' variants
+
+ Michael Schmidt (@RunDevelopment) - Found better "magic" coefficients for integer interpolation
+ of reference colors in BC1 color block, that match with
+ the floating point interpolation. This also made it faster
+ than integer division by 3!
+
+ bugfixes:
+ @linkmauve
+
+ LICENSE: See end of file for license information.
+*/
+
+#ifndef BCDEC_HEADER_INCLUDED
+#define BCDEC_HEADER_INCLUDED
+
+#define BCDEC_VERSION_MAJOR 0
+#define BCDEC_VERSION_MINOR 97
+
+/* if BCDEC_STATIC causes problems, try defining BCDECDEF to 'inline' or 'static inline' */
+#ifndef BCDECDEF
+#ifdef BCDEC_STATIC
+#define BCDECDEF static
+#else
+#ifdef __cplusplus
+#define BCDECDEF extern "C"
+#else
+#define BCDECDEF extern
+#endif
+#endif
+#endif
+
+/* Used information sources:
+ https://docs.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression
+ https://docs.microsoft.com/en-us/windows/win32/direct3d11/bc6h-format
+ https://docs.microsoft.com/en-us/windows/win32/direct3d11/bc7-format
+ https://docs.microsoft.com/en-us/windows/win32/direct3d11/bc7-format-mode-reference
+
+ ! WARNING ! Khronos's BPTC partitions tables contain mistakes, do not use them!
+ https://www.khronos.org/registry/DataFormat/specs/1.1/dataformat.1.1.html#BPTC
+
+ ! Use tables from here instead !
+ https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_bptc.txt
+
+ Leaving it here as it's a nice read
+ https://fgiesen.wordpress.com/2021/10/04/gpu-bcn-decoding/
+
+ Fast half to float function from here
+ https://gist.github.com/rygorous/2144712
+*/
+
+#define BCDEC_BC1_BLOCK_SIZE 8
+#define BCDEC_BC2_BLOCK_SIZE 16
+#define BCDEC_BC3_BLOCK_SIZE 16
+#define BCDEC_BC4_BLOCK_SIZE 8
+#define BCDEC_BC5_BLOCK_SIZE 16
+#define BCDEC_BC6H_BLOCK_SIZE 16
+#define BCDEC_BC7_BLOCK_SIZE 16
+
+#define BCDEC_BC1_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC1_BLOCK_SIZE)
+#define BCDEC_BC2_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC2_BLOCK_SIZE)
+#define BCDEC_BC3_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC3_BLOCK_SIZE)
+#define BCDEC_BC4_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC4_BLOCK_SIZE)
+#define BCDEC_BC5_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC5_BLOCK_SIZE)
+#define BCDEC_BC6H_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC6H_BLOCK_SIZE)
+#define BCDEC_BC7_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*BCDEC_BC7_BLOCK_SIZE)
+
+BCDECDEF void bcdec_bc1(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
+BCDECDEF void bcdec_bc2(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
+BCDECDEF void bcdec_bc3(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
+BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
+BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
+BCDECDEF void bcdec_bc6h_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
+BCDECDEF void bcdec_bc6h_half(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned);
+BCDECDEF void bcdec_bc7(const void* compressedBlock, void* decompressedBlock, int destinationPitch);
+
+#endif /* BCDEC_HEADER_INCLUDED */
+
+#ifdef BCDEC_IMPLEMENTATION
+
+static void bcdec__color_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int onlyOpaqueMode) {
+ unsigned short c0, c1;
+ unsigned int refColors[4]; /* 0xAABBGGRR */
+ unsigned char* dstColors;
+ unsigned int colorIndices;
+ int i, j, idx;
+ unsigned int r0, g0, b0, r1, g1, b1, r, g, b;
+
+ c0 = ((unsigned short*)compressedBlock)[0];
+ c1 = ((unsigned short*)compressedBlock)[1];
+
+ /* Unpack 565 ref colors */
+ r0 = (c0 >> 11) & 0x1F;
+ g0 = (c0 >> 5) & 0x3F;
+ b0 = c0 & 0x1F;
+
+ r1 = (c1 >> 11) & 0x1F;
+ g1 = (c1 >> 5) & 0x3F;
+ b1 = c1 & 0x1F;
+
+ /* Expand 565 ref colors to 888 */
+ r = (r0 * 527 + 23) >> 6;
+ g = (g0 * 259 + 33) >> 6;
+ b = (b0 * 527 + 23) >> 6;
+ refColors[0] = 0xFF000000 | (b << 16) | (g << 8) | r;
+
+ r = (r1 * 527 + 23) >> 6;
+ g = (g1 * 259 + 33) >> 6;
+ b = (b1 * 527 + 23) >> 6;
+ refColors[1] = 0xFF000000 | (b << 16) | (g << 8) | r;
+
+ if (c0 > c1 || onlyOpaqueMode) { /* Standard BC1 mode (also BC3 color block uses ONLY this mode) */
+ /* color_2 = 2/3*color_0 + 1/3*color_1
+ color_3 = 1/3*color_0 + 2/3*color_1 */
+ r = ((2 * r0 + r1) * 351 + 61) >> 7;
+ g = ((2 * g0 + g1) * 2763 + 1039) >> 11;
+ b = ((2 * b0 + b1) * 351 + 61) >> 7;
+ refColors[2] = 0xFF000000 | (b << 16) | (g << 8) | r;
+
+ r = ((r0 + r1 * 2) * 351 + 61) >> 7;
+ g = ((g0 + g1 * 2) * 2763 + 1039) >> 11;
+ b = ((b0 + b1 * 2) * 351 + 61) >> 7;
+ refColors[3] = 0xFF000000 | (b << 16) | (g << 8) | r;
+ } else { /* Quite rare BC1A mode */
+ /* color_2 = 1/2*color_0 + 1/2*color_1;
+ color_3 = 0; */
+ r = ((r0 + r1) * 1053 + 125) >> 8;
+ g = ((g0 + g1) * 4145 + 1019) >> 11;
+ b = ((b0 + b1) * 1053 + 125) >> 8;
+ refColors[2] = 0xFF000000 | (b << 16) | (g << 8) | r;
+
+ refColors[3] = 0x00000000;
+ }
+
+ colorIndices = ((unsigned int*)compressedBlock)[1];
+
+ /* Fill out the decompressed color block */
+ dstColors = (unsigned char*)decompressedBlock;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ idx = colorIndices & 0x03;
+ ((unsigned int*)dstColors)[j] = refColors[idx];
+ colorIndices >>= 2;
+ }
+
+ dstColors += destinationPitch;
+ }
+}
+
+static void bcdec__sharp_alpha_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ unsigned short* alpha;
+ unsigned char* decompressed;
+ int i, j;
+
+ alpha = (unsigned short*)compressedBlock;
+ decompressed = (unsigned char*)decompressedBlock;
+
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ decompressed[j * 4] = ((alpha[i] >> (4 * j)) & 0x0F) * 17;
+ }
+
+ decompressed += destinationPitch;
+ }
+}
+
+static void bcdec__smooth_alpha_block(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int pixelSize) {
+ unsigned char* decompressed;
+ unsigned char alpha[8];
+ int i, j;
+ unsigned long long block, indices;
+
+ block = *(unsigned long long*)compressedBlock;
+ decompressed = (unsigned char*)decompressedBlock;
+
+ alpha[0] = block & 0xFF;
+ alpha[1] = (block >> 8) & 0xFF;
+
+ if (alpha[0] > alpha[1]) {
+ /* 6 interpolated alpha values. */
+ alpha[2] = (6 * alpha[0] + alpha[1] + 1) / 7; /* 6/7*alpha_0 + 1/7*alpha_1 */
+ alpha[3] = (5 * alpha[0] + 2 * alpha[1] + 1) / 7; /* 5/7*alpha_0 + 2/7*alpha_1 */
+ alpha[4] = (4 * alpha[0] + 3 * alpha[1] + 1) / 7; /* 4/7*alpha_0 + 3/7*alpha_1 */
+ alpha[5] = (3 * alpha[0] + 4 * alpha[1] + 1) / 7; /* 3/7*alpha_0 + 4/7*alpha_1 */
+ alpha[6] = (2 * alpha[0] + 5 * alpha[1] + 1) / 7; /* 2/7*alpha_0 + 5/7*alpha_1 */
+ alpha[7] = ( alpha[0] + 6 * alpha[1] + 1) / 7; /* 1/7*alpha_0 + 6/7*alpha_1 */
+ }
+ else {
+ /* 4 interpolated alpha values. */
+ alpha[2] = (4 * alpha[0] + alpha[1] + 1) / 5; /* 4/5*alpha_0 + 1/5*alpha_1 */
+ alpha[3] = (3 * alpha[0] + 2 * alpha[1] + 1) / 5; /* 3/5*alpha_0 + 2/5*alpha_1 */
+ alpha[4] = (2 * alpha[0] + 3 * alpha[1] + 1) / 5; /* 2/5*alpha_0 + 3/5*alpha_1 */
+ alpha[5] = ( alpha[0] + 4 * alpha[1] + 1) / 5; /* 1/5*alpha_0 + 4/5*alpha_1 */
+ alpha[6] = 0x00;
+ alpha[7] = 0xFF;
+ }
+
+ indices = block >> 16;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ decompressed[j * pixelSize] = alpha[indices & 0x07];
+ indices >>= 3;
+ }
+
+ decompressed += destinationPitch;
+ }
+}
+
+typedef struct bcdec__bitstream {
+ unsigned long long low;
+ unsigned long long high;
+} bcdec__bitstream_t;
+
+static int bcdec__bitstream_read_bits(bcdec__bitstream_t* bstream, int numBits) {
+ unsigned int mask = (1 << numBits) - 1;
+ /* Read the low N bits */
+ unsigned int bits = (bstream->low & mask);
+
+ bstream->low >>= numBits;
+ /* Put the low N bits of "high" into the high 64-N bits of "low". */
+ bstream->low |= (bstream->high & mask) << (sizeof(bstream->high) * 8 - numBits);
+ bstream->high >>= numBits;
+
+ return bits;
+}
+
+static int bcdec__bitstream_read_bit(bcdec__bitstream_t* bstream) {
+ return bcdec__bitstream_read_bits(bstream, 1);
+}
+
+/* reversed bits pulling, used in BC6H decoding
+ why ?? just why ??? */
+static int bcdec__bitstream_read_bits_r(bcdec__bitstream_t* bstream, int numBits) {
+ int bits = bcdec__bitstream_read_bits(bstream, numBits);
+ /* Reverse the bits. */
+ int result = 0;
+ while (numBits--) {
+ result <<= 1;
+ result |= (bits & 1);
+ bits >>= 1;
+ }
+ return result;
+}
+
+
+
+BCDECDEF void bcdec_bc1(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ bcdec__color_block(compressedBlock, decompressedBlock, destinationPitch, 0);
+}
+
+BCDECDEF void bcdec_bc2(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ bcdec__color_block(((char*)compressedBlock) + 8, decompressedBlock, destinationPitch, 1);
+ bcdec__sharp_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch);
+}
+
+BCDECDEF void bcdec_bc3(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ bcdec__color_block(((char*)compressedBlock) + 8, decompressedBlock, destinationPitch, 1);
+ bcdec__smooth_alpha_block(compressedBlock, ((char*)decompressedBlock) + 3, destinationPitch, 4);
+}
+
+BCDECDEF void bcdec_bc4(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 1);
+}
+
+BCDECDEF void bcdec_bc5(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ bcdec__smooth_alpha_block(compressedBlock, decompressedBlock, destinationPitch, 2);
+ bcdec__smooth_alpha_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 1, destinationPitch, 2);
+}
+
+/* http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend */
+static int bcdec__extend_sign(int val, int bits) {
+ return (val << (32 - bits)) >> (32 - bits);
+}
+
+static int bcdec__transform_inverse(int val, int a0, int bits, int isSigned) {
+ /* If the precision of A0 is "p" bits, then the transform algorithm is:
+ B0 = (B0 + A0) & ((1 << p) - 1) */
+ val = (val + a0) & ((1 << bits) - 1);
+ if (isSigned) {
+ val = bcdec__extend_sign(val, bits);
+ }
+ return val;
+}
+
+/* pretty much copy-paste from documentation */
+static int bcdec__unquantize(int val, int bits, int isSigned) {
+ int unq, s = 0;
+
+ if (!isSigned) {
+ if (bits >= 15) {
+ unq = val;
+ } else if (!val) {
+ unq = 0;
+ } else if (val == ((1 << bits) - 1)) {
+ unq = 0xFFFF;
+ } else {
+ unq = ((val << 16) + 0x8000) >> bits;
+ }
+ } else {
+ if (bits >= 16) {
+ unq = val;
+ } else {
+ if (val < 0) {
+ s = 1;
+ val = -val;
+ }
+
+ if (val == 0) {
+ unq = 0;
+ } else if (val >= ((1 << (bits - 1)) - 1)) {
+ unq = 0x7FFF;
+ } else {
+ unq = ((val << 15) + 0x4000) >> (bits - 1);
+ }
+
+ if (s) {
+ unq = -unq;
+ }
+ }
+ }
+ return unq;
+}
+
+static int bcdec__interpolate(int a, int b, int* weights, int index) {
+ return (a * (64 - weights[index]) + b * weights[index] + 32) >> 6;
+}
+
+static unsigned short bcdec__finish_unquantize(int val, int isSigned) {
+ int s;
+
+ if (!isSigned) {
+ return (unsigned short)((val * 31) >> 6); /* scale the magnitude by 31 / 64 */
+ } else {
+ val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5; /* scale the magnitude by 31 / 32 */
+ s = 0;
+ if (val < 0) {
+ s = 0x8000;
+ val = -val;
+ }
+ return (unsigned short)(s | val);
+ }
+}
+
+/* modified half_to_float_fast4 from https://gist.github.com/rygorous/2144712 */
+static float bcdec__half_to_float_quick(unsigned short half) {
+ typedef union {
+ unsigned int u;
+ float f;
+ } FP32;
+
+ static const FP32 magic = { 113 << 23 };
+ static const unsigned int shifted_exp = 0x7c00 << 13; /* exponent mask after shift */
+ FP32 o;
+ unsigned int exp;
+
+ o.u = (half & 0x7fff) << 13; /* exponent/mantissa bits */
+ exp = shifted_exp & o.u; /* just the exponent */
+ o.u += (127 - 15) << 23; /* exponent adjust */
+
+ /* handle exponent special cases */
+ if (exp == shifted_exp) { /* Inf/NaN? */
+ o.u += (128 - 16) << 23; /* extra exp adjust */
+ } else if (exp == 0) { /* Zero/Denormal? */
+ o.u += 1 << 23; /* extra exp adjust */
+ o.f -= magic.f; /* renormalize */
+ }
+
+ o.u |= (half & 0x8000) << 16; /* sign bit */
+ return o.f;
+}
+
+BCDECDEF void bcdec_bc6h_half(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
+ static char actual_bits_count[4][14] = {
+ { 10, 7, 11, 11, 11, 9, 8, 8, 8, 6, 10, 11, 12, 16 }, /* W */
+ { 5, 6, 5, 4, 4, 5, 6, 5, 5, 6, 10, 9, 8, 4 }, /* dR */
+ { 5, 6, 4, 5, 4, 5, 5, 6, 5, 6, 10, 9, 8, 4 }, /* dG */
+ { 5, 6, 4, 4, 5, 5, 5, 5, 6, 6, 10, 9, 8, 4 } /* dB */
+ };
+
+ /* There are 32 possible partition sets for a two-region tile.
+ Each 4x4 block represents a single shape.
+ Here also every fix-up index has MSB bit set. */
+ static unsigned char partition_sets[32][4][4] = {
+ { {128, 0, 1, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 0, 1, 129} }, /* 0 */
+ { {128, 0, 0, 1}, {0, 0, 0, 1}, { 0, 0, 0, 1}, {0, 0, 0, 129} }, /* 1 */
+ { {128, 1, 1, 1}, {0, 1, 1, 1}, { 0, 1, 1, 1}, {0, 1, 1, 129} }, /* 2 */
+ { {128, 0, 0, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 1, 1, 129} }, /* 3 */
+ { {128, 0, 0, 0}, {0, 0, 0, 1}, { 0, 0, 0, 1}, {0, 0, 1, 129} }, /* 4 */
+ { {128, 0, 1, 1}, {0, 1, 1, 1}, { 0, 1, 1, 1}, {1, 1, 1, 129} }, /* 5 */
+ { {128, 0, 0, 1}, {0, 0, 1, 1}, { 0, 1, 1, 1}, {1, 1, 1, 129} }, /* 6 */
+ { {128, 0, 0, 0}, {0, 0, 0, 1}, { 0, 0, 1, 1}, {0, 1, 1, 129} }, /* 7 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 1}, {0, 0, 1, 129} }, /* 8 */
+ { {128, 0, 1, 1}, {0, 1, 1, 1}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 9 */
+ { {128, 0, 0, 0}, {0, 0, 0, 1}, { 0, 1, 1, 1}, {1, 1, 1, 129} }, /* 10 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 1}, {0, 1, 1, 129} }, /* 11 */
+ { {128, 0, 0, 1}, {0, 1, 1, 1}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 12 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 13 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 14 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 0}, {1, 1, 1, 129} }, /* 15 */
+ { {128, 0, 0, 0}, {1, 0, 0, 0}, { 1, 1, 1, 0}, {1, 1, 1, 129} }, /* 16 */
+ { {128, 1, 129, 1}, {0, 0, 0, 1}, { 0, 0, 0, 0}, {0, 0, 0, 0} }, /* 17 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {129, 0, 0, 0}, {1, 1, 1, 0} }, /* 18 */
+ { {128, 1, 129, 1}, {0, 0, 1, 1}, { 0, 0, 0, 1}, {0, 0, 0, 0} }, /* 19 */
+ { {128, 0, 129, 1}, {0, 0, 0, 1}, { 0, 0, 0, 0}, {0, 0, 0, 0} }, /* 20 */
+ { {128, 0, 0, 0}, {1, 0, 0, 0}, {129, 1, 0, 0}, {1, 1, 1, 0} }, /* 21 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {129, 0, 0, 0}, {1, 1, 0, 0} }, /* 22 */
+ { {128, 1, 1, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 0, 0, 129} }, /* 23 */
+ { {128, 0, 129, 1}, {0, 0, 0, 1}, { 0, 0, 0, 1}, {0, 0, 0, 0} }, /* 24 */
+ { {128, 0, 0, 0}, {1, 0, 0, 0}, {129, 0, 0, 0}, {1, 1, 0, 0} }, /* 25 */
+ { {128, 1, 129, 0}, {0, 1, 1, 0}, { 0, 1, 1, 0}, {0, 1, 1, 0} }, /* 26 */
+ { {128, 0, 129, 1}, {0, 1, 1, 0}, { 0, 1, 1, 0}, {1, 1, 0, 0} }, /* 27 */
+ { {128, 0, 0, 1}, {0, 1, 1, 1}, {129, 1, 1, 0}, {1, 0, 0, 0} }, /* 28 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, {129, 1, 1, 1}, {0, 0, 0, 0} }, /* 29 */
+ { {128, 1, 129, 1}, {0, 0, 0, 1}, { 1, 0, 0, 0}, {1, 1, 1, 0} }, /* 30 */
+ { {128, 0, 129, 1}, {1, 0, 0, 1}, { 1, 0, 0, 1}, {1, 1, 0, 0} } /* 31 */
+ };
+
+ static int aWeight3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 };
+ static int aWeight4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 };
+
+ bcdec__bitstream_t bstream;
+ int mode, partition, numPartitions, i, j, partitionSet, indexBits, index, ep_i, actualBits0Mode;
+ int r[4], g[4], b[4]; /* wxyz */
+ unsigned short* decompressed;
+ int* weights;
+
+ decompressed = (unsigned short*)decompressedBlock;
+
+ bstream.low = ((unsigned long long*)compressedBlock)[0];
+ bstream.high = ((unsigned long long*)compressedBlock)[1];
+
+ r[0] = r[1] = r[2] = r[3] = 0;
+ g[0] = g[1] = g[2] = g[3] = 0;
+ b[0] = b[1] = b[2] = b[3] = 0;
+
+ mode = bcdec__bitstream_read_bits(&bstream, 2);
+ if (mode > 1) {
+ mode |= (bcdec__bitstream_read_bits(&bstream, 3) << 2);
+ }
+
+ /* modes >= 11 (10 in my code) are using 0 one, others will read it from the bitstream */
+ partition = 0;
+
+ switch (mode) {
+ /* mode 1 */
+ case 0b00: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 75 bits (10.555, 10.555, 10.555) */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* rx[4:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* gx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* bx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 5); /* ry[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 5); /* rz[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 0;
+ } break;
+
+ /* mode 2 */
+ case 0b01: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 75 bits (7666, 7666, 7666) */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 5; /* gy[5] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 5; /* gz[5] */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 7); /* rw[6:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 7); /* gw[6:0] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 5; /* by[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 7); /* bw[6:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 5; /* bz[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* rx[5:0] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* gx[5:0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* bx[5:0] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 6); /* ry[5:0] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 6); /* rz[5:0] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 1;
+ } break;
+
+ /* mode 3 */
+ case 0b00010: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (11.555, 11.444, 11.444) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* rx[4:0] */
+ r[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* rw[10] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* gx[3:0] */
+ g[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* gw[10] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* bx[3:0] */
+ b[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* bw[10] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 5); /* ry[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 5); /* rz[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 2;
+ } break;
+
+ /* mode 4 */
+ case 0b00110: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (11.444, 11.555, 11.444) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* rx[3:0] */
+ r[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* rw[10] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* gx[4:0] */
+ g[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* gw[10] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* bx[3:0] */
+ b[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* bw[10] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* ry[3:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* rz[3:0] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 3;
+ } break;
+
+ /* mode 5 */
+ case 0b01010: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (11.444, 11.444, 11.555) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* rx[3:0] */
+ r[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* rw[10] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* gx[3:0] */
+ g[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* gw[10] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* bx[4:0] */
+ b[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* bw[10] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* ry[3:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* rz[3:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 4;
+ } break;
+
+ /* mode 6 */
+ case 0b01110: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (9555, 9555, 9555) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 9); /* rw[8:0] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 9); /* gw[8:0] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 9); /* bw[8:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* rx[4:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* gx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gx[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* bx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 5); /* ry[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 5); /* rz[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 5;
+ } break;
+
+ /* mode 7 */
+ case 0b10010: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (8666, 8555, 8555) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* rw[7:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* gw[7:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* bw[7:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* rx[5:0] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* gx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* bx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 6); /* ry[5:0] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 6); /* rz[5:0] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 6;
+ } break;
+
+ /* mode 8 */
+ case 0b10110: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (8555, 8666, 8555) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* rw[7:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* gw[7:0] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 5; /* gy[5] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* bw[7:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 5; /* gz[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* rx[4:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* gx[5:0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* zx[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* bx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 5); /* ry[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 5); /* rz[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 7;
+ } break;
+
+ /* mode 9 */
+ case 0b11010: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (8555, 8555, 8666) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* rw[7:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* gw[7:0] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 5; /* by[5] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 8); /* bw[7:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 5; /* bz[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* bw[4:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 5); /* gx[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* bx[5:0] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 5); /* ry[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 5); /* rz[4:0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 8;
+ } break;
+
+ /* mode 10 */
+ case 0b11110: {
+ /* Partitition indices: 46 bits
+ Partition: 5 bits
+ Color Endpoints: 72 bits (6666, 6666, 6666) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 6); /* rw[5:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gz[4] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream); /* bz[0] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 1; /* bz[1] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* by[4] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 6); /* gw[5:0] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 5; /* gy[5] */
+ b[2] |= bcdec__bitstream_read_bit(&bstream) << 5; /* by[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 2; /* bz[2] */
+ g[2] |= bcdec__bitstream_read_bit(&bstream) << 4; /* gy[4] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 6); /* bw[5:0] */
+ g[3] |= bcdec__bitstream_read_bit(&bstream) << 5; /* gz[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 3; /* bz[3] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 5; /* bz[5] */
+ b[3] |= bcdec__bitstream_read_bit(&bstream) << 4; /* bz[4] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* rx[5:0] */
+ g[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* gy[3:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* gx[5:0] */
+ g[3] |= bcdec__bitstream_read_bits(&bstream, 4); /* gz[3:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 6); /* bx[5:0] */
+ b[2] |= bcdec__bitstream_read_bits(&bstream, 4); /* by[3:0] */
+ r[2] |= bcdec__bitstream_read_bits(&bstream, 6); /* ry[5:0] */
+ r[3] |= bcdec__bitstream_read_bits(&bstream, 6); /* rz[5:0] */
+ partition = bcdec__bitstream_read_bits(&bstream, 5); /* d[4:0] */
+ mode = 9;
+ } break;
+
+ /* mode 11 */
+ case 0b00011: {
+ /* Partitition indices: 63 bits
+ Partition: 0 bits
+ Color Endpoints: 60 bits (10.10, 10.10, 10.10) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 10); /* rx[9:0] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 10); /* gx[9:0] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 10); /* bx[9:0] */
+ mode = 10;
+ } break;
+
+ /* mode 12 */
+ case 0b00111: {
+ /* Partitition indices: 63 bits
+ Partition: 0 bits
+ Color Endpoints: 60 bits (11.9, 11.9, 11.9) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 9); /* rx[8:0] */
+ r[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* rw[10] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 9); /* gx[8:0] */
+ g[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* gw[10] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 9); /* bx[8:0] */
+ b[0] |= bcdec__bitstream_read_bit(&bstream) << 10; /* bw[10] */
+ mode = 11;
+ } break;
+
+ /* mode 13 */
+ case 0b01011: {
+ /* Partitition indices: 63 bits
+ Partition: 0 bits
+ Color Endpoints: 60 bits (12.8, 12.8, 12.8) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 8); /* rx[7:0] */
+ r[0] |= bcdec__bitstream_read_bits_r(&bstream, 2) << 10;/* rx[10:11] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 8); /* gx[7:0] */
+ g[0] |= bcdec__bitstream_read_bits_r(&bstream, 2) << 10;/* gx[10:11] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 8); /* bx[7:0] */
+ b[0] |= bcdec__bitstream_read_bits_r(&bstream, 2) << 10;/* bx[10:11] */
+ mode = 12;
+ } break;
+
+ /* mode 14 */
+ case 0b01111: {
+ /* Partitition indices: 63 bits
+ Partition: 0 bits
+ Color Endpoints: 60 bits (16.4, 16.4, 16.4) */
+ r[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* rw[9:0] */
+ g[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* gw[9:0] */
+ b[0] |= bcdec__bitstream_read_bits(&bstream, 10); /* bw[9:0] */
+ r[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* rx[3:0] */
+ r[0] |= bcdec__bitstream_read_bits_r(&bstream, 6) << 10;/* rw[10:15] */
+ g[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* gx[3:0] */
+ g[0] |= bcdec__bitstream_read_bits_r(&bstream, 6) << 10;/* gw[10:15] */
+ b[1] |= bcdec__bitstream_read_bits(&bstream, 4); /* bx[3:0] */
+ b[0] |= bcdec__bitstream_read_bits_r(&bstream, 6) << 10;/* bw[10:15] */
+ mode = 13;
+ } break;
+
+ default: {
+ /* Modes 10011, 10111, 11011, and 11111 (not shown) are reserved.
+ Do not use these in your encoder. If the hardware is passed blocks
+ with one of these modes specified, the resulting decompressed block
+ must contain all zeroes in all channels except for the alpha channel. */
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ decompressed[j * 3 + 0] = 0;
+ decompressed[j * 3 + 1] = 0;
+ decompressed[j * 3 + 2] = 0;
+ }
+ decompressed += destinationPitch;
+ }
+
+ return;
+ }
+ }
+
+ numPartitions = (mode >= 10) ? 0 : 1;
+
+ actualBits0Mode = actual_bits_count[0][mode];
+ if (isSigned) {
+ r[0] = bcdec__extend_sign(r[0], actualBits0Mode);
+ g[0] = bcdec__extend_sign(g[0], actualBits0Mode);
+ b[0] = bcdec__extend_sign(b[0], actualBits0Mode);
+ }
+
+ /* Mode 11 (like Mode 10) does not use delta compression,
+ and instead stores both color endpoints explicitly. */
+ if ((mode != 9 && mode != 10) || isSigned) {
+ for (i = 1; i < (numPartitions + 1) * 2; ++i) {
+ r[i] = bcdec__extend_sign(r[i], actual_bits_count[1][mode]);
+ g[i] = bcdec__extend_sign(g[i], actual_bits_count[2][mode]);
+ b[i] = bcdec__extend_sign(b[i], actual_bits_count[3][mode]);
+ }
+ }
+
+ if (mode != 9 && mode != 10) {
+ for (i = 1; i < (numPartitions + 1) * 2; ++i) {
+ r[i] = bcdec__transform_inverse(r[i], r[0], actualBits0Mode, isSigned);
+ g[i] = bcdec__transform_inverse(g[i], g[0], actualBits0Mode, isSigned);
+ b[i] = bcdec__transform_inverse(b[i], b[0], actualBits0Mode, isSigned);
+ }
+ }
+
+ for (i = 0; i < (numPartitions + 1) * 2; ++i) {
+ r[i] = bcdec__unquantize(r[i], actualBits0Mode, isSigned);
+ g[i] = bcdec__unquantize(g[i], actualBits0Mode, isSigned);
+ b[i] = bcdec__unquantize(b[i], actualBits0Mode, isSigned);
+ }
+
+ weights = (mode >= 10) ? aWeight4 : aWeight3;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ partitionSet = (mode >= 10) ? ((i|j) ? 0 : 128) : partition_sets[partition][i][j];
+
+ indexBits = (mode >= 10) ? 4 : 3;
+ /* fix-up index is specified with one less bit */
+ /* The fix-up index for subset 0 is always index 0 */
+ if (partitionSet & 0x80) {
+ indexBits--;
+ }
+ partitionSet &= 0x01;
+
+ index = bcdec__bitstream_read_bits(&bstream, indexBits);
+
+ ep_i = partitionSet * 2;
+ decompressed[j * 3 + 0] = bcdec__finish_unquantize(
+ bcdec__interpolate(r[ep_i], r[ep_i+1], weights, index), isSigned);
+ decompressed[j * 3 + 1] = bcdec__finish_unquantize(
+ bcdec__interpolate(g[ep_i], g[ep_i+1], weights, index), isSigned);
+ decompressed[j * 3 + 2] = bcdec__finish_unquantize(
+ bcdec__interpolate(b[ep_i], b[ep_i+1], weights, index), isSigned);
+ }
+
+ decompressed += destinationPitch;
+ }
+}
+
+BCDECDEF void bcdec_bc6h_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) {
+ unsigned short block[16*3];
+ float* decompressed;
+ const unsigned short* b;
+ int i, j;
+
+ bcdec_bc6h_half(compressedBlock, block, 4*3, isSigned);
+ b = block;
+ decompressed = (float*)decompressedBlock;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ decompressed[j * 3 + 0] = bcdec__half_to_float_quick(*b++);
+ decompressed[j * 3 + 1] = bcdec__half_to_float_quick(*b++);
+ decompressed[j * 3 + 2] = bcdec__half_to_float_quick(*b++);
+ }
+ decompressed += destinationPitch;
+ }
+}
+
+static void bcdec__swap_values(int* a, int* b) {
+ a[0] ^= b[0], b[0] ^= a[0], a[0] ^= b[0];
+}
+
+BCDECDEF void bcdec_bc7(const void* compressedBlock, void* decompressedBlock, int destinationPitch) {
+ static char actual_bits_count[2][8] = {
+ { 4, 6, 5, 7, 5, 7, 7, 5 }, /* RGBA */
+ { 0, 0, 0, 0, 6, 8, 7, 5 }, /* Alpha */
+ };
+
+ /* There are 64 possible partition sets for a two-region tile.
+ Each 4x4 block represents a single shape.
+ Here also every fix-up index has MSB bit set. */
+ static unsigned char partition_sets[2][64][4][4] = {
+ { /* Partition table for 2-subset BPTC */
+ { {128, 0, 1, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 0, 1, 129} }, /* 0 */
+ { {128, 0, 0, 1}, {0, 0, 0, 1}, { 0, 0, 0, 1}, {0, 0, 0, 129} }, /* 1 */
+ { {128, 1, 1, 1}, {0, 1, 1, 1}, { 0, 1, 1, 1}, {0, 1, 1, 129} }, /* 2 */
+ { {128, 0, 0, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 1, 1, 129} }, /* 3 */
+ { {128, 0, 0, 0}, {0, 0, 0, 1}, { 0, 0, 0, 1}, {0, 0, 1, 129} }, /* 4 */
+ { {128, 0, 1, 1}, {0, 1, 1, 1}, { 0, 1, 1, 1}, {1, 1, 1, 129} }, /* 5 */
+ { {128, 0, 0, 1}, {0, 0, 1, 1}, { 0, 1, 1, 1}, {1, 1, 1, 129} }, /* 6 */
+ { {128, 0, 0, 0}, {0, 0, 0, 1}, { 0, 0, 1, 1}, {0, 1, 1, 129} }, /* 7 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 1}, {0, 0, 1, 129} }, /* 8 */
+ { {128, 0, 1, 1}, {0, 1, 1, 1}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 9 */
+ { {128, 0, 0, 0}, {0, 0, 0, 1}, { 0, 1, 1, 1}, {1, 1, 1, 129} }, /* 10 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 1}, {0, 1, 1, 129} }, /* 11 */
+ { {128, 0, 0, 1}, {0, 1, 1, 1}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 12 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 13 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, { 1, 1, 1, 1}, {1, 1, 1, 129} }, /* 14 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 0}, {1, 1, 1, 129} }, /* 15 */
+ { {128, 0, 0, 0}, {1, 0, 0, 0}, { 1, 1, 1, 0}, {1, 1, 1, 129} }, /* 16 */
+ { {128, 1, 129, 1}, {0, 0, 0, 1}, { 0, 0, 0, 0}, {0, 0, 0, 0} }, /* 17 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {129, 0, 0, 0}, {1, 1, 1, 0} }, /* 18 */
+ { {128, 1, 129, 1}, {0, 0, 1, 1}, { 0, 0, 0, 1}, {0, 0, 0, 0} }, /* 19 */
+ { {128, 0, 129, 1}, {0, 0, 0, 1}, { 0, 0, 0, 0}, {0, 0, 0, 0} }, /* 20 */
+ { {128, 0, 0, 0}, {1, 0, 0, 0}, {129, 1, 0, 0}, {1, 1, 1, 0} }, /* 21 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {129, 0, 0, 0}, {1, 1, 0, 0} }, /* 22 */
+ { {128, 1, 1, 1}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {0, 0, 0, 129} }, /* 23 */
+ { {128, 0, 129, 1}, {0, 0, 0, 1}, { 0, 0, 0, 1}, {0, 0, 0, 0} }, /* 24 */
+ { {128, 0, 0, 0}, {1, 0, 0, 0}, {129, 0, 0, 0}, {1, 1, 0, 0} }, /* 25 */
+ { {128, 1, 129, 0}, {0, 1, 1, 0}, { 0, 1, 1, 0}, {0, 1, 1, 0} }, /* 26 */
+ { {128, 0, 129, 1}, {0, 1, 1, 0}, { 0, 1, 1, 0}, {1, 1, 0, 0} }, /* 27 */
+ { {128, 0, 0, 1}, {0, 1, 1, 1}, {129, 1, 1, 0}, {1, 0, 0, 0} }, /* 28 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, {129, 1, 1, 1}, {0, 0, 0, 0} }, /* 29 */
+ { {128, 1, 129, 1}, {0, 0, 0, 1}, { 1, 0, 0, 0}, {1, 1, 1, 0} }, /* 30 */
+ { {128, 0, 129, 1}, {1, 0, 0, 1}, { 1, 0, 0, 1}, {1, 1, 0, 0} }, /* 31 */
+ { {128, 1, 0, 1}, {0, 1, 0, 1}, { 0, 1, 0, 1}, {0, 1, 0, 129} }, /* 32 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, { 0, 0, 0, 0}, {1, 1, 1, 129} }, /* 33 */
+ { {128, 1, 0, 1}, {1, 0, 129, 0}, { 0, 1, 0, 1}, {1, 0, 1, 0} }, /* 34 */
+ { {128, 0, 1, 1}, {0, 0, 1, 1}, {129, 1, 0, 0}, {1, 1, 0, 0} }, /* 35 */
+ { {128, 0, 129, 1}, {1, 1, 0, 0}, { 0, 0, 1, 1}, {1, 1, 0, 0} }, /* 36 */
+ { {128, 1, 0, 1}, {0, 1, 0, 1}, {129, 0, 1, 0}, {1, 0, 1, 0} }, /* 37 */
+ { {128, 1, 1, 0}, {1, 0, 0, 1}, { 0, 1, 1, 0}, {1, 0, 0, 129} }, /* 38 */
+ { {128, 1, 0, 1}, {1, 0, 1, 0}, { 1, 0, 1, 0}, {0, 1, 0, 129} }, /* 39 */
+ { {128, 1, 129, 1}, {0, 0, 1, 1}, { 1, 1, 0, 0}, {1, 1, 1, 0} }, /* 40 */
+ { {128, 0, 0, 1}, {0, 0, 1, 1}, {129, 1, 0, 0}, {1, 0, 0, 0} }, /* 41 */
+ { {128, 0, 129, 1}, {0, 0, 1, 0}, { 0, 1, 0, 0}, {1, 1, 0, 0} }, /* 42 */
+ { {128, 0, 129, 1}, {1, 0, 1, 1}, { 1, 1, 0, 1}, {1, 1, 0, 0} }, /* 43 */
+ { {128, 1, 129, 0}, {1, 0, 0, 1}, { 1, 0, 0, 1}, {0, 1, 1, 0} }, /* 44 */
+ { {128, 0, 1, 1}, {1, 1, 0, 0}, { 1, 1, 0, 0}, {0, 0, 1, 129} }, /* 45 */
+ { {128, 1, 1, 0}, {0, 1, 1, 0}, { 1, 0, 0, 1}, {1, 0, 0, 129} }, /* 46 */
+ { {128, 0, 0, 0}, {0, 1, 129, 0}, { 0, 1, 1, 0}, {0, 0, 0, 0} }, /* 47 */
+ { {128, 1, 0, 0}, {1, 1, 129, 0}, { 0, 1, 0, 0}, {0, 0, 0, 0} }, /* 48 */
+ { {128, 0, 129, 0}, {0, 1, 1, 1}, { 0, 0, 1, 0}, {0, 0, 0, 0} }, /* 49 */
+ { {128, 0, 0, 0}, {0, 0, 129, 0}, { 0, 1, 1, 1}, {0, 0, 1, 0} }, /* 50 */
+ { {128, 0, 0, 0}, {0, 1, 0, 0}, {129, 1, 1, 0}, {0, 1, 0, 0} }, /* 51 */
+ { {128, 1, 1, 0}, {1, 1, 0, 0}, { 1, 0, 0, 1}, {0, 0, 1, 129} }, /* 52 */
+ { {128, 0, 1, 1}, {0, 1, 1, 0}, { 1, 1, 0, 0}, {1, 0, 0, 129} }, /* 53 */
+ { {128, 1, 129, 0}, {0, 0, 1, 1}, { 1, 0, 0, 1}, {1, 1, 0, 0} }, /* 54 */
+ { {128, 0, 129, 1}, {1, 0, 0, 1}, { 1, 1, 0, 0}, {0, 1, 1, 0} }, /* 55 */
+ { {128, 1, 1, 0}, {1, 1, 0, 0}, { 1, 1, 0, 0}, {1, 0, 0, 129} }, /* 56 */
+ { {128, 1, 1, 0}, {0, 0, 1, 1}, { 0, 0, 1, 1}, {1, 0, 0, 129} }, /* 57 */
+ { {128, 1, 1, 1}, {1, 1, 1, 0}, { 1, 0, 0, 0}, {0, 0, 0, 129} }, /* 58 */
+ { {128, 0, 0, 1}, {1, 0, 0, 0}, { 1, 1, 1, 0}, {0, 1, 1, 129} }, /* 59 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, { 0, 0, 1, 1}, {0, 0, 1, 129} }, /* 60 */
+ { {128, 0, 129, 1}, {0, 0, 1, 1}, { 1, 1, 1, 1}, {0, 0, 0, 0} }, /* 61 */
+ { {128, 0, 129, 0}, {0, 0, 1, 0}, { 1, 1, 1, 0}, {1, 1, 1, 0} }, /* 62 */
+ { {128, 1, 0, 0}, {0, 1, 0, 0}, { 0, 1, 1, 1}, {0, 1, 1, 129} } /* 63 */
+ },
+ { /* Partition table for 3-subset BPTC */
+ { {128, 0, 1, 129}, {0, 0, 1, 1}, { 0, 2, 2, 1}, { 2, 2, 2, 130} }, /* 0 */
+ { {128, 0, 0, 129}, {0, 0, 1, 1}, {130, 2, 1, 1}, { 2, 2, 2, 1} }, /* 1 */
+ { {128, 0, 0, 0}, {2, 0, 0, 1}, {130, 2, 1, 1}, { 2, 2, 1, 129} }, /* 2 */
+ { {128, 2, 2, 130}, {0, 0, 2, 2}, { 0, 0, 1, 1}, { 0, 1, 1, 129} }, /* 3 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {129, 1, 2, 2}, { 1, 1, 2, 130} }, /* 4 */
+ { {128, 0, 1, 129}, {0, 0, 1, 1}, { 0, 0, 2, 2}, { 0, 0, 2, 130} }, /* 5 */
+ { {128, 0, 2, 130}, {0, 0, 2, 2}, { 1, 1, 1, 1}, { 1, 1, 1, 129} }, /* 6 */
+ { {128, 0, 1, 1}, {0, 0, 1, 1}, {130, 2, 1, 1}, { 2, 2, 1, 129} }, /* 7 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {129, 1, 1, 1}, { 2, 2, 2, 130} }, /* 8 */
+ { {128, 0, 0, 0}, {1, 1, 1, 1}, {129, 1, 1, 1}, { 2, 2, 2, 130} }, /* 9 */
+ { {128, 0, 0, 0}, {1, 1, 129, 1}, { 2, 2, 2, 2}, { 2, 2, 2, 130} }, /* 10 */
+ { {128, 0, 1, 2}, {0, 0, 129, 2}, { 0, 0, 1, 2}, { 0, 0, 1, 130} }, /* 11 */
+ { {128, 1, 1, 2}, {0, 1, 129, 2}, { 0, 1, 1, 2}, { 0, 1, 1, 130} }, /* 12 */
+ { {128, 1, 2, 2}, {0, 129, 2, 2}, { 0, 1, 2, 2}, { 0, 1, 2, 130} }, /* 13 */
+ { {128, 0, 1, 129}, {0, 1, 1, 2}, { 1, 1, 2, 2}, { 1, 2, 2, 130} }, /* 14 */
+ { {128, 0, 1, 129}, {2, 0, 0, 1}, {130, 2, 0, 0}, { 2, 2, 2, 0} }, /* 15 */
+ { {128, 0, 0, 129}, {0, 0, 1, 1}, { 0, 1, 1, 2}, { 1, 1, 2, 130} }, /* 16 */
+ { {128, 1, 1, 129}, {0, 0, 1, 1}, {130, 0, 0, 1}, { 2, 2, 0, 0} }, /* 17 */
+ { {128, 0, 0, 0}, {1, 1, 2, 2}, {129, 1, 2, 2}, { 1, 1, 2, 130} }, /* 18 */
+ { {128, 0, 2, 130}, {0, 0, 2, 2}, { 0, 0, 2, 2}, { 1, 1, 1, 129} }, /* 19 */
+ { {128, 1, 1, 129}, {0, 1, 1, 1}, { 0, 2, 2, 2}, { 0, 2, 2, 130} }, /* 20 */
+ { {128, 0, 0, 129}, {0, 0, 0, 1}, {130, 2, 2, 1}, { 2, 2, 2, 1} }, /* 21 */
+ { {128, 0, 0, 0}, {0, 0, 129, 1}, { 0, 1, 2, 2}, { 0, 1, 2, 130} }, /* 22 */
+ { {128, 0, 0, 0}, {1, 1, 0, 0}, {130, 2, 129, 0}, { 2, 2, 1, 0} }, /* 23 */
+ { {128, 1, 2, 130}, {0, 129, 2, 2}, { 0, 0, 1, 1}, { 0, 0, 0, 0} }, /* 24 */
+ { {128, 0, 1, 2}, {0, 0, 1, 2}, {129, 1, 2, 2}, { 2, 2, 2, 130} }, /* 25 */
+ { {128, 1, 1, 0}, {1, 2, 130, 1}, {129, 2, 2, 1}, { 0, 1, 1, 0} }, /* 26 */
+ { {128, 0, 0, 0}, {0, 1, 129, 0}, { 1, 2, 130, 1}, { 1, 2, 2, 1} }, /* 27 */
+ { {128, 0, 2, 2}, {1, 1, 0, 2}, {129, 1, 0, 2}, { 0, 0, 2, 130} }, /* 28 */
+ { {128, 1, 1, 0}, {0, 129, 1, 0}, { 2, 0, 0, 2}, { 2, 2, 2, 130} }, /* 29 */
+ { {128, 0, 1, 1}, {0, 1, 2, 2}, { 0, 1, 130, 2}, { 0, 0, 1, 129} }, /* 30 */
+ { {128, 0, 0, 0}, {2, 0, 0, 0}, {130, 2, 1, 1}, { 2, 2, 2, 129} }, /* 31 */
+ { {128, 0, 0, 0}, {0, 0, 0, 2}, {129, 1, 2, 2}, { 1, 2, 2, 130} }, /* 32 */
+ { {128, 2, 2, 130}, {0, 0, 2, 2}, { 0, 0, 1, 2}, { 0, 0, 1, 129} }, /* 33 */
+ { {128, 0, 1, 129}, {0, 0, 1, 2}, { 0, 0, 2, 2}, { 0, 2, 2, 130} }, /* 34 */
+ { {128, 1, 2, 0}, {0, 129, 2, 0}, { 0, 1, 130, 0}, { 0, 1, 2, 0} }, /* 35 */
+ { {128, 0, 0, 0}, {1, 1, 129, 1}, { 2, 2, 130, 2}, { 0, 0, 0, 0} }, /* 36 */
+ { {128, 1, 2, 0}, {1, 2, 0, 1}, {130, 0, 129, 2}, { 0, 1, 2, 0} }, /* 37 */
+ { {128, 1, 2, 0}, {2, 0, 1, 2}, {129, 130, 0, 1}, { 0, 1, 2, 0} }, /* 38 */
+ { {128, 0, 1, 1}, {2, 2, 0, 0}, { 1, 1, 130, 2}, { 0, 0, 1, 129} }, /* 39 */
+ { {128, 0, 1, 1}, {1, 1, 130, 2}, { 2, 2, 0, 0}, { 0, 0, 1, 129} }, /* 40 */
+ { {128, 1, 0, 129}, {0, 1, 0, 1}, { 2, 2, 2, 2}, { 2, 2, 2, 130} }, /* 41 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, {130, 1, 2, 1}, { 2, 1, 2, 129} }, /* 42 */
+ { {128, 0, 2, 2}, {1, 129, 2, 2}, { 0, 0, 2, 2}, { 1, 1, 2, 130} }, /* 43 */
+ { {128, 0, 2, 130}, {0, 0, 1, 1}, { 0, 0, 2, 2}, { 0, 0, 1, 129} }, /* 44 */
+ { {128, 2, 2, 0}, {1, 2, 130, 1}, { 0, 2, 2, 0}, { 1, 2, 2, 129} }, /* 45 */
+ { {128, 1, 0, 1}, {2, 2, 130, 2}, { 2, 2, 2, 2}, { 0, 1, 0, 129} }, /* 46 */
+ { {128, 0, 0, 0}, {2, 1, 2, 1}, {130, 1, 2, 1}, { 2, 1, 2, 129} }, /* 47 */
+ { {128, 1, 0, 129}, {0, 1, 0, 1}, { 0, 1, 0, 1}, { 2, 2, 2, 130} }, /* 48 */
+ { {128, 2, 2, 130}, {0, 1, 1, 1}, { 0, 2, 2, 2}, { 0, 1, 1, 129} }, /* 49 */
+ { {128, 0, 0, 2}, {1, 129, 1, 2}, { 0, 0, 0, 2}, { 1, 1, 1, 130} }, /* 50 */
+ { {128, 0, 0, 0}, {2, 129, 1, 2}, { 2, 1, 1, 2}, { 2, 1, 1, 130} }, /* 51 */
+ { {128, 2, 2, 2}, {0, 129, 1, 1}, { 0, 1, 1, 1}, { 0, 2, 2, 130} }, /* 52 */
+ { {128, 0, 0, 2}, {1, 1, 1, 2}, {129, 1, 1, 2}, { 0, 0, 0, 130} }, /* 53 */
+ { {128, 1, 1, 0}, {0, 129, 1, 0}, { 0, 1, 1, 0}, { 2, 2, 2, 130} }, /* 54 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 2, 1, 129, 2}, { 2, 1, 1, 130} }, /* 55 */
+ { {128, 1, 1, 0}, {0, 129, 1, 0}, { 2, 2, 2, 2}, { 2, 2, 2, 130} }, /* 56 */
+ { {128, 0, 2, 2}, {0, 0, 1, 1}, { 0, 0, 129, 1}, { 0, 0, 2, 130} }, /* 57 */
+ { {128, 0, 2, 2}, {1, 1, 2, 2}, {129, 1, 2, 2}, { 0, 0, 2, 130} }, /* 58 */
+ { {128, 0, 0, 0}, {0, 0, 0, 0}, { 0, 0, 0, 0}, { 2, 129, 1, 130} }, /* 59 */
+ { {128, 0, 0, 130}, {0, 0, 0, 1}, { 0, 0, 0, 2}, { 0, 0, 0, 129} }, /* 60 */
+ { {128, 2, 2, 2}, {1, 2, 2, 2}, { 0, 2, 2, 2}, {129, 2, 2, 130} }, /* 61 */
+ { {128, 1, 0, 129}, {2, 2, 2, 2}, { 2, 2, 2, 2}, { 2, 2, 2, 130} }, /* 62 */
+ { {128, 1, 1, 129}, {2, 0, 1, 1}, {130, 2, 0, 1}, { 2, 2, 2, 0} } /* 63 */
+ }
+ };
+
+ static int aWeight2[] = { 0, 21, 43, 64 };
+ static int aWeight3[] = { 0, 9, 18, 27, 37, 46, 55, 64 };
+ static int aWeight4[] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 };
+
+ static unsigned char sModeHasPBits = 0b11001011;
+
+ bcdec__bitstream_t bstream;
+ int mode, partition, numPartitions, numEndpoints, i, j, k, rotation, partitionSet;
+ int indexSelectionBit, indexBits, indexBits2, index, index2;
+ int endpoints[6][4];
+ char indices[4][4];
+ int r, g, b, a;
+ int* weights, * weights2;
+ unsigned char* decompressed;
+
+ decompressed = (unsigned char*)decompressedBlock;
+
+ bstream.low = ((unsigned long long*)compressedBlock)[0];
+ bstream.high = ((unsigned long long*)compressedBlock)[1];
+
+ for (mode = 0; mode < 8 && (0 == bcdec__bitstream_read_bit(&bstream)); ++mode);
+
+ /* unexpected mode, clear the block (transparent black) */
+ if (mode >= 8) {
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ decompressed[j * 4 + 0] = 0;
+ decompressed[j * 4 + 1] = 0;
+ decompressed[j * 4 + 2] = 0;
+ decompressed[j * 4 + 3] = 0;
+ }
+ decompressed += destinationPitch;
+ }
+
+ return;
+ }
+
+ partition = 0;
+ numPartitions = 1;
+ rotation = 0;
+ indexSelectionBit = 0;
+
+ if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 7) {
+ numPartitions = (mode == 0 || mode == 2) ? 3 : 2;
+ partition = bcdec__bitstream_read_bits(&bstream, (mode == 0) ? 4 : 6);
+ }
+
+ numEndpoints = numPartitions * 2;
+
+ if (mode == 4 || mode == 5) {
+ rotation = bcdec__bitstream_read_bits(&bstream, 2);
+
+ if (mode == 4) {
+ indexSelectionBit = bcdec__bitstream_read_bit(&bstream);
+ }
+ }
+
+ /* Extract endpoints */
+ /* RGB */
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < numEndpoints; ++j) {
+ endpoints[j][i] = bcdec__bitstream_read_bits(&bstream, actual_bits_count[0][mode]);
+ }
+ }
+ /* Alpha (if any) */
+ if (actual_bits_count[1][mode] > 0) {
+ for (j = 0; j < numEndpoints; ++j) {
+ endpoints[j][3] = bcdec__bitstream_read_bits(&bstream, actual_bits_count[1][mode]);
+ }
+ }
+
+ /* Fully decode endpoints */
+ /* First handle modes that have P-bits */
+ if (mode == 0 || mode == 1 || mode == 3 || mode == 6 || mode == 7) {
+ for (i = 0; i < numEndpoints; ++i) {
+ /* component-wise left-shift */
+ for (j = 0; j < 4; ++j) {
+ endpoints[i][j] <<= 1;
+ }
+ }
+
+ /* if P-bit is shared */
+ if (mode == 1) {
+ i = bcdec__bitstream_read_bit(&bstream);
+ j = bcdec__bitstream_read_bit(&bstream);
+
+ /* rgb component-wise insert pbits */
+ for (k = 0; k < 3; ++k) {
+ endpoints[0][k] |= i;
+ endpoints[1][k] |= i;
+ endpoints[2][k] |= j;
+ endpoints[3][k] |= j;
+ }
+ } else if (sModeHasPBits & (1 << mode)) {
+ /* unique P-bit per endpoint */
+ for (i = 0; i < numEndpoints; ++i) {
+ j = bcdec__bitstream_read_bit(&bstream);
+ for (k = 0; k < 4; ++k) {
+ endpoints[i][k] |= j;
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < numEndpoints; ++i) {
+ /* get color components precision including pbit */
+ j = actual_bits_count[0][mode] + ((sModeHasPBits >> mode) & 1);
+
+ for (k = 0; k < 3; ++k) {
+ /* left shift endpoint components so that their MSB lies in bit 7 */
+ endpoints[i][k] = endpoints[i][k] << (8 - j);
+ /* Replicate each component's MSB into the LSBs revealed by the left-shift operation above */
+ endpoints[i][k] = endpoints[i][k] | (endpoints[i][k] >> j);
+ }
+
+ /* get alpha component precision including pbit */
+ j = actual_bits_count[1][mode] + ((sModeHasPBits >> mode) & 1);
+
+ /* left shift endpoint components so that their MSB lies in bit 7 */
+ endpoints[i][3] = endpoints[i][3] << (8 - j);
+ /* Replicate each component's MSB into the LSBs revealed by the left-shift operation above */
+ endpoints[i][3] = endpoints[i][3] | (endpoints[i][3] >> j);
+ }
+
+ /* If this mode does not explicitly define the alpha component */
+ /* set alpha equal to 1.0 */
+ if (!actual_bits_count[1][mode]) {
+ for (j = 0; j < numEndpoints; ++j) {
+ endpoints[j][3] = 0xFF;
+ }
+ }
+
+ /* Determine weights tables */
+ indexBits = (mode == 0 || mode == 1) ? 3 : ((mode == 6) ? 4 : 2);
+ indexBits2 = (mode == 4) ? 3 : ((mode == 5) ? 2 : 0);
+ weights = (indexBits == 2) ? aWeight2 : ((indexBits == 3) ? aWeight3 : aWeight4);
+ weights2 = (indexBits2 == 2) ? aWeight2 : aWeight3;
+
+ /* Quite inconvenient that indices aren't interleaved so we have to make 2 passes here */
+ /* Pass #1: collecting color indices */
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ partitionSet = (numPartitions == 1) ? ((i | j) ? 0 : 128) : partition_sets[numPartitions - 2][partition][i][j];
+
+ indexBits = (mode == 0 || mode == 1) ? 3 : ((mode == 6) ? 4 : 2);
+ /* fix-up index is specified with one less bit */
+ /* The fix-up index for subset 0 is always index 0 */
+ if (partitionSet & 0x80) {
+ indexBits--;
+ }
+
+ indices[i][j] = bcdec__bitstream_read_bits(&bstream, indexBits);
+ }
+ }
+
+ /* Pass #2: reading alpha indices (if any) and interpolating & rotating */
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 4; ++j) {
+ partitionSet = (numPartitions == 1) ? ((i|j) ? 0 : 128) : partition_sets[numPartitions - 2][partition][i][j];
+ partitionSet &= 0x03;
+
+ index = indices[i][j];
+
+ if (!indexBits2) {
+ r = bcdec__interpolate(endpoints[partitionSet * 2][0], endpoints[partitionSet * 2 + 1][0], weights, index);
+ g = bcdec__interpolate(endpoints[partitionSet * 2][1], endpoints[partitionSet * 2 + 1][1], weights, index);
+ b = bcdec__interpolate(endpoints[partitionSet * 2][2], endpoints[partitionSet * 2 + 1][2], weights, index);
+ a = bcdec__interpolate(endpoints[partitionSet * 2][3], endpoints[partitionSet * 2 + 1][3], weights, index);
+ } else {
+ index2 = bcdec__bitstream_read_bits(&bstream, (i|j) ? indexBits2 : (indexBits2 - 1));
+ /* The index value for interpolating color comes from the secondary index bits for the texel
+ if the mode has an index selection bit and its value is one, and from the primary index bits otherwise.
+ The alpha index comes from the secondary index bits if the block has a secondary index and
+ the block either doesn’t have an index selection bit or that bit is zero, and from the primary index bits otherwise. */
+ if (!indexSelectionBit) {
+ r = bcdec__interpolate(endpoints[partitionSet * 2][0], endpoints[partitionSet * 2 + 1][0], weights, index);
+ g = bcdec__interpolate(endpoints[partitionSet * 2][1], endpoints[partitionSet * 2 + 1][1], weights, index);
+ b = bcdec__interpolate(endpoints[partitionSet * 2][2], endpoints[partitionSet * 2 + 1][2], weights, index);
+ a = bcdec__interpolate(endpoints[partitionSet * 2][3], endpoints[partitionSet * 2 + 1][3], weights2, index2);
+ } else {
+ r = bcdec__interpolate(endpoints[partitionSet * 2][0], endpoints[partitionSet * 2 + 1][0], weights2, index2);
+ g = bcdec__interpolate(endpoints[partitionSet * 2][1], endpoints[partitionSet * 2 + 1][1], weights2, index2);
+ b = bcdec__interpolate(endpoints[partitionSet * 2][2], endpoints[partitionSet * 2 + 1][2], weights2, index2);
+ a = bcdec__interpolate(endpoints[partitionSet * 2][3], endpoints[partitionSet * 2 + 1][3], weights, index);
+ }
+ }
+
+ switch (rotation) {
+ case 1: { /* 01 – Block format is Scalar(R) Vector(AGB) - swap A and R */
+ bcdec__swap_values(&a, &r);
+ } break;
+ case 2: { /* 10 – Block format is Scalar(G) Vector(RAB) - swap A and G */
+ bcdec__swap_values(&a, &g);
+ } break;
+ case 3: { /* 11 - Block format is Scalar(B) Vector(RGA) - swap A and B */
+ bcdec__swap_values(&a, &b);
+ } break;
+ }
+
+ decompressed[j * 4 + 0] = r;
+ decompressed[j * 4 + 1] = g;
+ decompressed[j * 4 + 2] = b;
+ decompressed[j * 4 + 3] = a;
+ }
+
+ decompressed += destinationPitch;
+ }
+}
+
+#endif /* BCDEC_IMPLEMENTATION */
+
+/* LICENSE:
+
+This software is available under 2 licenses -- choose whichever you prefer.
+
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+
+Copyright (c) 2022 Sergii Kudlai
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+------------------------------------------------------------------------------
+ALTERNATIVE B - The Unlicense
+
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
+
+*/
diff --git a/thirdparty/squish/LICENSE.txt b/thirdparty/squish/LICENSE.txt
deleted file mode 100644
index e491e36226..0000000000
--- a/thirdparty/squish/LICENSE.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/thirdparty/squish/alpha.cpp b/thirdparty/squish/alpha.cpp
deleted file mode 100644
index 7039c1a3b8..0000000000
--- a/thirdparty/squish/alpha.cpp
+++ /dev/null
@@ -1,350 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "alpha.h"
-
-#include <climits>
-#include <algorithm>
-
-namespace squish {
-
-static int FloatToInt( float a, int limit )
-{
- // use ANSI round-to-zero behaviour to get round-to-nearest
- int i = ( int )( a + 0.5f );
-
- // clamp to the limit
- if( i < 0 )
- i = 0;
- else if( i > limit )
- i = limit;
-
- // done
- return i;
-}
-
-void CompressAlphaDxt3( u8 const* rgba, int mask, void* block )
-{
- u8* bytes = reinterpret_cast< u8* >( block );
-
- // quantise and pack the alpha values pairwise
- for( int i = 0; i < 8; ++i )
- {
- // quantise down to 4 bits
- float alpha1 = ( float )rgba[8*i + 3] * ( 15.0f/255.0f );
- float alpha2 = ( float )rgba[8*i + 7] * ( 15.0f/255.0f );
- int quant1 = FloatToInt( alpha1, 15 );
- int quant2 = FloatToInt( alpha2, 15 );
-
- // set alpha to zero where masked
- int bit1 = 1 << ( 2*i );
- int bit2 = 1 << ( 2*i + 1 );
- if( ( mask & bit1 ) == 0 )
- quant1 = 0;
- if( ( mask & bit2 ) == 0 )
- quant2 = 0;
-
- // pack into the byte
- bytes[i] = ( u8 )( quant1 | ( quant2 << 4 ) );
- }
-}
-
-void DecompressAlphaDxt3( u8* rgba, void const* block )
-{
- u8 const* bytes = reinterpret_cast< u8 const* >( block );
-
- // unpack the alpha values pairwise
- for( int i = 0; i < 8; ++i )
- {
- // quantise down to 4 bits
- u8 quant = bytes[i];
-
- // unpack the values
- u8 lo = quant & 0x0f;
- u8 hi = quant & 0xf0;
-
- // convert back up to bytes
- rgba[8*i + 3] = lo | ( lo << 4 );
- rgba[8*i + 7] = hi | ( hi >> 4 );
- }
-}
-
-static void FixRange( int& min, int& max, int steps )
-{
- if( max - min < steps )
- max = std::min( min + steps, 255 );
- if( max - min < steps )
- min = std::max( 0, max - steps );
-}
-
-static int FitCodes( u8 const* rgba, int mask, u8 const* codes, u8* indices )
-{
- // fit each alpha value to the codebook
- int err = 0;
- for( int i = 0; i < 16; ++i )
- {
- // check this pixel is valid
- int bit = 1 << i;
- if( ( mask & bit ) == 0 )
- {
- // use the first code
- indices[i] = 0;
- continue;
- }
-
- // find the least error and corresponding index
- int value = rgba[4*i + 3];
- int least = INT_MAX;
- int index = 0;
- for( int j = 0; j < 8; ++j )
- {
- // get the squared error from this code
- int dist = ( int )value - ( int )codes[j];
- dist *= dist;
-
- // compare with the best so far
- if( dist < least )
- {
- least = dist;
- index = j;
- }
- }
-
- // save this index and accumulate the error
- indices[i] = ( u8 )index;
- err += least;
- }
-
- // return the total error
- return err;
-}
-
-static void WriteAlphaBlock( int alpha0, int alpha1, u8 const* indices, void* block )
-{
- u8* bytes = reinterpret_cast< u8* >( block );
-
- // write the first two bytes
- bytes[0] = ( u8 )alpha0;
- bytes[1] = ( u8 )alpha1;
-
- // pack the indices with 3 bits each
- u8* dest = bytes + 2;
- u8 const* src = indices;
- for( int i = 0; i < 2; ++i )
- {
- // pack 8 3-bit values
- int value = 0;
- for( int j = 0; j < 8; ++j )
- {
- int index = *src++;
- value |= ( index << 3*j );
- }
-
- // store in 3 bytes
- for( int j = 0; j < 3; ++j )
- {
- int byte = ( value >> 8*j ) & 0xff;
- *dest++ = ( u8 )byte;
- }
- }
-}
-
-static void WriteAlphaBlock5( int alpha0, int alpha1, u8 const* indices, void* block )
-{
- // check the relative values of the endpoints
- if( alpha0 > alpha1 )
- {
- // swap the indices
- u8 swapped[16];
- for( int i = 0; i < 16; ++i )
- {
- u8 index = indices[i];
- if( index == 0 )
- swapped[i] = 1;
- else if( index == 1 )
- swapped[i] = 0;
- else if( index <= 5 )
- swapped[i] = 7 - index;
- else
- swapped[i] = index;
- }
-
- // write the block
- WriteAlphaBlock( alpha1, alpha0, swapped, block );
- }
- else
- {
- // write the block
- WriteAlphaBlock( alpha0, alpha1, indices, block );
- }
-}
-
-static void WriteAlphaBlock7( int alpha0, int alpha1, u8 const* indices, void* block )
-{
- // check the relative values of the endpoints
- if( alpha0 < alpha1 )
- {
- // swap the indices
- u8 swapped[16];
- for( int i = 0; i < 16; ++i )
- {
- u8 index = indices[i];
- if( index == 0 )
- swapped[i] = 1;
- else if( index == 1 )
- swapped[i] = 0;
- else
- swapped[i] = 9 - index;
- }
-
- // write the block
- WriteAlphaBlock( alpha1, alpha0, swapped, block );
- }
- else
- {
- // write the block
- WriteAlphaBlock( alpha0, alpha1, indices, block );
- }
-}
-
-void CompressAlphaDxt5( u8 const* rgba, int mask, void* block )
-{
- // get the range for 5-alpha and 7-alpha interpolation
- int min5 = 255;
- int max5 = 0;
- int min7 = 255;
- int max7 = 0;
- for( int i = 0; i < 16; ++i )
- {
- // check this pixel is valid
- int bit = 1 << i;
- if( ( mask & bit ) == 0 )
- continue;
-
- // incorporate into the min/max
- int value = rgba[4*i + 3];
- if( value < min7 )
- min7 = value;
- if( value > max7 )
- max7 = value;
- if( value != 0 && value < min5 )
- min5 = value;
- if( value != 255 && value > max5 )
- max5 = value;
- }
-
- // handle the case that no valid range was found
- if( min5 > max5 )
- min5 = max5;
- if( min7 > max7 )
- min7 = max7;
-
- // fix the range to be the minimum in each case
- FixRange( min5, max5, 5 );
- FixRange( min7, max7, 7 );
-
- // set up the 5-alpha code book
- u8 codes5[8];
- codes5[0] = ( u8 )min5;
- codes5[1] = ( u8 )max5;
- for( int i = 1; i < 5; ++i )
- codes5[1 + i] = ( u8 )( ( ( 5 - i )*min5 + i*max5 )/5 );
- codes5[6] = 0;
- codes5[7] = 255;
-
- // set up the 7-alpha code book
- u8 codes7[8];
- codes7[0] = ( u8 )min7;
- codes7[1] = ( u8 )max7;
- for( int i = 1; i < 7; ++i )
- codes7[1 + i] = ( u8 )( ( ( 7 - i )*min7 + i*max7 )/7 );
-
- // fit the data to both code books
- u8 indices5[16];
- u8 indices7[16];
- int err5 = FitCodes( rgba, mask, codes5, indices5 );
- int err7 = FitCodes( rgba, mask, codes7, indices7 );
-
- // save the block with least error
- if( err5 <= err7 )
- WriteAlphaBlock5( min5, max5, indices5, block );
- else
- WriteAlphaBlock7( min7, max7, indices7, block );
-}
-
-void DecompressAlphaDxt5( u8* rgba, void const* block )
-{
- // get the two alpha values
- u8 const* bytes = reinterpret_cast< u8 const* >( block );
- int alpha0 = bytes[0];
- int alpha1 = bytes[1];
-
- // compare the values to build the codebook
- u8 codes[8];
- codes[0] = ( u8 )alpha0;
- codes[1] = ( u8 )alpha1;
- if( alpha0 <= alpha1 )
- {
- // use 5-alpha codebook
- for( int i = 1; i < 5; ++i )
- codes[1 + i] = ( u8 )( ( ( 5 - i )*alpha0 + i*alpha1 )/5 );
- codes[6] = 0;
- codes[7] = 255;
- }
- else
- {
- // use 7-alpha codebook
- for( int i = 1; i < 7; ++i )
- codes[1 + i] = ( u8 )( ( ( 7 - i )*alpha0 + i*alpha1 )/7 );
- }
-
- // decode the indices
- u8 indices[16];
- u8 const* src = bytes + 2;
- u8* dest = indices;
- for( int i = 0; i < 2; ++i )
- {
- // grab 3 bytes
- int value = 0;
- for( int j = 0; j < 3; ++j )
- {
- int byte = *src++;
- value |= ( byte << 8*j );
- }
-
- // unpack 8 3-bit values from it
- for( int j = 0; j < 8; ++j )
- {
- int index = ( value >> 3*j ) & 0x7;
- *dest++ = ( u8 )index;
- }
- }
-
- // write out the indexed codebook values
- for( int i = 0; i < 16; ++i )
- rgba[4*i + 3] = codes[indices[i]];
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/alpha.h b/thirdparty/squish/alpha.h
deleted file mode 100644
index a1fffd4049..0000000000
--- a/thirdparty/squish/alpha.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_ALPHA_H
-#define SQUISH_ALPHA_H
-
-#include "squish.h"
-
-namespace squish {
-
-void CompressAlphaDxt3( u8 const* rgba, int mask, void* block );
-void CompressAlphaDxt5( u8 const* rgba, int mask, void* block );
-
-void DecompressAlphaDxt3( u8* rgba, void const* block );
-void DecompressAlphaDxt5( u8* rgba, void const* block );
-
-} // namespace squish
-
-#endif // ndef SQUISH_ALPHA_H
diff --git a/thirdparty/squish/clusterfit.cpp b/thirdparty/squish/clusterfit.cpp
deleted file mode 100644
index 1610ecb5d8..0000000000
--- a/thirdparty/squish/clusterfit.cpp
+++ /dev/null
@@ -1,392 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
- Copyright (c) 2007 Ignacio Castano icastano@nvidia.com
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "clusterfit.h"
-#include "colourset.h"
-#include "colourblock.h"
-#include <cfloat>
-
-namespace squish {
-
-ClusterFit::ClusterFit( ColourSet const* colours, int flags, float* metric )
- : ColourFit( colours, flags )
-{
- // set the iteration count
- m_iterationCount = ( m_flags & kColourIterativeClusterFit ) ? kMaxIterations : 1;
-
- // initialise the metric (old perceptual = 0.2126f, 0.7152f, 0.0722f)
- if( metric )
- m_metric = Vec4( metric[0], metric[1], metric[2], 1.0f );
- else
- m_metric = VEC4_CONST( 1.0f );
-
- // initialise the best error
- m_besterror = VEC4_CONST( FLT_MAX );
-
- // cache some values
- int const count = m_colours->GetCount();
- Vec3 const* values = m_colours->GetPoints();
-
- // get the covariance matrix
- Sym3x3 covariance = ComputeWeightedCovariance( count, values, m_colours->GetWeights() );
-
- // compute the principle component
- m_principle = ComputePrincipleComponent( covariance );
-}
-
-bool ClusterFit::ConstructOrdering( Vec3 const& axis, int iteration )
-{
- // cache some values
- int const count = m_colours->GetCount();
- Vec3 const* values = m_colours->GetPoints();
-
- // build the list of dot products
- float dps[16];
- u8* order = ( u8* )m_order + 16*iteration;
- for( int i = 0; i < count; ++i )
- {
- dps[i] = Dot( values[i], axis );
- order[i] = ( u8 )i;
- }
-
- // stable sort using them
- for( int i = 0; i < count; ++i )
- {
- for( int j = i; j > 0 && dps[j] < dps[j - 1]; --j )
- {
- std::swap( dps[j], dps[j - 1] );
- std::swap( order[j], order[j - 1] );
- }
- }
-
- // check this ordering is unique
- for( int it = 0; it < iteration; ++it )
- {
- u8 const* prev = ( u8* )m_order + 16*it;
- bool same = true;
- for( int i = 0; i < count; ++i )
- {
- if( order[i] != prev[i] )
- {
- same = false;
- break;
- }
- }
- if( same )
- return false;
- }
-
- // copy the ordering and weight all the points
- Vec3 const* unweighted = m_colours->GetPoints();
- float const* weights = m_colours->GetWeights();
- m_xsum_wsum = VEC4_CONST( 0.0f );
- for( int i = 0; i < count; ++i )
- {
- int j = order[i];
- Vec4 p( unweighted[j].X(), unweighted[j].Y(), unweighted[j].Z(), 1.0f );
- Vec4 w( weights[j] );
- Vec4 x = p*w;
- m_points_weights[i] = x;
- m_xsum_wsum += x;
- }
- return true;
-}
-
-void ClusterFit::Compress3( void* block )
-{
- // declare variables
- int const count = m_colours->GetCount();
- Vec4 const two = VEC4_CONST( 2.0 );
- Vec4 const one = VEC4_CONST( 1.0f );
- Vec4 const half_half2( 0.5f, 0.5f, 0.5f, 0.25f );
- Vec4 const zero = VEC4_CONST( 0.0f );
- Vec4 const half = VEC4_CONST( 0.5f );
- Vec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f );
- Vec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f );
-
- // prepare an ordering using the principle axis
- ConstructOrdering( m_principle, 0 );
-
- // check all possible clusters and iterate on the total order
- Vec4 beststart = VEC4_CONST( 0.0f );
- Vec4 bestend = VEC4_CONST( 0.0f );
- Vec4 besterror = m_besterror;
- u8 bestindices[16];
- int bestiteration = 0;
- int besti = 0, bestj = 0;
-
- // loop over iterations (we avoid the case that all points in first or last cluster)
- for( int iterationIndex = 0;; )
- {
- // first cluster [0,i) is at the start
- Vec4 part0 = VEC4_CONST( 0.0f );
- for( int i = 0; i < count; ++i )
- {
- // second cluster [i,j) is half along
- Vec4 part1 = ( i == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f );
- int jmin = ( i == 0 ) ? 1 : i;
- for( int j = jmin;; )
- {
- // last cluster [j,count) is at the end
- Vec4 part2 = m_xsum_wsum - part1 - part0;
-
- // compute least squares terms directly
- Vec4 alphax_sum = MultiplyAdd( part1, half_half2, part0 );
- Vec4 alpha2_sum = alphax_sum.SplatW();
-
- Vec4 betax_sum = MultiplyAdd( part1, half_half2, part2 );
- Vec4 beta2_sum = betax_sum.SplatW();
-
- Vec4 alphabeta_sum = ( part1*half_half2 ).SplatW();
-
- // compute the least-squares optimal points
- Vec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) );
- Vec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor;
- Vec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor;
-
- // clamp to the grid
- a = Min( one, Max( zero, a ) );
- b = Min( one, Max( zero, b ) );
- a = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp;
- b = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp;
-
- // compute the error (we skip the constant xxsum)
- Vec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum );
- Vec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum );
- Vec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 );
- Vec4 e4 = MultiplyAdd( two, e3, e1 );
-
- // apply the metric to the error term
- Vec4 e5 = e4*m_metric;
- Vec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ();
-
- // keep the solution if it wins
- if( CompareAnyLessThan( error, besterror ) )
- {
- beststart = a;
- bestend = b;
- besti = i;
- bestj = j;
- besterror = error;
- bestiteration = iterationIndex;
- }
-
- // advance
- if( j == count )
- break;
- part1 += m_points_weights[j];
- ++j;
- }
-
- // advance
- part0 += m_points_weights[i];
- }
-
- // stop if we didn't improve in this iteration
- if( bestiteration != iterationIndex )
- break;
-
- // advance if possible
- ++iterationIndex;
- if( iterationIndex == m_iterationCount )
- break;
-
- // stop if a new iteration is an ordering that has already been tried
- Vec3 axis = ( bestend - beststart ).GetVec3();
- if( !ConstructOrdering( axis, iterationIndex ) )
- break;
- }
-
- // save the block if necessary
- if( CompareAnyLessThan( besterror, m_besterror ) )
- {
- // remap the indices
- u8 const* order = ( u8* )m_order + 16*bestiteration;
-
- u8 unordered[16];
- for( int m = 0; m < besti; ++m )
- unordered[order[m]] = 0;
- for( int m = besti; m < bestj; ++m )
- unordered[order[m]] = 2;
- for( int m = bestj; m < count; ++m )
- unordered[order[m]] = 1;
-
- m_colours->RemapIndices( unordered, bestindices );
-
- // save the block
- WriteColourBlock3( beststart.GetVec3(), bestend.GetVec3(), bestindices, block );
-
- // save the error
- m_besterror = besterror;
- }
-}
-
-void ClusterFit::Compress4( void* block )
-{
- // declare variables
- int const count = m_colours->GetCount();
- Vec4 const two = VEC4_CONST( 2.0f );
- Vec4 const one = VEC4_CONST( 1.0f );
- Vec4 const onethird_onethird2( 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/9.0f );
- Vec4 const twothirds_twothirds2( 2.0f/3.0f, 2.0f/3.0f, 2.0f/3.0f, 4.0f/9.0f );
- Vec4 const twonineths = VEC4_CONST( 2.0f/9.0f );
- Vec4 const zero = VEC4_CONST( 0.0f );
- Vec4 const half = VEC4_CONST( 0.5f );
- Vec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f );
- Vec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f );
-
- // prepare an ordering using the principle axis
- ConstructOrdering( m_principle, 0 );
-
- // check all possible clusters and iterate on the total order
- Vec4 beststart = VEC4_CONST( 0.0f );
- Vec4 bestend = VEC4_CONST( 0.0f );
- Vec4 besterror = m_besterror;
- u8 bestindices[16];
- int bestiteration = 0;
- int besti = 0, bestj = 0, bestk = 0;
-
- // loop over iterations (we avoid the case that all points in first or last cluster)
- for( int iterationIndex = 0;; )
- {
- // first cluster [0,i) is at the start
- Vec4 part0 = VEC4_CONST( 0.0f );
- for( int i = 0; i < count; ++i )
- {
- // second cluster [i,j) is one third along
- Vec4 part1 = VEC4_CONST( 0.0f );
- for( int j = i;; )
- {
- // third cluster [j,k) is two thirds along
- Vec4 part2 = ( j == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f );
- int kmin = ( j == 0 ) ? 1 : j;
- for( int k = kmin;; )
- {
- // last cluster [k,count) is at the end
- Vec4 part3 = m_xsum_wsum - part2 - part1 - part0;
-
- // compute least squares terms directly
- Vec4 const alphax_sum = MultiplyAdd( part2, onethird_onethird2, MultiplyAdd( part1, twothirds_twothirds2, part0 ) );
- Vec4 const alpha2_sum = alphax_sum.SplatW();
-
- Vec4 const betax_sum = MultiplyAdd( part1, onethird_onethird2, MultiplyAdd( part2, twothirds_twothirds2, part3 ) );
- Vec4 const beta2_sum = betax_sum.SplatW();
-
- Vec4 const alphabeta_sum = twonineths*( part1 + part2 ).SplatW();
-
- // compute the least-squares optimal points
- Vec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) );
- Vec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor;
- Vec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor;
-
- // clamp to the grid
- a = Min( one, Max( zero, a ) );
- b = Min( one, Max( zero, b ) );
- a = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp;
- b = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp;
-
- // compute the error (we skip the constant xxsum)
- Vec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum );
- Vec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum );
- Vec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 );
- Vec4 e4 = MultiplyAdd( two, e3, e1 );
-
- // apply the metric to the error term
- Vec4 e5 = e4*m_metric;
- Vec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ();
-
- // keep the solution if it wins
- if( CompareAnyLessThan( error, besterror ) )
- {
- beststart = a;
- bestend = b;
- besterror = error;
- besti = i;
- bestj = j;
- bestk = k;
- bestiteration = iterationIndex;
- }
-
- // advance
- if( k == count )
- break;
- part2 += m_points_weights[k];
- ++k;
- }
-
- // advance
- if( j == count )
- break;
- part1 += m_points_weights[j];
- ++j;
- }
-
- // advance
- part0 += m_points_weights[i];
- }
-
- // stop if we didn't improve in this iteration
- if( bestiteration != iterationIndex )
- break;
-
- // advance if possible
- ++iterationIndex;
- if( iterationIndex == m_iterationCount )
- break;
-
- // stop if a new iteration is an ordering that has already been tried
- Vec3 axis = ( bestend - beststart ).GetVec3();
- if( !ConstructOrdering( axis, iterationIndex ) )
- break;
- }
-
- // save the block if necessary
- if( CompareAnyLessThan( besterror, m_besterror ) )
- {
- // remap the indices
- u8 const* order = ( u8* )m_order + 16*bestiteration;
-
- u8 unordered[16];
- for( int m = 0; m < besti; ++m )
- unordered[order[m]] = 0;
- for( int m = besti; m < bestj; ++m )
- unordered[order[m]] = 2;
- for( int m = bestj; m < bestk; ++m )
- unordered[order[m]] = 3;
- for( int m = bestk; m < count; ++m )
- unordered[order[m]] = 1;
-
- m_colours->RemapIndices( unordered, bestindices );
-
- // save the block
- WriteColourBlock4( beststart.GetVec3(), bestend.GetVec3(), bestindices, block );
-
- // save the error
- m_besterror = besterror;
- }
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/clusterfit.h b/thirdparty/squish/clusterfit.h
deleted file mode 100644
index 999396b262..0000000000
--- a/thirdparty/squish/clusterfit.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
- Copyright (c) 2007 Ignacio Castano icastano@nvidia.com
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_CLUSTERFIT_H
-#define SQUISH_CLUSTERFIT_H
-
-#include "squish.h"
-#include "maths.h"
-#include "simd.h"
-#include "colourfit.h"
-
-namespace squish {
-
-class ClusterFit : public ColourFit
-{
-public:
- ClusterFit( ColourSet const* colours, int flags, float* metric );
-
-private:
- bool ConstructOrdering( Vec3 const& axis, int iteration );
-
- virtual void Compress3( void* block );
- virtual void Compress4( void* block );
-
- enum { kMaxIterations = 8 };
-
- int m_iterationCount;
- Vec3 m_principle;
- u8 m_order[16*kMaxIterations];
- Vec4 m_points_weights[16];
- Vec4 m_xsum_wsum;
- Vec4 m_metric;
- Vec4 m_besterror;
-};
-
-} // namespace squish
-
-#endif // ndef SQUISH_CLUSTERFIT_H
diff --git a/thirdparty/squish/colourblock.cpp b/thirdparty/squish/colourblock.cpp
deleted file mode 100644
index f14c9362bd..0000000000
--- a/thirdparty/squish/colourblock.cpp
+++ /dev/null
@@ -1,247 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "colourblock.h"
-// -- GODOT start --
-#include "alpha.h"
-// -- GODOT end --
-
-namespace squish {
-
-static int FloatToInt( float a, int limit )
-{
- // use ANSI round-to-zero behaviour to get round-to-nearest
- int i = ( int )( a + 0.5f );
-
- // clamp to the limit
- if( i < 0 )
- i = 0;
- else if( i > limit )
- i = limit;
-
- // done
- return i;
-}
-
-static int FloatTo565( Vec3::Arg colour )
-{
- // get the components in the correct range
- int r = FloatToInt( 31.0f*colour.X(), 31 );
- int g = FloatToInt( 63.0f*colour.Y(), 63 );
- int b = FloatToInt( 31.0f*colour.Z(), 31 );
-
- // pack into a single value
- return ( r << 11 ) | ( g << 5 ) | b;
-}
-
-static void WriteColourBlock( int a, int b, u8* indices, void* block )
-{
- // get the block as bytes
- u8* bytes = ( u8* )block;
-
- // write the endpoints
- bytes[0] = ( u8 )( a & 0xff );
- bytes[1] = ( u8 )( a >> 8 );
- bytes[2] = ( u8 )( b & 0xff );
- bytes[3] = ( u8 )( b >> 8 );
-
- // write the indices
- for( int i = 0; i < 4; ++i )
- {
- u8 const* ind = indices + 4*i;
- bytes[4 + i] = ind[0] | ( ind[1] << 2 ) | ( ind[2] << 4 ) | ( ind[3] << 6 );
- }
-}
-
-void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block )
-{
- // get the packed values
- int a = FloatTo565( start );
- int b = FloatTo565( end );
-
- // remap the indices
- u8 remapped[16];
- if( a <= b )
- {
- // use the indices directly
- for( int i = 0; i < 16; ++i )
- remapped[i] = indices[i];
- }
- else
- {
- // swap a and b
- std::swap( a, b );
- for( int i = 0; i < 16; ++i )
- {
- if( indices[i] == 0 )
- remapped[i] = 1;
- else if( indices[i] == 1 )
- remapped[i] = 0;
- else
- remapped[i] = indices[i];
- }
- }
-
- // write the block
- WriteColourBlock( a, b, remapped, block );
-}
-
-void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block )
-{
- // get the packed values
- int a = FloatTo565( start );
- int b = FloatTo565( end );
-
- // remap the indices
- u8 remapped[16];
- if( a < b )
- {
- // swap a and b
- std::swap( a, b );
- for( int i = 0; i < 16; ++i )
- remapped[i] = ( indices[i] ^ 0x1 ) & 0x3;
- }
- else if( a == b )
- {
- // use index 0
- for( int i = 0; i < 16; ++i )
- remapped[i] = 0;
- }
- else
- {
- // use the indices directly
- for( int i = 0; i < 16; ++i )
- remapped[i] = indices[i];
- }
-
- // write the block
- WriteColourBlock( a, b, remapped, block );
-}
-
-static int Unpack565( u8 const* packed, u8* colour )
-{
- // build the packed value
- int value = ( int )packed[0] | ( ( int )packed[1] << 8 );
-
- // get the components in the stored range
- u8 red = ( u8 )( ( value >> 11 ) & 0x1f );
- u8 green = ( u8 )( ( value >> 5 ) & 0x3f );
- u8 blue = ( u8 )( value & 0x1f );
-
- // scale up to 8 bits
- colour[0] = ( red << 3 ) | ( red >> 2 );
- colour[1] = ( green << 2 ) | ( green >> 4 );
- colour[2] = ( blue << 3 ) | ( blue >> 2 );
- colour[3] = 255;
-
- // return the value
- return value;
-}
-
-void DecompressColour( u8* rgba, void const* block, bool isDxt1 )
-{
- // get the block bytes
- u8 const* bytes = reinterpret_cast< u8 const* >( block );
-
- // unpack the endpoints
- u8 codes[16];
- int a = Unpack565( bytes, codes );
- int b = Unpack565( bytes + 2, codes + 4 );
-
- // generate the midpoints
- for( int i = 0; i < 3; ++i )
- {
- int c = codes[i];
- int d = codes[4 + i];
-
- if( isDxt1 && a <= b )
- {
- codes[8 + i] = ( u8 )( ( c + d )/2 );
- codes[12 + i] = 0;
- }
- else
- {
- codes[8 + i] = ( u8 )( ( 2*c + d )/3 );
- codes[12 + i] = ( u8 )( ( c + 2*d )/3 );
- }
- }
-
- // fill in alpha for the intermediate values
- codes[8 + 3] = 255;
- codes[12 + 3] = ( isDxt1 && a <= b ) ? 0 : 255;
-
- // unpack the indices
- u8 indices[16];
- for( int i = 0; i < 4; ++i )
- {
- u8* ind = indices + 4*i;
- u8 packed = bytes[4 + i];
-
- ind[0] = packed & 0x3;
- ind[1] = ( packed >> 2 ) & 0x3;
- ind[2] = ( packed >> 4 ) & 0x3;
- ind[3] = ( packed >> 6 ) & 0x3;
- }
-
- // store out the colours
- for( int i = 0; i < 16; ++i )
- {
- u8 offset = 4*indices[i];
- for( int j = 0; j < 4; ++j )
- rgba[4*i + j] = codes[offset + j];
- }
-}
-
-// -- GODOT start --
-void DecompressColourBc4( u8* rgba, void const* block)
-{
- DecompressAlphaDxt5(rgba,block);
- for ( int i = 0; i < 16; ++i ) {
- rgba[i*4] = rgba[i*4 + 3];
- rgba[i*4 + 1] = 0;
- rgba[i*4 + 2] = 0;
- rgba[i*4 + 3] = 255;
- }
-}
-
-void DecompressColourBc5( u8* rgba, void const* block)
-{
- void const* rblock = block;
- void const* gblock = reinterpret_cast< u8 const* >( block ) + 8;
- DecompressAlphaDxt5(rgba,rblock);
- for ( int i = 0; i < 16; ++i ) {
- rgba[i*4] = rgba[i*4 + 3];
- }
- DecompressAlphaDxt5(rgba,gblock);
- for ( int i = 0; i < 16; ++i ) {
- rgba[i*4+1] = rgba[i*4 + 3];
- rgba[i*4 + 2] = 0;
- rgba[i*4 + 3] = 255;
- }
-}
-// -- GODOT end --
-
-
-} // namespace squish
diff --git a/thirdparty/squish/colourblock.h b/thirdparty/squish/colourblock.h
deleted file mode 100644
index e1eb9e4917..0000000000
--- a/thirdparty/squish/colourblock.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_COLOURBLOCK_H
-#define SQUISH_COLOURBLOCK_H
-
-#include "squish.h"
-#include "maths.h"
-
-namespace squish {
-
-void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block );
-void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block );
-
-void DecompressColour( u8* rgba, void const* block, bool isDxt1 );
-// -- GODOT start --
-void DecompressColourBc4( u8* rgba, void const* block );
-void DecompressColourBc5( u8* rgba, void const* block );
-// -- GODOT end --
-
-} // namespace squish
-
-#endif // ndef SQUISH_COLOURBLOCK_H
diff --git a/thirdparty/squish/colourfit.cpp b/thirdparty/squish/colourfit.cpp
deleted file mode 100644
index e45b656557..0000000000
--- a/thirdparty/squish/colourfit.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "colourfit.h"
-#include "colourset.h"
-
-namespace squish {
-
-ColourFit::ColourFit( ColourSet const* colours, int flags )
- : m_colours( colours ),
- m_flags( flags )
-{
-}
-
-ColourFit::~ColourFit()
-{
-}
-
-void ColourFit::Compress( void* block )
-{
- bool isDxt1 = ( ( m_flags & kDxt1 ) != 0 );
- if( isDxt1 )
- {
- Compress3( block );
- if( !m_colours->IsTransparent() )
- Compress4( block );
- }
- else
- Compress4( block );
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/colourfit.h b/thirdparty/squish/colourfit.h
deleted file mode 100644
index e73dceb2eb..0000000000
--- a/thirdparty/squish/colourfit.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_COLOURFIT_H
-#define SQUISH_COLOURFIT_H
-
-#include "squish.h"
-#include "maths.h"
-
-#include <climits>
-
-namespace squish {
-
-class ColourSet;
-
-class ColourFit
-{
-public:
- ColourFit( ColourSet const* colours, int flags );
- virtual ~ColourFit();
-
- void Compress( void* block );
-
-protected:
- virtual void Compress3( void* block ) = 0;
- virtual void Compress4( void* block ) = 0;
-
- ColourSet const* m_colours;
- int m_flags;
-};
-
-} // namespace squish
-
-#endif // ndef SQUISH_COLOURFIT_H
diff --git a/thirdparty/squish/colourset.cpp b/thirdparty/squish/colourset.cpp
deleted file mode 100644
index e900556471..0000000000
--- a/thirdparty/squish/colourset.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "colourset.h"
-
-namespace squish {
-
-ColourSet::ColourSet( u8 const* rgba, int mask, int flags )
- : m_count( 0 ),
- m_transparent( false )
-{
- // check the compression mode for dxt1
- bool isDxt1 = ( ( flags & kDxt1 ) != 0 );
- bool weightByAlpha = ( ( flags & kWeightColourByAlpha ) != 0 );
-
- // create the minimal set
- for( int i = 0; i < 16; ++i )
- {
- // check this pixel is enabled
- int bit = 1 << i;
- if( ( mask & bit ) == 0 )
- {
- m_remap[i] = -1;
- continue;
- }
-
- // check for transparent pixels when using dxt1
- if( isDxt1 && rgba[4*i + 3] < 128 )
- {
- m_remap[i] = -1;
- m_transparent = true;
- continue;
- }
-
- // loop over previous points for a match
- for( int j = 0;; ++j )
- {
- // allocate a new point
- if( j == i )
- {
- // normalise coordinates to [0,1]
- float x = ( float )rgba[4*i] / 255.0f;
- float y = ( float )rgba[4*i + 1] / 255.0f;
- float z = ( float )rgba[4*i + 2] / 255.0f;
-
- // ensure there is always non-zero weight even for zero alpha
- float w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f;
-
- // add the point
- m_points[m_count] = Vec3( x, y, z );
- m_weights[m_count] = ( weightByAlpha ? w : 1.0f );
- m_remap[i] = m_count;
-
- // advance
- ++m_count;
- break;
- }
-
- // check for a match
- int oldbit = 1 << j;
- bool match = ( ( mask & oldbit ) != 0 )
- && ( rgba[4*i] == rgba[4*j] )
- && ( rgba[4*i + 1] == rgba[4*j + 1] )
- && ( rgba[4*i + 2] == rgba[4*j + 2] )
- && ( rgba[4*j + 3] >= 128 || !isDxt1 );
- if( match )
- {
- // get the index of the match
- int index = m_remap[j];
-
- // ensure there is always non-zero weight even for zero alpha
- float w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f;
-
- // map to this point and increase the weight
- m_weights[index] += ( weightByAlpha ? w : 1.0f );
- m_remap[i] = index;
- break;
- }
- }
- }
-
- // square root the weights
- for( int i = 0; i < m_count; ++i )
- m_weights[i] = std::sqrt( m_weights[i] );
-}
-
-void ColourSet::RemapIndices( u8 const* source, u8* target ) const
-{
- for( int i = 0; i < 16; ++i )
- {
- int j = m_remap[i];
- if( j == -1 )
- target[i] = 3;
- else
- target[i] = source[j];
- }
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/colourset.h b/thirdparty/squish/colourset.h
deleted file mode 100644
index e13bb6fc35..0000000000
--- a/thirdparty/squish/colourset.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_COLOURSET_H
-#define SQUISH_COLOURSET_H
-
-#include "squish.h"
-#include "maths.h"
-
-namespace squish {
-
-/*! @brief Represents a set of block colours
-*/
-class ColourSet
-{
-public:
- ColourSet( u8 const* rgba, int mask, int flags );
-
- int GetCount() const { return m_count; }
- Vec3 const* GetPoints() const { return m_points; }
- float const* GetWeights() const { return m_weights; }
- bool IsTransparent() const { return m_transparent; }
-
- void RemapIndices( u8 const* source, u8* target ) const;
-
-private:
- int m_count;
- Vec3 m_points[16];
- float m_weights[16];
- int m_remap[16];
- bool m_transparent;
-};
-
-} // namespace sqish
-
-#endif // ndef SQUISH_COLOURSET_H
diff --git a/thirdparty/squish/config.h b/thirdparty/squish/config.h
deleted file mode 100644
index 05f8d72598..0000000000
--- a/thirdparty/squish/config.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_CONFIG_H
-#define SQUISH_CONFIG_H
-
-// Set to 1 when building squish to use Altivec instructions.
-#ifndef SQUISH_USE_ALTIVEC
-#define SQUISH_USE_ALTIVEC 0
-#endif
-
-// Set to 1 or 2 when building squish to use SSE or SSE2 instructions.
-// -- GODOT start --
-#ifdef _MSC_VER
- #if defined(_M_IX86_FP)
- #if _M_IX86_FP >= 2
- #define SQUISH_USE_SSE 2
- #elif _M_IX86_FP >= 1
- #define SQUISH_USE_SSE 1
- #endif
- #elif defined(_M_X64)
- #define SQUISH_USE_SSE 2
- #endif
-#else
- #if defined(__SSE2__)
- #define SQUISH_USE_SSE 2
- #elif defined(__SSE__)
- #define SQUISH_USE_SSE 1
- #endif
-#endif
-// -- GODOT end --
-
-#ifndef SQUISH_USE_SSE
-#define SQUISH_USE_SSE 0
-#endif
-
-// Internally set SQUISH_USE_SIMD when either Altivec or SSE is available.
-#if SQUISH_USE_ALTIVEC && SQUISH_USE_SSE
-#error "Cannot enable both Altivec and SSE!"
-#endif
-#if SQUISH_USE_ALTIVEC || SQUISH_USE_SSE
-#define SQUISH_USE_SIMD 1
-#else
-#define SQUISH_USE_SIMD 0
-#endif
-
-#endif // ndef SQUISH_CONFIG_H
diff --git a/thirdparty/squish/maths.cpp b/thirdparty/squish/maths.cpp
deleted file mode 100644
index 4fa0bcfb35..0000000000
--- a/thirdparty/squish/maths.cpp
+++ /dev/null
@@ -1,259 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-/*! @file
-
- The symmetric eigensystem solver algorithm is from
- http://www.geometrictools.com/Documentation/EigenSymmetric3x3.pdf
-*/
-
-#include "maths.h"
-#include "simd.h"
-#include <cfloat>
-
-namespace squish {
-
-Sym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights )
-{
- // compute the centroid
- float total = 0.0f;
- Vec3 centroid( 0.0f );
- for( int i = 0; i < n; ++i )
- {
- total += weights[i];
- centroid += weights[i]*points[i];
- }
- if( total > FLT_EPSILON )
- centroid /= total;
-
- // accumulate the covariance matrix
- Sym3x3 covariance( 0.0f );
- for( int i = 0; i < n; ++i )
- {
- Vec3 a = points[i] - centroid;
- Vec3 b = weights[i]*a;
-
- covariance[0] += a.X()*b.X();
- covariance[1] += a.X()*b.Y();
- covariance[2] += a.X()*b.Z();
- covariance[3] += a.Y()*b.Y();
- covariance[4] += a.Y()*b.Z();
- covariance[5] += a.Z()*b.Z();
- }
-
- // return it
- return covariance;
-}
-
-#if 0
-
-static Vec3 GetMultiplicity1Evector( Sym3x3 const& matrix, float evalue )
-{
- // compute M
- Sym3x3 m;
- m[0] = matrix[0] - evalue;
- m[1] = matrix[1];
- m[2] = matrix[2];
- m[3] = matrix[3] - evalue;
- m[4] = matrix[4];
- m[5] = matrix[5] - evalue;
-
- // compute U
- Sym3x3 u;
- u[0] = m[3]*m[5] - m[4]*m[4];
- u[1] = m[2]*m[4] - m[1]*m[5];
- u[2] = m[1]*m[4] - m[2]*m[3];
- u[3] = m[0]*m[5] - m[2]*m[2];
- u[4] = m[1]*m[2] - m[4]*m[0];
- u[5] = m[0]*m[3] - m[1]*m[1];
-
- // find the largest component
- float mc = std::fabs( u[0] );
- int mi = 0;
- for( int i = 1; i < 6; ++i )
- {
- float c = std::fabs( u[i] );
- if( c > mc )
- {
- mc = c;
- mi = i;
- }
- }
-
- // pick the column with this component
- switch( mi )
- {
- case 0:
- return Vec3( u[0], u[1], u[2] );
-
- case 1:
- case 3:
- return Vec3( u[1], u[3], u[4] );
-
- default:
- return Vec3( u[2], u[4], u[5] );
- }
-}
-
-static Vec3 GetMultiplicity2Evector( Sym3x3 const& matrix, float evalue )
-{
- // compute M
- Sym3x3 m;
- m[0] = matrix[0] - evalue;
- m[1] = matrix[1];
- m[2] = matrix[2];
- m[3] = matrix[3] - evalue;
- m[4] = matrix[4];
- m[5] = matrix[5] - evalue;
-
- // find the largest component
- float mc = std::fabs( m[0] );
- int mi = 0;
- for( int i = 1; i < 6; ++i )
- {
- float c = std::fabs( m[i] );
- if( c > mc )
- {
- mc = c;
- mi = i;
- }
- }
-
- // pick the first eigenvector based on this index
- switch( mi )
- {
- case 0:
- case 1:
- return Vec3( -m[1], m[0], 0.0f );
-
- case 2:
- return Vec3( m[2], 0.0f, -m[0] );
-
- case 3:
- case 4:
- return Vec3( 0.0f, -m[4], m[3] );
-
- default:
- return Vec3( 0.0f, -m[5], m[4] );
- }
-}
-
-Vec3 ComputePrincipleComponent( Sym3x3 const& matrix )
-{
- // compute the cubic coefficients
- float c0 = matrix[0]*matrix[3]*matrix[5]
- + 2.0f*matrix[1]*matrix[2]*matrix[4]
- - matrix[0]*matrix[4]*matrix[4]
- - matrix[3]*matrix[2]*matrix[2]
- - matrix[5]*matrix[1]*matrix[1];
- float c1 = matrix[0]*matrix[3] + matrix[0]*matrix[5] + matrix[3]*matrix[5]
- - matrix[1]*matrix[1] - matrix[2]*matrix[2] - matrix[4]*matrix[4];
- float c2 = matrix[0] + matrix[3] + matrix[5];
-
- // compute the quadratic coefficients
- float a = c1 - ( 1.0f/3.0f )*c2*c2;
- float b = ( -2.0f/27.0f )*c2*c2*c2 + ( 1.0f/3.0f )*c1*c2 - c0;
-
- // compute the root count check
- float Q = 0.25f*b*b + ( 1.0f/27.0f )*a*a*a;
-
- // test the multiplicity
- if( FLT_EPSILON < Q )
- {
- // only one root, which implies we have a multiple of the identity
- return Vec3( 1.0f );
- }
- else if( Q < -FLT_EPSILON )
- {
- // three distinct roots
- float theta = std::atan2( std::sqrt( -Q ), -0.5f*b );
- float rho = std::sqrt( 0.25f*b*b - Q );
-
- float rt = std::pow( rho, 1.0f/3.0f );
- float ct = std::cos( theta/3.0f );
- float st = std::sin( theta/3.0f );
-
- float l1 = ( 1.0f/3.0f )*c2 + 2.0f*rt*ct;
- float l2 = ( 1.0f/3.0f )*c2 - rt*( ct + ( float )sqrt( 3.0f )*st );
- float l3 = ( 1.0f/3.0f )*c2 - rt*( ct - ( float )sqrt( 3.0f )*st );
-
- // pick the larger
- if( std::fabs( l2 ) > std::fabs( l1 ) )
- l1 = l2;
- if( std::fabs( l3 ) > std::fabs( l1 ) )
- l1 = l3;
-
- // get the eigenvector
- return GetMultiplicity1Evector( matrix, l1 );
- }
- else // if( -FLT_EPSILON <= Q && Q <= FLT_EPSILON )
- {
- // two roots
- float rt;
- if( b < 0.0f )
- rt = -std::pow( -0.5f*b, 1.0f/3.0f );
- else
- rt = std::pow( 0.5f*b, 1.0f/3.0f );
-
- float l1 = ( 1.0f/3.0f )*c2 + rt; // repeated
- float l2 = ( 1.0f/3.0f )*c2 - 2.0f*rt;
-
- // get the eigenvector
- if( std::fabs( l1 ) > std::fabs( l2 ) )
- return GetMultiplicity2Evector( matrix, l1 );
- else
- return GetMultiplicity1Evector( matrix, l2 );
- }
-}
-
-#else
-
-#define POWER_ITERATION_COUNT 8
-
-Vec3 ComputePrincipleComponent( Sym3x3 const& matrix )
-{
- Vec4 const row0( matrix[0], matrix[1], matrix[2], 0.0f );
- Vec4 const row1( matrix[1], matrix[3], matrix[4], 0.0f );
- Vec4 const row2( matrix[2], matrix[4], matrix[5], 0.0f );
- Vec4 v = VEC4_CONST( 1.0f );
- for( int i = 0; i < POWER_ITERATION_COUNT; ++i )
- {
- // matrix multiply
- Vec4 w = row0*v.SplatX();
- w = MultiplyAdd(row1, v.SplatY(), w);
- w = MultiplyAdd(row2, v.SplatZ(), w);
-
- // get max component from xyz in all channels
- Vec4 a = Max(w.SplatX(), Max(w.SplatY(), w.SplatZ()));
-
- // divide through and advance
- v = w*Reciprocal(a);
- }
- return v.GetVec3();
-}
-
-#endif
-
-} // namespace squish
diff --git a/thirdparty/squish/maths.h b/thirdparty/squish/maths.h
deleted file mode 100644
index 59c32196b1..0000000000
--- a/thirdparty/squish/maths.h
+++ /dev/null
@@ -1,233 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_MATHS_H
-#define SQUISH_MATHS_H
-
-#include <cmath>
-#include <algorithm>
-#include "config.h"
-
-namespace squish {
-
-class Vec3
-{
-public:
- typedef Vec3 const& Arg;
-
- Vec3()
- {
- }
-
- explicit Vec3( float s )
- {
- m_x = s;
- m_y = s;
- m_z = s;
- }
-
- Vec3( float x, float y, float z )
- {
- m_x = x;
- m_y = y;
- m_z = z;
- }
-
- float X() const { return m_x; }
- float Y() const { return m_y; }
- float Z() const { return m_z; }
-
- Vec3 operator-() const
- {
- return Vec3( -m_x, -m_y, -m_z );
- }
-
- Vec3& operator+=( Arg v )
- {
- m_x += v.m_x;
- m_y += v.m_y;
- m_z += v.m_z;
- return *this;
- }
-
- Vec3& operator-=( Arg v )
- {
- m_x -= v.m_x;
- m_y -= v.m_y;
- m_z -= v.m_z;
- return *this;
- }
-
- Vec3& operator*=( Arg v )
- {
- m_x *= v.m_x;
- m_y *= v.m_y;
- m_z *= v.m_z;
- return *this;
- }
-
- Vec3& operator*=( float s )
- {
- m_x *= s;
- m_y *= s;
- m_z *= s;
- return *this;
- }
-
- Vec3& operator/=( Arg v )
- {
- m_x /= v.m_x;
- m_y /= v.m_y;
- m_z /= v.m_z;
- return *this;
- }
-
- Vec3& operator/=( float s )
- {
- float t = 1.0f/s;
- m_x *= t;
- m_y *= t;
- m_z *= t;
- return *this;
- }
-
- friend Vec3 operator+( Arg left, Arg right )
- {
- Vec3 copy( left );
- return copy += right;
- }
-
- friend Vec3 operator-( Arg left, Arg right )
- {
- Vec3 copy( left );
- return copy -= right;
- }
-
- friend Vec3 operator*( Arg left, Arg right )
- {
- Vec3 copy( left );
- return copy *= right;
- }
-
- friend Vec3 operator*( Arg left, float right )
- {
- Vec3 copy( left );
- return copy *= right;
- }
-
- friend Vec3 operator*( float left, Arg right )
- {
- Vec3 copy( right );
- return copy *= left;
- }
-
- friend Vec3 operator/( Arg left, Arg right )
- {
- Vec3 copy( left );
- return copy /= right;
- }
-
- friend Vec3 operator/( Arg left, float right )
- {
- Vec3 copy( left );
- return copy /= right;
- }
-
- friend float Dot( Arg left, Arg right )
- {
- return left.m_x*right.m_x + left.m_y*right.m_y + left.m_z*right.m_z;
- }
-
- friend Vec3 Min( Arg left, Arg right )
- {
- return Vec3(
- std::min( left.m_x, right.m_x ),
- std::min( left.m_y, right.m_y ),
- std::min( left.m_z, right.m_z )
- );
- }
-
- friend Vec3 Max( Arg left, Arg right )
- {
- return Vec3(
- std::max( left.m_x, right.m_x ),
- std::max( left.m_y, right.m_y ),
- std::max( left.m_z, right.m_z )
- );
- }
-
- friend Vec3 Truncate( Arg v )
- {
- return Vec3(
- v.m_x > 0.0f ? std::floor( v.m_x ) : std::ceil( v.m_x ),
- v.m_y > 0.0f ? std::floor( v.m_y ) : std::ceil( v.m_y ),
- v.m_z > 0.0f ? std::floor( v.m_z ) : std::ceil( v.m_z )
- );
- }
-
-private:
- float m_x;
- float m_y;
- float m_z;
-};
-
-inline float LengthSquared( Vec3::Arg v )
-{
- return Dot( v, v );
-}
-
-class Sym3x3
-{
-public:
- Sym3x3()
- {
- }
-
- Sym3x3( float s )
- {
- for( int i = 0; i < 6; ++i )
- m_x[i] = s;
- }
-
- float operator[]( int index ) const
- {
- return m_x[index];
- }
-
- float& operator[]( int index )
- {
- return m_x[index];
- }
-
-private:
- float m_x[6];
-};
-
-Sym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights );
-Vec3 ComputePrincipleComponent( Sym3x3 const& matrix );
-
-} // namespace squish
-
-#endif // ndef SQUISH_MATHS_H
diff --git a/thirdparty/squish/patches/config_sse.patch b/thirdparty/squish/patches/config_sse.patch
deleted file mode 100644
index 047701ee32..0000000000
--- a/thirdparty/squish/patches/config_sse.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-diff --git a/thirdparty/squish/config.h b/thirdparty/squish/config.h
-index 92edefe966..05f8d72598 100644
---- a/thirdparty/squish/config.h
-+++ b/thirdparty/squish/config.h
-@@ -32,6 +32,26 @@
- #endif
-
- // Set to 1 or 2 when building squish to use SSE or SSE2 instructions.
-+// -- GODOT start --
-+#ifdef _MSC_VER
-+ #if defined(_M_IX86_FP)
-+ #if _M_IX86_FP >= 2
-+ #define SQUISH_USE_SSE 2
-+ #elif _M_IX86_FP >= 1
-+ #define SQUISH_USE_SSE 1
-+ #endif
-+ #elif defined(_M_X64)
-+ #define SQUISH_USE_SSE 2
-+ #endif
-+#else
-+ #if defined(__SSE2__)
-+ #define SQUISH_USE_SSE 2
-+ #elif defined(__SSE__)
-+ #define SQUISH_USE_SSE 1
-+ #endif
-+#endif
-+// -- GODOT end --
-+
- #ifndef SQUISH_USE_SSE
- #define SQUISH_USE_SSE 0
- #endif
diff --git a/thirdparty/squish/patches/decompress_bc4_bc5.patch b/thirdparty/squish/patches/decompress_bc4_bc5.patch
deleted file mode 100644
index 949375560c..0000000000
--- a/thirdparty/squish/patches/decompress_bc4_bc5.patch
+++ /dev/null
@@ -1,85 +0,0 @@
-diff --git a/thirdparty/squish/colourblock.cpp b/thirdparty/squish/colourblock.cpp
-index af8b980365..f14c9362bd 100644
---- a/thirdparty/squish/colourblock.cpp
-+++ b/thirdparty/squish/colourblock.cpp
-@@ -24,6 +24,9 @@
- -------------------------------------------------------------------------- */
-
- #include "colourblock.h"
-+// -- GODOT start --
-+#include "alpha.h"
-+// -- GODOT end --
-
- namespace squish {
-
-@@ -211,4 +214,34 @@ void DecompressColour( u8* rgba, void const* block, bool isDxt1 )
- }
- }
-
-+// -- GODOT start --
-+void DecompressColourBc4( u8* rgba, void const* block)
-+{
-+ DecompressAlphaDxt5(rgba,block);
-+ for ( int i = 0; i < 16; ++i ) {
-+ rgba[i*4] = rgba[i*4 + 3];
-+ rgba[i*4 + 1] = 0;
-+ rgba[i*4 + 2] = 0;
-+ rgba[i*4 + 3] = 255;
-+ }
-+}
-+
-+void DecompressColourBc5( u8* rgba, void const* block)
-+{
-+ void const* rblock = block;
-+ void const* gblock = reinterpret_cast< u8 const* >( block ) + 8;
-+ DecompressAlphaDxt5(rgba,rblock);
-+ for ( int i = 0; i < 16; ++i ) {
-+ rgba[i*4] = rgba[i*4 + 3];
-+ }
-+ DecompressAlphaDxt5(rgba,gblock);
-+ for ( int i = 0; i < 16; ++i ) {
-+ rgba[i*4+1] = rgba[i*4 + 3];
-+ rgba[i*4 + 2] = 0;
-+ rgba[i*4 + 3] = 255;
-+ }
-+}
-+// -- GODOT end --
-+
-+
- } // namespace squish
-diff --git a/thirdparty/squish/colourblock.h b/thirdparty/squish/colourblock.h
-index fee2cd7c5d..e1eb9e4917 100644
---- a/thirdparty/squish/colourblock.h
-+++ b/thirdparty/squish/colourblock.h
-@@ -35,6 +35,10 @@ void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void*
- void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block );
-
- void DecompressColour( u8* rgba, void const* block, bool isDxt1 );
-+// -- GODOT start --
-+void DecompressColourBc4( u8* rgba, void const* block );
-+void DecompressColourBc5( u8* rgba, void const* block );
-+// -- GODOT end --
-
- } // namespace squish
-
-diff --git a/thirdparty/squish/squish.cpp b/thirdparty/squish/squish.cpp
-index 1d22a64ad6..086ba11cd0 100644
---- a/thirdparty/squish/squish.cpp
-+++ b/thirdparty/squish/squish.cpp
-@@ -135,7 +135,15 @@ void Decompress( u8* rgba, void const* block, int flags )
- colourBlock = reinterpret_cast< u8 const* >( block ) + 8;
-
- // decompress colour
-- DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 );
-+ // -- GODOT start --
-+ //DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 );
-+ if(( flags & ( kBc4 ) ) != 0)
-+ DecompressColourBc4( rgba, colourBlock);
-+ else if(( flags & ( kBc5 ) ) != 0)
-+ DecompressColourBc5( rgba, colourBlock);
-+ else
-+ DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 );
-+ // -- GODOT end --
-
- // decompress alpha separately if necessary
- if( ( flags & kDxt3 ) != 0 )
diff --git a/thirdparty/squish/rangefit.cpp b/thirdparty/squish/rangefit.cpp
deleted file mode 100644
index adc07ed7d2..0000000000
--- a/thirdparty/squish/rangefit.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "rangefit.h"
-#include "colourset.h"
-#include "colourblock.h"
-#include <cfloat>
-
-namespace squish {
-
-RangeFit::RangeFit( ColourSet const* colours, int flags, float* metric )
- : ColourFit( colours, flags )
-{
- // initialise the metric (old perceptual = 0.2126f, 0.7152f, 0.0722f)
- if( metric )
- m_metric = Vec3( metric[0], metric[1], metric[2] );
- else
- m_metric = Vec3( 1.0f );
-
- // initialise the best error
- m_besterror = FLT_MAX;
-
- // cache some values
- int const count = m_colours->GetCount();
- Vec3 const* values = m_colours->GetPoints();
- float const* weights = m_colours->GetWeights();
-
- // get the covariance matrix
- Sym3x3 covariance = ComputeWeightedCovariance( count, values, weights );
-
- // compute the principle component
- Vec3 principle = ComputePrincipleComponent( covariance );
-
- // get the min and max range as the codebook endpoints
- Vec3 start( 0.0f );
- Vec3 end( 0.0f );
- if( count > 0 )
- {
- float min, max;
-
- // compute the range
- start = end = values[0];
- min = max = Dot( values[0], principle );
- for( int i = 1; i < count; ++i )
- {
- float val = Dot( values[i], principle );
- if( val < min )
- {
- start = values[i];
- min = val;
- }
- else if( val > max )
- {
- end = values[i];
- max = val;
- }
- }
- }
-
- // clamp the output to [0, 1]
- Vec3 const one( 1.0f );
- Vec3 const zero( 0.0f );
- start = Min( one, Max( zero, start ) );
- end = Min( one, Max( zero, end ) );
-
- // clamp to the grid and save
- Vec3 const grid( 31.0f, 63.0f, 31.0f );
- Vec3 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f );
- Vec3 const half( 0.5f );
- m_start = Truncate( grid*start + half )*gridrcp;
- m_end = Truncate( grid*end + half )*gridrcp;
-}
-
-void RangeFit::Compress3( void* block )
-{
- // cache some values
- int const count = m_colours->GetCount();
- Vec3 const* values = m_colours->GetPoints();
-
- // create a codebook
- Vec3 codes[3];
- codes[0] = m_start;
- codes[1] = m_end;
- codes[2] = 0.5f*m_start + 0.5f*m_end;
-
- // match each point to the closest code
- u8 closest[16];
- float error = 0.0f;
- for( int i = 0; i < count; ++i )
- {
- // find the closest code
- float dist = FLT_MAX;
- int idx = 0;
- for( int j = 0; j < 3; ++j )
- {
- float d = LengthSquared( m_metric*( values[i] - codes[j] ) );
- if( d < dist )
- {
- dist = d;
- idx = j;
- }
- }
-
- // save the index
- closest[i] = ( u8 )idx;
-
- // accumulate the error
- error += dist;
- }
-
- // save this scheme if it wins
- if( error < m_besterror )
- {
- // remap the indices
- u8 indices[16];
- m_colours->RemapIndices( closest, indices );
-
- // save the block
- WriteColourBlock3( m_start, m_end, indices, block );
-
- // save the error
- m_besterror = error;
- }
-}
-
-void RangeFit::Compress4( void* block )
-{
- // cache some values
- int const count = m_colours->GetCount();
- Vec3 const* values = m_colours->GetPoints();
-
- // create a codebook
- Vec3 codes[4];
- codes[0] = m_start;
- codes[1] = m_end;
- codes[2] = ( 2.0f/3.0f )*m_start + ( 1.0f/3.0f )*m_end;
- codes[3] = ( 1.0f/3.0f )*m_start + ( 2.0f/3.0f )*m_end;
-
- // match each point to the closest code
- u8 closest[16];
- float error = 0.0f;
- for( int i = 0; i < count; ++i )
- {
- // find the closest code
- float dist = FLT_MAX;
- int idx = 0;
- for( int j = 0; j < 4; ++j )
- {
- float d = LengthSquared( m_metric*( values[i] - codes[j] ) );
- if( d < dist )
- {
- dist = d;
- idx = j;
- }
- }
-
- // save the index
- closest[i] = ( u8 )idx;
-
- // accumulate the error
- error += dist;
- }
-
- // save this scheme if it wins
- if( error < m_besterror )
- {
- // remap the indices
- u8 indices[16];
- m_colours->RemapIndices( closest, indices );
-
- // save the block
- WriteColourBlock4( m_start, m_end, indices, block );
-
- // save the error
- m_besterror = error;
- }
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/rangefit.h b/thirdparty/squish/rangefit.h
deleted file mode 100644
index bdb21a9007..0000000000
--- a/thirdparty/squish/rangefit.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_RANGEFIT_H
-#define SQUISH_RANGEFIT_H
-
-#include "squish.h"
-#include "colourfit.h"
-#include "maths.h"
-
-namespace squish {
-
-class ColourSet;
-
-class RangeFit : public ColourFit
-{
-public:
- RangeFit( ColourSet const* colours, int flags, float* metric );
-
-private:
- virtual void Compress3( void* block );
- virtual void Compress4( void* block );
-
- Vec3 m_metric;
- Vec3 m_start;
- Vec3 m_end;
- float m_besterror;
-};
-
-} // squish
-
-#endif // ndef SQUISH_RANGEFIT_H
diff --git a/thirdparty/squish/simd.h b/thirdparty/squish/simd.h
deleted file mode 100644
index 1e02fa160e..0000000000
--- a/thirdparty/squish/simd.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_SIMD_H
-#define SQUISH_SIMD_H
-
-#include "maths.h"
-
-#if SQUISH_USE_ALTIVEC
-#include "simd_ve.h"
-#elif SQUISH_USE_SSE
-#include "simd_sse.h"
-#else
-#include "simd_float.h"
-#endif
-
-
-#endif // ndef SQUISH_SIMD_H
diff --git a/thirdparty/squish/simd_float.h b/thirdparty/squish/simd_float.h
deleted file mode 100644
index 030ea70950..0000000000
--- a/thirdparty/squish/simd_float.h
+++ /dev/null
@@ -1,183 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_SIMD_FLOAT_H
-#define SQUISH_SIMD_FLOAT_H
-
-#include <algorithm>
-
-namespace squish {
-
-#define VEC4_CONST( X ) Vec4( X )
-
-class Vec4
-{
-public:
- typedef Vec4 const& Arg;
-
- Vec4() {}
-
- explicit Vec4( float s )
- : m_x( s ),
- m_y( s ),
- m_z( s ),
- m_w( s )
- {
- }
-
- Vec4( float x, float y, float z, float w )
- : m_x( x ),
- m_y( y ),
- m_z( z ),
- m_w( w )
- {
- }
-
- Vec3 GetVec3() const
- {
- return Vec3( m_x, m_y, m_z );
- }
-
- Vec4 SplatX() const { return Vec4( m_x ); }
- Vec4 SplatY() const { return Vec4( m_y ); }
- Vec4 SplatZ() const { return Vec4( m_z ); }
- Vec4 SplatW() const { return Vec4( m_w ); }
-
- Vec4& operator+=( Arg v )
- {
- m_x += v.m_x;
- m_y += v.m_y;
- m_z += v.m_z;
- m_w += v.m_w;
- return *this;
- }
-
- Vec4& operator-=( Arg v )
- {
- m_x -= v.m_x;
- m_y -= v.m_y;
- m_z -= v.m_z;
- m_w -= v.m_w;
- return *this;
- }
-
- Vec4& operator*=( Arg v )
- {
- m_x *= v.m_x;
- m_y *= v.m_y;
- m_z *= v.m_z;
- m_w *= v.m_w;
- return *this;
- }
-
- friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right )
- {
- Vec4 copy( left );
- return copy += right;
- }
-
- friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right )
- {
- Vec4 copy( left );
- return copy -= right;
- }
-
- friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right )
- {
- Vec4 copy( left );
- return copy *= right;
- }
-
- //! Returns a*b + c
- friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )
- {
- return a*b + c;
- }
-
- //! Returns -( a*b - c )
- friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )
- {
- return c - a*b;
- }
-
- friend Vec4 Reciprocal( Vec4::Arg v )
- {
- return Vec4(
- 1.0f/v.m_x,
- 1.0f/v.m_y,
- 1.0f/v.m_z,
- 1.0f/v.m_w
- );
- }
-
- friend Vec4 Min( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4(
- std::min( left.m_x, right.m_x ),
- std::min( left.m_y, right.m_y ),
- std::min( left.m_z, right.m_z ),
- std::min( left.m_w, right.m_w )
- );
- }
-
- friend Vec4 Max( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4(
- std::max( left.m_x, right.m_x ),
- std::max( left.m_y, right.m_y ),
- std::max( left.m_z, right.m_z ),
- std::max( left.m_w, right.m_w )
- );
- }
-
- friend Vec4 Truncate( Vec4::Arg v )
- {
- return Vec4(
- v.m_x > 0.0f ? std::floor( v.m_x ) : std::ceil( v.m_x ),
- v.m_y > 0.0f ? std::floor( v.m_y ) : std::ceil( v.m_y ),
- v.m_z > 0.0f ? std::floor( v.m_z ) : std::ceil( v.m_z ),
- v.m_w > 0.0f ? std::floor( v.m_w ) : std::ceil( v.m_w )
- );
- }
-
- friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right )
- {
- return left.m_x < right.m_x
- || left.m_y < right.m_y
- || left.m_z < right.m_z
- || left.m_w < right.m_w;
- }
-
-private:
- float m_x;
- float m_y;
- float m_z;
- float m_w;
-};
-
-} // namespace squish
-
-#endif // ndef SQUISH_SIMD_FLOAT_H
-
diff --git a/thirdparty/squish/simd_sse.h b/thirdparty/squish/simd_sse.h
deleted file mode 100644
index 2e8be4ca7b..0000000000
--- a/thirdparty/squish/simd_sse.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_SIMD_SSE_H
-#define SQUISH_SIMD_SSE_H
-
-#include <xmmintrin.h>
-#if ( SQUISH_USE_SSE > 1 )
-#include <emmintrin.h>
-#endif
-
-#define SQUISH_SSE_SPLAT( a ) \
- ( ( a ) | ( ( a ) << 2 ) | ( ( a ) << 4 ) | ( ( a ) << 6 ) )
-
-#define SQUISH_SSE_SHUF( x, y, z, w ) \
- ( ( x ) | ( ( y ) << 2 ) | ( ( z ) << 4 ) | ( ( w ) << 6 ) )
-
-namespace squish {
-
-#define VEC4_CONST( X ) Vec4( X )
-
-class Vec4
-{
-public:
- typedef Vec4 const& Arg;
-
- Vec4() {}
-
- explicit Vec4( __m128 v ) : m_v( v ) {}
-
- Vec4( Vec4 const& arg ) : m_v( arg.m_v ) {}
-
- Vec4& operator=( Vec4 const& arg )
- {
- m_v = arg.m_v;
- return *this;
- }
-
- explicit Vec4( float s ) : m_v( _mm_set1_ps( s ) ) {}
-
- Vec4( float x, float y, float z, float w ) : m_v( _mm_setr_ps( x, y, z, w ) ) {}
-
- Vec3 GetVec3() const
- {
-#ifdef __GNUC__
- __attribute__ ((__aligned__ (16))) float c[4];
-#else
- __declspec(align(16)) float c[4];
-#endif
- _mm_store_ps( c, m_v );
- return Vec3( c[0], c[1], c[2] );
- }
-
- Vec4 SplatX() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 0 ) ) ); }
- Vec4 SplatY() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 1 ) ) ); }
- Vec4 SplatZ() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 2 ) ) ); }
- Vec4 SplatW() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 3 ) ) ); }
-
- Vec4& operator+=( Arg v )
- {
- m_v = _mm_add_ps( m_v, v.m_v );
- return *this;
- }
-
- Vec4& operator-=( Arg v )
- {
- m_v = _mm_sub_ps( m_v, v.m_v );
- return *this;
- }
-
- Vec4& operator*=( Arg v )
- {
- m_v = _mm_mul_ps( m_v, v.m_v );
- return *this;
- }
-
- friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( _mm_add_ps( left.m_v, right.m_v ) );
- }
-
- friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( _mm_sub_ps( left.m_v, right.m_v ) );
- }
-
- friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( _mm_mul_ps( left.m_v, right.m_v ) );
- }
-
- //! Returns a*b + c
- friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )
- {
- return Vec4( _mm_add_ps( _mm_mul_ps( a.m_v, b.m_v ), c.m_v ) );
- }
-
- //! Returns -( a*b - c )
- friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )
- {
- return Vec4( _mm_sub_ps( c.m_v, _mm_mul_ps( a.m_v, b.m_v ) ) );
- }
-
- friend Vec4 Reciprocal( Vec4::Arg v )
- {
- // get the reciprocal estimate
- __m128 estimate = _mm_rcp_ps( v.m_v );
-
- // one round of Newton-Rhaphson refinement
- __m128 diff = _mm_sub_ps( _mm_set1_ps( 1.0f ), _mm_mul_ps( estimate, v.m_v ) );
- return Vec4( _mm_add_ps( _mm_mul_ps( diff, estimate ), estimate ) );
- }
-
- friend Vec4 Min( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( _mm_min_ps( left.m_v, right.m_v ) );
- }
-
- friend Vec4 Max( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( _mm_max_ps( left.m_v, right.m_v ) );
- }
-
- friend Vec4 Truncate( Vec4::Arg v )
- {
-#if ( SQUISH_USE_SSE == 1 )
- // convert to ints
- __m128 input = v.m_v;
- __m64 lo = _mm_cvttps_pi32( input );
- __m64 hi = _mm_cvttps_pi32( _mm_movehl_ps( input, input ) );
-
- // convert to floats
- __m128 part = _mm_movelh_ps( input, _mm_cvtpi32_ps( input, hi ) );
- __m128 truncated = _mm_cvtpi32_ps( part, lo );
-
- // clear out the MMX multimedia state to allow FP calls later
- _mm_empty();
- return Vec4( truncated );
-#else
- // use SSE2 instructions
- return Vec4( _mm_cvtepi32_ps( _mm_cvttps_epi32( v.m_v ) ) );
-#endif
- }
-
- friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right )
- {
- __m128 bits = _mm_cmplt_ps( left.m_v, right.m_v );
- int value = _mm_movemask_ps( bits );
- return value != 0;
- }
-
-private:
- __m128 m_v;
-};
-
-} // namespace squish
-
-#endif // ndef SQUISH_SIMD_SSE_H
diff --git a/thirdparty/squish/simd_ve.h b/thirdparty/squish/simd_ve.h
deleted file mode 100644
index 08a1537503..0000000000
--- a/thirdparty/squish/simd_ve.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_SIMD_VE_H
-#define SQUISH_SIMD_VE_H
-
-#include <altivec.h>
-#undef bool
-
-namespace squish {
-
-#define VEC4_CONST( X ) Vec4( ( vector float ){ X } )
-
-class Vec4
-{
-public:
- typedef Vec4 Arg;
-
- Vec4() {}
-
- explicit Vec4( vector float v ) : m_v( v ) {}
-
- Vec4( Vec4 const& arg ) : m_v( arg.m_v ) {}
-
- Vec4& operator=( Vec4 const& arg )
- {
- m_v = arg.m_v;
- return *this;
- }
-
- explicit Vec4( float s )
- {
- union { vector float v; float c[4]; } u;
- u.c[0] = s;
- u.c[1] = s;
- u.c[2] = s;
- u.c[3] = s;
- m_v = u.v;
- }
-
- Vec4( float x, float y, float z, float w )
- {
- union { vector float v; float c[4]; } u;
- u.c[0] = x;
- u.c[1] = y;
- u.c[2] = z;
- u.c[3] = w;
- m_v = u.v;
- }
-
- Vec3 GetVec3() const
- {
- union { vector float v; float c[4]; } u;
- u.v = m_v;
- return Vec3( u.c[0], u.c[1], u.c[2] );
- }
-
- Vec4 SplatX() const { return Vec4( vec_splat( m_v, 0 ) ); }
- Vec4 SplatY() const { return Vec4( vec_splat( m_v, 1 ) ); }
- Vec4 SplatZ() const { return Vec4( vec_splat( m_v, 2 ) ); }
- Vec4 SplatW() const { return Vec4( vec_splat( m_v, 3 ) ); }
-
- Vec4& operator+=( Arg v )
- {
- m_v = vec_add( m_v, v.m_v );
- return *this;
- }
-
- Vec4& operator-=( Arg v )
- {
- m_v = vec_sub( m_v, v.m_v );
- return *this;
- }
-
- Vec4& operator*=( Arg v )
- {
- m_v = vec_madd( m_v, v.m_v, ( vector float ){ -0.0f } );
- return *this;
- }
-
- friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( vec_add( left.m_v, right.m_v ) );
- }
-
- friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( vec_sub( left.m_v, right.m_v ) );
- }
-
- friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( vec_madd( left.m_v, right.m_v, ( vector float ){ -0.0f } ) );
- }
-
- //! Returns a*b + c
- friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )
- {
- return Vec4( vec_madd( a.m_v, b.m_v, c.m_v ) );
- }
-
- //! Returns -( a*b - c )
- friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c )
- {
- return Vec4( vec_nmsub( a.m_v, b.m_v, c.m_v ) );
- }
-
- friend Vec4 Reciprocal( Vec4::Arg v )
- {
- // get the reciprocal estimate
- vector float estimate = vec_re( v.m_v );
-
- // one round of Newton-Rhaphson refinement
- vector float diff = vec_nmsub( estimate, v.m_v, ( vector float ){ 1.0f } );
- return Vec4( vec_madd( diff, estimate, estimate ) );
- }
-
- friend Vec4 Min( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( vec_min( left.m_v, right.m_v ) );
- }
-
- friend Vec4 Max( Vec4::Arg left, Vec4::Arg right )
- {
- return Vec4( vec_max( left.m_v, right.m_v ) );
- }
-
- friend Vec4 Truncate( Vec4::Arg v )
- {
- return Vec4( vec_trunc( v.m_v ) );
- }
-
- friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right )
- {
- return vec_any_lt( left.m_v, right.m_v ) != 0;
- }
-
-private:
- vector float m_v;
-};
-
-} // namespace squish
-
-#endif // ndef SQUISH_SIMD_VE_H
diff --git a/thirdparty/squish/singlecolourfit.cpp b/thirdparty/squish/singlecolourfit.cpp
deleted file mode 100644
index cef0ebc410..0000000000
--- a/thirdparty/squish/singlecolourfit.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include "singlecolourfit.h"
-#include "colourset.h"
-#include "colourblock.h"
-
-namespace squish {
-
-struct SourceBlock
-{
- u8 start;
- u8 end;
- u8 error;
-};
-
-struct SingleColourLookup
-{
- SourceBlock sources[2];
-};
-
-#include "singlecolourlookup.inl"
-
-static int FloatToInt( float a, int limit )
-{
- // use ANSI round-to-zero behaviour to get round-to-nearest
- int i = ( int )( a + 0.5f );
-
- // clamp to the limit
- if( i < 0 )
- i = 0;
- else if( i > limit )
- i = limit;
-
- // done
- return i;
-}
-
-SingleColourFit::SingleColourFit( ColourSet const* colours, int flags )
- : ColourFit( colours, flags )
-{
- // grab the single colour
- Vec3 const* values = m_colours->GetPoints();
- m_colour[0] = ( u8 )FloatToInt( 255.0f*values->X(), 255 );
- m_colour[1] = ( u8 )FloatToInt( 255.0f*values->Y(), 255 );
- m_colour[2] = ( u8 )FloatToInt( 255.0f*values->Z(), 255 );
-
- // initialise the best error
- m_besterror = INT_MAX;
-}
-
-void SingleColourFit::Compress3( void* block )
-{
- // build the table of lookups
- SingleColourLookup const* const lookups[] =
- {
- lookup_5_3,
- lookup_6_3,
- lookup_5_3
- };
-
- // find the best end-points and index
- ComputeEndPoints( lookups );
-
- // build the block if we win
- if( m_error < m_besterror )
- {
- // remap the indices
- u8 indices[16];
- m_colours->RemapIndices( &m_index, indices );
-
- // save the block
- WriteColourBlock3( m_start, m_end, indices, block );
-
- // save the error
- m_besterror = m_error;
- }
-}
-
-void SingleColourFit::Compress4( void* block )
-{
- // build the table of lookups
- SingleColourLookup const* const lookups[] =
- {
- lookup_5_4,
- lookup_6_4,
- lookup_5_4
- };
-
- // find the best end-points and index
- ComputeEndPoints( lookups );
-
- // build the block if we win
- if( m_error < m_besterror )
- {
- // remap the indices
- u8 indices[16];
- m_colours->RemapIndices( &m_index, indices );
-
- // save the block
- WriteColourBlock4( m_start, m_end, indices, block );
-
- // save the error
- m_besterror = m_error;
- }
-}
-
-void SingleColourFit::ComputeEndPoints( SingleColourLookup const* const* lookups )
-{
- // check each index combination (endpoint or intermediate)
- m_error = INT_MAX;
- for( int index = 0; index < 2; ++index )
- {
- // check the error for this codebook index
- SourceBlock const* sources[3];
- int error = 0;
- for( int channel = 0; channel < 3; ++channel )
- {
- // grab the lookup table and index for this channel
- SingleColourLookup const* lookup = lookups[channel];
- int target = m_colour[channel];
-
- // store a pointer to the source for this channel
- sources[channel] = lookup[target].sources + index;
-
- // accumulate the error
- int diff = sources[channel]->error;
- error += diff*diff;
- }
-
- // keep it if the error is lower
- if( error < m_error )
- {
- m_start = Vec3(
- ( float )sources[0]->start/31.0f,
- ( float )sources[1]->start/63.0f,
- ( float )sources[2]->start/31.0f
- );
- m_end = Vec3(
- ( float )sources[0]->end/31.0f,
- ( float )sources[1]->end/63.0f,
- ( float )sources[2]->end/31.0f
- );
- m_index = ( u8 )( 2*index );
- m_error = error;
- }
- }
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/singlecolourfit.h b/thirdparty/squish/singlecolourfit.h
deleted file mode 100644
index 974ce77256..0000000000
--- a/thirdparty/squish/singlecolourfit.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_SINGLECOLOURFIT_H
-#define SQUISH_SINGLECOLOURFIT_H
-
-#include "squish.h"
-#include "colourfit.h"
-
-namespace squish {
-
-class ColourSet;
-struct SingleColourLookup;
-
-class SingleColourFit : public ColourFit
-{
-public:
- SingleColourFit( ColourSet const* colours, int flags );
-
-private:
- virtual void Compress3( void* block );
- virtual void Compress4( void* block );
-
- void ComputeEndPoints( SingleColourLookup const* const* lookups );
-
- u8 m_colour[3];
- Vec3 m_start;
- Vec3 m_end;
- u8 m_index;
- int m_error;
- int m_besterror;
-};
-
-} // namespace squish
-
-#endif // ndef SQUISH_SINGLECOLOURFIT_H
diff --git a/thirdparty/squish/singlecolourlookup.inl b/thirdparty/squish/singlecolourlookup.inl
deleted file mode 100644
index 5b44a1e5e6..0000000000
--- a/thirdparty/squish/singlecolourlookup.inl
+++ /dev/null
@@ -1,1064 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-static SingleColourLookup const lookup_5_3[] =
-{
- { { { 0, 0, 0 }, { 0, 0, 0 } } },
- { { { 0, 0, 1 }, { 0, 0, 1 } } },
- { { { 0, 0, 2 }, { 0, 0, 2 } } },
- { { { 0, 0, 3 }, { 0, 1, 1 } } },
- { { { 0, 0, 4 }, { 0, 1, 0 } } },
- { { { 1, 0, 3 }, { 0, 1, 1 } } },
- { { { 1, 0, 2 }, { 0, 1, 2 } } },
- { { { 1, 0, 1 }, { 0, 2, 1 } } },
- { { { 1, 0, 0 }, { 0, 2, 0 } } },
- { { { 1, 0, 1 }, { 0, 2, 1 } } },
- { { { 1, 0, 2 }, { 0, 2, 2 } } },
- { { { 1, 0, 3 }, { 0, 3, 1 } } },
- { { { 1, 0, 4 }, { 0, 3, 0 } } },
- { { { 2, 0, 3 }, { 0, 3, 1 } } },
- { { { 2, 0, 2 }, { 0, 3, 2 } } },
- { { { 2, 0, 1 }, { 0, 4, 1 } } },
- { { { 2, 0, 0 }, { 0, 4, 0 } } },
- { { { 2, 0, 1 }, { 0, 4, 1 } } },
- { { { 2, 0, 2 }, { 0, 4, 2 } } },
- { { { 2, 0, 3 }, { 0, 5, 1 } } },
- { { { 2, 0, 4 }, { 0, 5, 0 } } },
- { { { 3, 0, 3 }, { 0, 5, 1 } } },
- { { { 3, 0, 2 }, { 0, 5, 2 } } },
- { { { 3, 0, 1 }, { 0, 6, 1 } } },
- { { { 3, 0, 0 }, { 0, 6, 0 } } },
- { { { 3, 0, 1 }, { 0, 6, 1 } } },
- { { { 3, 0, 2 }, { 0, 6, 2 } } },
- { { { 3, 0, 3 }, { 0, 7, 1 } } },
- { { { 3, 0, 4 }, { 0, 7, 0 } } },
- { { { 4, 0, 4 }, { 0, 7, 1 } } },
- { { { 4, 0, 3 }, { 0, 7, 2 } } },
- { { { 4, 0, 2 }, { 1, 7, 1 } } },
- { { { 4, 0, 1 }, { 1, 7, 0 } } },
- { { { 4, 0, 0 }, { 0, 8, 0 } } },
- { { { 4, 0, 1 }, { 0, 8, 1 } } },
- { { { 4, 0, 2 }, { 2, 7, 1 } } },
- { { { 4, 0, 3 }, { 2, 7, 0 } } },
- { { { 4, 0, 4 }, { 0, 9, 0 } } },
- { { { 5, 0, 3 }, { 0, 9, 1 } } },
- { { { 5, 0, 2 }, { 3, 7, 1 } } },
- { { { 5, 0, 1 }, { 3, 7, 0 } } },
- { { { 5, 0, 0 }, { 0, 10, 0 } } },
- { { { 5, 0, 1 }, { 0, 10, 1 } } },
- { { { 5, 0, 2 }, { 0, 10, 2 } } },
- { { { 5, 0, 3 }, { 0, 11, 1 } } },
- { { { 5, 0, 4 }, { 0, 11, 0 } } },
- { { { 6, 0, 3 }, { 0, 11, 1 } } },
- { { { 6, 0, 2 }, { 0, 11, 2 } } },
- { { { 6, 0, 1 }, { 0, 12, 1 } } },
- { { { 6, 0, 0 }, { 0, 12, 0 } } },
- { { { 6, 0, 1 }, { 0, 12, 1 } } },
- { { { 6, 0, 2 }, { 0, 12, 2 } } },
- { { { 6, 0, 3 }, { 0, 13, 1 } } },
- { { { 6, 0, 4 }, { 0, 13, 0 } } },
- { { { 7, 0, 3 }, { 0, 13, 1 } } },
- { { { 7, 0, 2 }, { 0, 13, 2 } } },
- { { { 7, 0, 1 }, { 0, 14, 1 } } },
- { { { 7, 0, 0 }, { 0, 14, 0 } } },
- { { { 7, 0, 1 }, { 0, 14, 1 } } },
- { { { 7, 0, 2 }, { 0, 14, 2 } } },
- { { { 7, 0, 3 }, { 0, 15, 1 } } },
- { { { 7, 0, 4 }, { 0, 15, 0 } } },
- { { { 8, 0, 4 }, { 0, 15, 1 } } },
- { { { 8, 0, 3 }, { 0, 15, 2 } } },
- { { { 8, 0, 2 }, { 1, 15, 1 } } },
- { { { 8, 0, 1 }, { 1, 15, 0 } } },
- { { { 8, 0, 0 }, { 0, 16, 0 } } },
- { { { 8, 0, 1 }, { 0, 16, 1 } } },
- { { { 8, 0, 2 }, { 2, 15, 1 } } },
- { { { 8, 0, 3 }, { 2, 15, 0 } } },
- { { { 8, 0, 4 }, { 0, 17, 0 } } },
- { { { 9, 0, 3 }, { 0, 17, 1 } } },
- { { { 9, 0, 2 }, { 3, 15, 1 } } },
- { { { 9, 0, 1 }, { 3, 15, 0 } } },
- { { { 9, 0, 0 }, { 0, 18, 0 } } },
- { { { 9, 0, 1 }, { 0, 18, 1 } } },
- { { { 9, 0, 2 }, { 0, 18, 2 } } },
- { { { 9, 0, 3 }, { 0, 19, 1 } } },
- { { { 9, 0, 4 }, { 0, 19, 0 } } },
- { { { 10, 0, 3 }, { 0, 19, 1 } } },
- { { { 10, 0, 2 }, { 0, 19, 2 } } },
- { { { 10, 0, 1 }, { 0, 20, 1 } } },
- { { { 10, 0, 0 }, { 0, 20, 0 } } },
- { { { 10, 0, 1 }, { 0, 20, 1 } } },
- { { { 10, 0, 2 }, { 0, 20, 2 } } },
- { { { 10, 0, 3 }, { 0, 21, 1 } } },
- { { { 10, 0, 4 }, { 0, 21, 0 } } },
- { { { 11, 0, 3 }, { 0, 21, 1 } } },
- { { { 11, 0, 2 }, { 0, 21, 2 } } },
- { { { 11, 0, 1 }, { 0, 22, 1 } } },
- { { { 11, 0, 0 }, { 0, 22, 0 } } },
- { { { 11, 0, 1 }, { 0, 22, 1 } } },
- { { { 11, 0, 2 }, { 0, 22, 2 } } },
- { { { 11, 0, 3 }, { 0, 23, 1 } } },
- { { { 11, 0, 4 }, { 0, 23, 0 } } },
- { { { 12, 0, 4 }, { 0, 23, 1 } } },
- { { { 12, 0, 3 }, { 0, 23, 2 } } },
- { { { 12, 0, 2 }, { 1, 23, 1 } } },
- { { { 12, 0, 1 }, { 1, 23, 0 } } },
- { { { 12, 0, 0 }, { 0, 24, 0 } } },
- { { { 12, 0, 1 }, { 0, 24, 1 } } },
- { { { 12, 0, 2 }, { 2, 23, 1 } } },
- { { { 12, 0, 3 }, { 2, 23, 0 } } },
- { { { 12, 0, 4 }, { 0, 25, 0 } } },
- { { { 13, 0, 3 }, { 0, 25, 1 } } },
- { { { 13, 0, 2 }, { 3, 23, 1 } } },
- { { { 13, 0, 1 }, { 3, 23, 0 } } },
- { { { 13, 0, 0 }, { 0, 26, 0 } } },
- { { { 13, 0, 1 }, { 0, 26, 1 } } },
- { { { 13, 0, 2 }, { 0, 26, 2 } } },
- { { { 13, 0, 3 }, { 0, 27, 1 } } },
- { { { 13, 0, 4 }, { 0, 27, 0 } } },
- { { { 14, 0, 3 }, { 0, 27, 1 } } },
- { { { 14, 0, 2 }, { 0, 27, 2 } } },
- { { { 14, 0, 1 }, { 0, 28, 1 } } },
- { { { 14, 0, 0 }, { 0, 28, 0 } } },
- { { { 14, 0, 1 }, { 0, 28, 1 } } },
- { { { 14, 0, 2 }, { 0, 28, 2 } } },
- { { { 14, 0, 3 }, { 0, 29, 1 } } },
- { { { 14, 0, 4 }, { 0, 29, 0 } } },
- { { { 15, 0, 3 }, { 0, 29, 1 } } },
- { { { 15, 0, 2 }, { 0, 29, 2 } } },
- { { { 15, 0, 1 }, { 0, 30, 1 } } },
- { { { 15, 0, 0 }, { 0, 30, 0 } } },
- { { { 15, 0, 1 }, { 0, 30, 1 } } },
- { { { 15, 0, 2 }, { 0, 30, 2 } } },
- { { { 15, 0, 3 }, { 0, 31, 1 } } },
- { { { 15, 0, 4 }, { 0, 31, 0 } } },
- { { { 16, 0, 4 }, { 0, 31, 1 } } },
- { { { 16, 0, 3 }, { 0, 31, 2 } } },
- { { { 16, 0, 2 }, { 1, 31, 1 } } },
- { { { 16, 0, 1 }, { 1, 31, 0 } } },
- { { { 16, 0, 0 }, { 4, 28, 0 } } },
- { { { 16, 0, 1 }, { 4, 28, 1 } } },
- { { { 16, 0, 2 }, { 2, 31, 1 } } },
- { { { 16, 0, 3 }, { 2, 31, 0 } } },
- { { { 16, 0, 4 }, { 4, 29, 0 } } },
- { { { 17, 0, 3 }, { 4, 29, 1 } } },
- { { { 17, 0, 2 }, { 3, 31, 1 } } },
- { { { 17, 0, 1 }, { 3, 31, 0 } } },
- { { { 17, 0, 0 }, { 4, 30, 0 } } },
- { { { 17, 0, 1 }, { 4, 30, 1 } } },
- { { { 17, 0, 2 }, { 4, 30, 2 } } },
- { { { 17, 0, 3 }, { 4, 31, 1 } } },
- { { { 17, 0, 4 }, { 4, 31, 0 } } },
- { { { 18, 0, 3 }, { 4, 31, 1 } } },
- { { { 18, 0, 2 }, { 4, 31, 2 } } },
- { { { 18, 0, 1 }, { 5, 31, 1 } } },
- { { { 18, 0, 0 }, { 5, 31, 0 } } },
- { { { 18, 0, 1 }, { 5, 31, 1 } } },
- { { { 18, 0, 2 }, { 5, 31, 2 } } },
- { { { 18, 0, 3 }, { 6, 31, 1 } } },
- { { { 18, 0, 4 }, { 6, 31, 0 } } },
- { { { 19, 0, 3 }, { 6, 31, 1 } } },
- { { { 19, 0, 2 }, { 6, 31, 2 } } },
- { { { 19, 0, 1 }, { 7, 31, 1 } } },
- { { { 19, 0, 0 }, { 7, 31, 0 } } },
- { { { 19, 0, 1 }, { 7, 31, 1 } } },
- { { { 19, 0, 2 }, { 7, 31, 2 } } },
- { { { 19, 0, 3 }, { 8, 31, 1 } } },
- { { { 19, 0, 4 }, { 8, 31, 0 } } },
- { { { 20, 0, 4 }, { 8, 31, 1 } } },
- { { { 20, 0, 3 }, { 8, 31, 2 } } },
- { { { 20, 0, 2 }, { 9, 31, 1 } } },
- { { { 20, 0, 1 }, { 9, 31, 0 } } },
- { { { 20, 0, 0 }, { 12, 28, 0 } } },
- { { { 20, 0, 1 }, { 12, 28, 1 } } },
- { { { 20, 0, 2 }, { 10, 31, 1 } } },
- { { { 20, 0, 3 }, { 10, 31, 0 } } },
- { { { 20, 0, 4 }, { 12, 29, 0 } } },
- { { { 21, 0, 3 }, { 12, 29, 1 } } },
- { { { 21, 0, 2 }, { 11, 31, 1 } } },
- { { { 21, 0, 1 }, { 11, 31, 0 } } },
- { { { 21, 0, 0 }, { 12, 30, 0 } } },
- { { { 21, 0, 1 }, { 12, 30, 1 } } },
- { { { 21, 0, 2 }, { 12, 30, 2 } } },
- { { { 21, 0, 3 }, { 12, 31, 1 } } },
- { { { 21, 0, 4 }, { 12, 31, 0 } } },
- { { { 22, 0, 3 }, { 12, 31, 1 } } },
- { { { 22, 0, 2 }, { 12, 31, 2 } } },
- { { { 22, 0, 1 }, { 13, 31, 1 } } },
- { { { 22, 0, 0 }, { 13, 31, 0 } } },
- { { { 22, 0, 1 }, { 13, 31, 1 } } },
- { { { 22, 0, 2 }, { 13, 31, 2 } } },
- { { { 22, 0, 3 }, { 14, 31, 1 } } },
- { { { 22, 0, 4 }, { 14, 31, 0 } } },
- { { { 23, 0, 3 }, { 14, 31, 1 } } },
- { { { 23, 0, 2 }, { 14, 31, 2 } } },
- { { { 23, 0, 1 }, { 15, 31, 1 } } },
- { { { 23, 0, 0 }, { 15, 31, 0 } } },
- { { { 23, 0, 1 }, { 15, 31, 1 } } },
- { { { 23, 0, 2 }, { 15, 31, 2 } } },
- { { { 23, 0, 3 }, { 16, 31, 1 } } },
- { { { 23, 0, 4 }, { 16, 31, 0 } } },
- { { { 24, 0, 4 }, { 16, 31, 1 } } },
- { { { 24, 0, 3 }, { 16, 31, 2 } } },
- { { { 24, 0, 2 }, { 17, 31, 1 } } },
- { { { 24, 0, 1 }, { 17, 31, 0 } } },
- { { { 24, 0, 0 }, { 20, 28, 0 } } },
- { { { 24, 0, 1 }, { 20, 28, 1 } } },
- { { { 24, 0, 2 }, { 18, 31, 1 } } },
- { { { 24, 0, 3 }, { 18, 31, 0 } } },
- { { { 24, 0, 4 }, { 20, 29, 0 } } },
- { { { 25, 0, 3 }, { 20, 29, 1 } } },
- { { { 25, 0, 2 }, { 19, 31, 1 } } },
- { { { 25, 0, 1 }, { 19, 31, 0 } } },
- { { { 25, 0, 0 }, { 20, 30, 0 } } },
- { { { 25, 0, 1 }, { 20, 30, 1 } } },
- { { { 25, 0, 2 }, { 20, 30, 2 } } },
- { { { 25, 0, 3 }, { 20, 31, 1 } } },
- { { { 25, 0, 4 }, { 20, 31, 0 } } },
- { { { 26, 0, 3 }, { 20, 31, 1 } } },
- { { { 26, 0, 2 }, { 20, 31, 2 } } },
- { { { 26, 0, 1 }, { 21, 31, 1 } } },
- { { { 26, 0, 0 }, { 21, 31, 0 } } },
- { { { 26, 0, 1 }, { 21, 31, 1 } } },
- { { { 26, 0, 2 }, { 21, 31, 2 } } },
- { { { 26, 0, 3 }, { 22, 31, 1 } } },
- { { { 26, 0, 4 }, { 22, 31, 0 } } },
- { { { 27, 0, 3 }, { 22, 31, 1 } } },
- { { { 27, 0, 2 }, { 22, 31, 2 } } },
- { { { 27, 0, 1 }, { 23, 31, 1 } } },
- { { { 27, 0, 0 }, { 23, 31, 0 } } },
- { { { 27, 0, 1 }, { 23, 31, 1 } } },
- { { { 27, 0, 2 }, { 23, 31, 2 } } },
- { { { 27, 0, 3 }, { 24, 31, 1 } } },
- { { { 27, 0, 4 }, { 24, 31, 0 } } },
- { { { 28, 0, 4 }, { 24, 31, 1 } } },
- { { { 28, 0, 3 }, { 24, 31, 2 } } },
- { { { 28, 0, 2 }, { 25, 31, 1 } } },
- { { { 28, 0, 1 }, { 25, 31, 0 } } },
- { { { 28, 0, 0 }, { 28, 28, 0 } } },
- { { { 28, 0, 1 }, { 28, 28, 1 } } },
- { { { 28, 0, 2 }, { 26, 31, 1 } } },
- { { { 28, 0, 3 }, { 26, 31, 0 } } },
- { { { 28, 0, 4 }, { 28, 29, 0 } } },
- { { { 29, 0, 3 }, { 28, 29, 1 } } },
- { { { 29, 0, 2 }, { 27, 31, 1 } } },
- { { { 29, 0, 1 }, { 27, 31, 0 } } },
- { { { 29, 0, 0 }, { 28, 30, 0 } } },
- { { { 29, 0, 1 }, { 28, 30, 1 } } },
- { { { 29, 0, 2 }, { 28, 30, 2 } } },
- { { { 29, 0, 3 }, { 28, 31, 1 } } },
- { { { 29, 0, 4 }, { 28, 31, 0 } } },
- { { { 30, 0, 3 }, { 28, 31, 1 } } },
- { { { 30, 0, 2 }, { 28, 31, 2 } } },
- { { { 30, 0, 1 }, { 29, 31, 1 } } },
- { { { 30, 0, 0 }, { 29, 31, 0 } } },
- { { { 30, 0, 1 }, { 29, 31, 1 } } },
- { { { 30, 0, 2 }, { 29, 31, 2 } } },
- { { { 30, 0, 3 }, { 30, 31, 1 } } },
- { { { 30, 0, 4 }, { 30, 31, 0 } } },
- { { { 31, 0, 3 }, { 30, 31, 1 } } },
- { { { 31, 0, 2 }, { 30, 31, 2 } } },
- { { { 31, 0, 1 }, { 31, 31, 1 } } },
- { { { 31, 0, 0 }, { 31, 31, 0 } } }
-};
-
-static SingleColourLookup const lookup_6_3[] =
-{
- { { { 0, 0, 0 }, { 0, 0, 0 } } },
- { { { 0, 0, 1 }, { 0, 1, 1 } } },
- { { { 0, 0, 2 }, { 0, 1, 0 } } },
- { { { 1, 0, 1 }, { 0, 2, 1 } } },
- { { { 1, 0, 0 }, { 0, 2, 0 } } },
- { { { 1, 0, 1 }, { 0, 3, 1 } } },
- { { { 1, 0, 2 }, { 0, 3, 0 } } },
- { { { 2, 0, 1 }, { 0, 4, 1 } } },
- { { { 2, 0, 0 }, { 0, 4, 0 } } },
- { { { 2, 0, 1 }, { 0, 5, 1 } } },
- { { { 2, 0, 2 }, { 0, 5, 0 } } },
- { { { 3, 0, 1 }, { 0, 6, 1 } } },
- { { { 3, 0, 0 }, { 0, 6, 0 } } },
- { { { 3, 0, 1 }, { 0, 7, 1 } } },
- { { { 3, 0, 2 }, { 0, 7, 0 } } },
- { { { 4, 0, 1 }, { 0, 8, 1 } } },
- { { { 4, 0, 0 }, { 0, 8, 0 } } },
- { { { 4, 0, 1 }, { 0, 9, 1 } } },
- { { { 4, 0, 2 }, { 0, 9, 0 } } },
- { { { 5, 0, 1 }, { 0, 10, 1 } } },
- { { { 5, 0, 0 }, { 0, 10, 0 } } },
- { { { 5, 0, 1 }, { 0, 11, 1 } } },
- { { { 5, 0, 2 }, { 0, 11, 0 } } },
- { { { 6, 0, 1 }, { 0, 12, 1 } } },
- { { { 6, 0, 0 }, { 0, 12, 0 } } },
- { { { 6, 0, 1 }, { 0, 13, 1 } } },
- { { { 6, 0, 2 }, { 0, 13, 0 } } },
- { { { 7, 0, 1 }, { 0, 14, 1 } } },
- { { { 7, 0, 0 }, { 0, 14, 0 } } },
- { { { 7, 0, 1 }, { 0, 15, 1 } } },
- { { { 7, 0, 2 }, { 0, 15, 0 } } },
- { { { 8, 0, 1 }, { 0, 16, 1 } } },
- { { { 8, 0, 0 }, { 0, 16, 0 } } },
- { { { 8, 0, 1 }, { 0, 17, 1 } } },
- { { { 8, 0, 2 }, { 0, 17, 0 } } },
- { { { 9, 0, 1 }, { 0, 18, 1 } } },
- { { { 9, 0, 0 }, { 0, 18, 0 } } },
- { { { 9, 0, 1 }, { 0, 19, 1 } } },
- { { { 9, 0, 2 }, { 0, 19, 0 } } },
- { { { 10, 0, 1 }, { 0, 20, 1 } } },
- { { { 10, 0, 0 }, { 0, 20, 0 } } },
- { { { 10, 0, 1 }, { 0, 21, 1 } } },
- { { { 10, 0, 2 }, { 0, 21, 0 } } },
- { { { 11, 0, 1 }, { 0, 22, 1 } } },
- { { { 11, 0, 0 }, { 0, 22, 0 } } },
- { { { 11, 0, 1 }, { 0, 23, 1 } } },
- { { { 11, 0, 2 }, { 0, 23, 0 } } },
- { { { 12, 0, 1 }, { 0, 24, 1 } } },
- { { { 12, 0, 0 }, { 0, 24, 0 } } },
- { { { 12, 0, 1 }, { 0, 25, 1 } } },
- { { { 12, 0, 2 }, { 0, 25, 0 } } },
- { { { 13, 0, 1 }, { 0, 26, 1 } } },
- { { { 13, 0, 0 }, { 0, 26, 0 } } },
- { { { 13, 0, 1 }, { 0, 27, 1 } } },
- { { { 13, 0, 2 }, { 0, 27, 0 } } },
- { { { 14, 0, 1 }, { 0, 28, 1 } } },
- { { { 14, 0, 0 }, { 0, 28, 0 } } },
- { { { 14, 0, 1 }, { 0, 29, 1 } } },
- { { { 14, 0, 2 }, { 0, 29, 0 } } },
- { { { 15, 0, 1 }, { 0, 30, 1 } } },
- { { { 15, 0, 0 }, { 0, 30, 0 } } },
- { { { 15, 0, 1 }, { 0, 31, 1 } } },
- { { { 15, 0, 2 }, { 0, 31, 0 } } },
- { { { 16, 0, 2 }, { 1, 31, 1 } } },
- { { { 16, 0, 1 }, { 1, 31, 0 } } },
- { { { 16, 0, 0 }, { 0, 32, 0 } } },
- { { { 16, 0, 1 }, { 2, 31, 0 } } },
- { { { 16, 0, 2 }, { 0, 33, 0 } } },
- { { { 17, 0, 1 }, { 3, 31, 0 } } },
- { { { 17, 0, 0 }, { 0, 34, 0 } } },
- { { { 17, 0, 1 }, { 4, 31, 0 } } },
- { { { 17, 0, 2 }, { 0, 35, 0 } } },
- { { { 18, 0, 1 }, { 5, 31, 0 } } },
- { { { 18, 0, 0 }, { 0, 36, 0 } } },
- { { { 18, 0, 1 }, { 6, 31, 0 } } },
- { { { 18, 0, 2 }, { 0, 37, 0 } } },
- { { { 19, 0, 1 }, { 7, 31, 0 } } },
- { { { 19, 0, 0 }, { 0, 38, 0 } } },
- { { { 19, 0, 1 }, { 8, 31, 0 } } },
- { { { 19, 0, 2 }, { 0, 39, 0 } } },
- { { { 20, 0, 1 }, { 9, 31, 0 } } },
- { { { 20, 0, 0 }, { 0, 40, 0 } } },
- { { { 20, 0, 1 }, { 10, 31, 0 } } },
- { { { 20, 0, 2 }, { 0, 41, 0 } } },
- { { { 21, 0, 1 }, { 11, 31, 0 } } },
- { { { 21, 0, 0 }, { 0, 42, 0 } } },
- { { { 21, 0, 1 }, { 12, 31, 0 } } },
- { { { 21, 0, 2 }, { 0, 43, 0 } } },
- { { { 22, 0, 1 }, { 13, 31, 0 } } },
- { { { 22, 0, 0 }, { 0, 44, 0 } } },
- { { { 22, 0, 1 }, { 14, 31, 0 } } },
- { { { 22, 0, 2 }, { 0, 45, 0 } } },
- { { { 23, 0, 1 }, { 15, 31, 0 } } },
- { { { 23, 0, 0 }, { 0, 46, 0 } } },
- { { { 23, 0, 1 }, { 0, 47, 1 } } },
- { { { 23, 0, 2 }, { 0, 47, 0 } } },
- { { { 24, 0, 1 }, { 0, 48, 1 } } },
- { { { 24, 0, 0 }, { 0, 48, 0 } } },
- { { { 24, 0, 1 }, { 0, 49, 1 } } },
- { { { 24, 0, 2 }, { 0, 49, 0 } } },
- { { { 25, 0, 1 }, { 0, 50, 1 } } },
- { { { 25, 0, 0 }, { 0, 50, 0 } } },
- { { { 25, 0, 1 }, { 0, 51, 1 } } },
- { { { 25, 0, 2 }, { 0, 51, 0 } } },
- { { { 26, 0, 1 }, { 0, 52, 1 } } },
- { { { 26, 0, 0 }, { 0, 52, 0 } } },
- { { { 26, 0, 1 }, { 0, 53, 1 } } },
- { { { 26, 0, 2 }, { 0, 53, 0 } } },
- { { { 27, 0, 1 }, { 0, 54, 1 } } },
- { { { 27, 0, 0 }, { 0, 54, 0 } } },
- { { { 27, 0, 1 }, { 0, 55, 1 } } },
- { { { 27, 0, 2 }, { 0, 55, 0 } } },
- { { { 28, 0, 1 }, { 0, 56, 1 } } },
- { { { 28, 0, 0 }, { 0, 56, 0 } } },
- { { { 28, 0, 1 }, { 0, 57, 1 } } },
- { { { 28, 0, 2 }, { 0, 57, 0 } } },
- { { { 29, 0, 1 }, { 0, 58, 1 } } },
- { { { 29, 0, 0 }, { 0, 58, 0 } } },
- { { { 29, 0, 1 }, { 0, 59, 1 } } },
- { { { 29, 0, 2 }, { 0, 59, 0 } } },
- { { { 30, 0, 1 }, { 0, 60, 1 } } },
- { { { 30, 0, 0 }, { 0, 60, 0 } } },
- { { { 30, 0, 1 }, { 0, 61, 1 } } },
- { { { 30, 0, 2 }, { 0, 61, 0 } } },
- { { { 31, 0, 1 }, { 0, 62, 1 } } },
- { { { 31, 0, 0 }, { 0, 62, 0 } } },
- { { { 31, 0, 1 }, { 0, 63, 1 } } },
- { { { 31, 0, 2 }, { 0, 63, 0 } } },
- { { { 32, 0, 2 }, { 1, 63, 1 } } },
- { { { 32, 0, 1 }, { 1, 63, 0 } } },
- { { { 32, 0, 0 }, { 16, 48, 0 } } },
- { { { 32, 0, 1 }, { 2, 63, 0 } } },
- { { { 32, 0, 2 }, { 16, 49, 0 } } },
- { { { 33, 0, 1 }, { 3, 63, 0 } } },
- { { { 33, 0, 0 }, { 16, 50, 0 } } },
- { { { 33, 0, 1 }, { 4, 63, 0 } } },
- { { { 33, 0, 2 }, { 16, 51, 0 } } },
- { { { 34, 0, 1 }, { 5, 63, 0 } } },
- { { { 34, 0, 0 }, { 16, 52, 0 } } },
- { { { 34, 0, 1 }, { 6, 63, 0 } } },
- { { { 34, 0, 2 }, { 16, 53, 0 } } },
- { { { 35, 0, 1 }, { 7, 63, 0 } } },
- { { { 35, 0, 0 }, { 16, 54, 0 } } },
- { { { 35, 0, 1 }, { 8, 63, 0 } } },
- { { { 35, 0, 2 }, { 16, 55, 0 } } },
- { { { 36, 0, 1 }, { 9, 63, 0 } } },
- { { { 36, 0, 0 }, { 16, 56, 0 } } },
- { { { 36, 0, 1 }, { 10, 63, 0 } } },
- { { { 36, 0, 2 }, { 16, 57, 0 } } },
- { { { 37, 0, 1 }, { 11, 63, 0 } } },
- { { { 37, 0, 0 }, { 16, 58, 0 } } },
- { { { 37, 0, 1 }, { 12, 63, 0 } } },
- { { { 37, 0, 2 }, { 16, 59, 0 } } },
- { { { 38, 0, 1 }, { 13, 63, 0 } } },
- { { { 38, 0, 0 }, { 16, 60, 0 } } },
- { { { 38, 0, 1 }, { 14, 63, 0 } } },
- { { { 38, 0, 2 }, { 16, 61, 0 } } },
- { { { 39, 0, 1 }, { 15, 63, 0 } } },
- { { { 39, 0, 0 }, { 16, 62, 0 } } },
- { { { 39, 0, 1 }, { 16, 63, 1 } } },
- { { { 39, 0, 2 }, { 16, 63, 0 } } },
- { { { 40, 0, 1 }, { 17, 63, 1 } } },
- { { { 40, 0, 0 }, { 17, 63, 0 } } },
- { { { 40, 0, 1 }, { 18, 63, 1 } } },
- { { { 40, 0, 2 }, { 18, 63, 0 } } },
- { { { 41, 0, 1 }, { 19, 63, 1 } } },
- { { { 41, 0, 0 }, { 19, 63, 0 } } },
- { { { 41, 0, 1 }, { 20, 63, 1 } } },
- { { { 41, 0, 2 }, { 20, 63, 0 } } },
- { { { 42, 0, 1 }, { 21, 63, 1 } } },
- { { { 42, 0, 0 }, { 21, 63, 0 } } },
- { { { 42, 0, 1 }, { 22, 63, 1 } } },
- { { { 42, 0, 2 }, { 22, 63, 0 } } },
- { { { 43, 0, 1 }, { 23, 63, 1 } } },
- { { { 43, 0, 0 }, { 23, 63, 0 } } },
- { { { 43, 0, 1 }, { 24, 63, 1 } } },
- { { { 43, 0, 2 }, { 24, 63, 0 } } },
- { { { 44, 0, 1 }, { 25, 63, 1 } } },
- { { { 44, 0, 0 }, { 25, 63, 0 } } },
- { { { 44, 0, 1 }, { 26, 63, 1 } } },
- { { { 44, 0, 2 }, { 26, 63, 0 } } },
- { { { 45, 0, 1 }, { 27, 63, 1 } } },
- { { { 45, 0, 0 }, { 27, 63, 0 } } },
- { { { 45, 0, 1 }, { 28, 63, 1 } } },
- { { { 45, 0, 2 }, { 28, 63, 0 } } },
- { { { 46, 0, 1 }, { 29, 63, 1 } } },
- { { { 46, 0, 0 }, { 29, 63, 0 } } },
- { { { 46, 0, 1 }, { 30, 63, 1 } } },
- { { { 46, 0, 2 }, { 30, 63, 0 } } },
- { { { 47, 0, 1 }, { 31, 63, 1 } } },
- { { { 47, 0, 0 }, { 31, 63, 0 } } },
- { { { 47, 0, 1 }, { 32, 63, 1 } } },
- { { { 47, 0, 2 }, { 32, 63, 0 } } },
- { { { 48, 0, 2 }, { 33, 63, 1 } } },
- { { { 48, 0, 1 }, { 33, 63, 0 } } },
- { { { 48, 0, 0 }, { 48, 48, 0 } } },
- { { { 48, 0, 1 }, { 34, 63, 0 } } },
- { { { 48, 0, 2 }, { 48, 49, 0 } } },
- { { { 49, 0, 1 }, { 35, 63, 0 } } },
- { { { 49, 0, 0 }, { 48, 50, 0 } } },
- { { { 49, 0, 1 }, { 36, 63, 0 } } },
- { { { 49, 0, 2 }, { 48, 51, 0 } } },
- { { { 50, 0, 1 }, { 37, 63, 0 } } },
- { { { 50, 0, 0 }, { 48, 52, 0 } } },
- { { { 50, 0, 1 }, { 38, 63, 0 } } },
- { { { 50, 0, 2 }, { 48, 53, 0 } } },
- { { { 51, 0, 1 }, { 39, 63, 0 } } },
- { { { 51, 0, 0 }, { 48, 54, 0 } } },
- { { { 51, 0, 1 }, { 40, 63, 0 } } },
- { { { 51, 0, 2 }, { 48, 55, 0 } } },
- { { { 52, 0, 1 }, { 41, 63, 0 } } },
- { { { 52, 0, 0 }, { 48, 56, 0 } } },
- { { { 52, 0, 1 }, { 42, 63, 0 } } },
- { { { 52, 0, 2 }, { 48, 57, 0 } } },
- { { { 53, 0, 1 }, { 43, 63, 0 } } },
- { { { 53, 0, 0 }, { 48, 58, 0 } } },
- { { { 53, 0, 1 }, { 44, 63, 0 } } },
- { { { 53, 0, 2 }, { 48, 59, 0 } } },
- { { { 54, 0, 1 }, { 45, 63, 0 } } },
- { { { 54, 0, 0 }, { 48, 60, 0 } } },
- { { { 54, 0, 1 }, { 46, 63, 0 } } },
- { { { 54, 0, 2 }, { 48, 61, 0 } } },
- { { { 55, 0, 1 }, { 47, 63, 0 } } },
- { { { 55, 0, 0 }, { 48, 62, 0 } } },
- { { { 55, 0, 1 }, { 48, 63, 1 } } },
- { { { 55, 0, 2 }, { 48, 63, 0 } } },
- { { { 56, 0, 1 }, { 49, 63, 1 } } },
- { { { 56, 0, 0 }, { 49, 63, 0 } } },
- { { { 56, 0, 1 }, { 50, 63, 1 } } },
- { { { 56, 0, 2 }, { 50, 63, 0 } } },
- { { { 57, 0, 1 }, { 51, 63, 1 } } },
- { { { 57, 0, 0 }, { 51, 63, 0 } } },
- { { { 57, 0, 1 }, { 52, 63, 1 } } },
- { { { 57, 0, 2 }, { 52, 63, 0 } } },
- { { { 58, 0, 1 }, { 53, 63, 1 } } },
- { { { 58, 0, 0 }, { 53, 63, 0 } } },
- { { { 58, 0, 1 }, { 54, 63, 1 } } },
- { { { 58, 0, 2 }, { 54, 63, 0 } } },
- { { { 59, 0, 1 }, { 55, 63, 1 } } },
- { { { 59, 0, 0 }, { 55, 63, 0 } } },
- { { { 59, 0, 1 }, { 56, 63, 1 } } },
- { { { 59, 0, 2 }, { 56, 63, 0 } } },
- { { { 60, 0, 1 }, { 57, 63, 1 } } },
- { { { 60, 0, 0 }, { 57, 63, 0 } } },
- { { { 60, 0, 1 }, { 58, 63, 1 } } },
- { { { 60, 0, 2 }, { 58, 63, 0 } } },
- { { { 61, 0, 1 }, { 59, 63, 1 } } },
- { { { 61, 0, 0 }, { 59, 63, 0 } } },
- { { { 61, 0, 1 }, { 60, 63, 1 } } },
- { { { 61, 0, 2 }, { 60, 63, 0 } } },
- { { { 62, 0, 1 }, { 61, 63, 1 } } },
- { { { 62, 0, 0 }, { 61, 63, 0 } } },
- { { { 62, 0, 1 }, { 62, 63, 1 } } },
- { { { 62, 0, 2 }, { 62, 63, 0 } } },
- { { { 63, 0, 1 }, { 63, 63, 1 } } },
- { { { 63, 0, 0 }, { 63, 63, 0 } } }
-};
-
-static SingleColourLookup const lookup_5_4[] =
-{
- { { { 0, 0, 0 }, { 0, 0, 0 } } },
- { { { 0, 0, 1 }, { 0, 1, 1 } } },
- { { { 0, 0, 2 }, { 0, 1, 0 } } },
- { { { 0, 0, 3 }, { 0, 1, 1 } } },
- { { { 0, 0, 4 }, { 0, 2, 1 } } },
- { { { 1, 0, 3 }, { 0, 2, 0 } } },
- { { { 1, 0, 2 }, { 0, 2, 1 } } },
- { { { 1, 0, 1 }, { 0, 3, 1 } } },
- { { { 1, 0, 0 }, { 0, 3, 0 } } },
- { { { 1, 0, 1 }, { 1, 2, 1 } } },
- { { { 1, 0, 2 }, { 1, 2, 0 } } },
- { { { 1, 0, 3 }, { 0, 4, 0 } } },
- { { { 1, 0, 4 }, { 0, 5, 1 } } },
- { { { 2, 0, 3 }, { 0, 5, 0 } } },
- { { { 2, 0, 2 }, { 0, 5, 1 } } },
- { { { 2, 0, 1 }, { 0, 6, 1 } } },
- { { { 2, 0, 0 }, { 0, 6, 0 } } },
- { { { 2, 0, 1 }, { 2, 3, 1 } } },
- { { { 2, 0, 2 }, { 2, 3, 0 } } },
- { { { 2, 0, 3 }, { 0, 7, 0 } } },
- { { { 2, 0, 4 }, { 1, 6, 1 } } },
- { { { 3, 0, 3 }, { 1, 6, 0 } } },
- { { { 3, 0, 2 }, { 0, 8, 0 } } },
- { { { 3, 0, 1 }, { 0, 9, 1 } } },
- { { { 3, 0, 0 }, { 0, 9, 0 } } },
- { { { 3, 0, 1 }, { 0, 9, 1 } } },
- { { { 3, 0, 2 }, { 0, 10, 1 } } },
- { { { 3, 0, 3 }, { 0, 10, 0 } } },
- { { { 3, 0, 4 }, { 2, 7, 1 } } },
- { { { 4, 0, 4 }, { 2, 7, 0 } } },
- { { { 4, 0, 3 }, { 0, 11, 0 } } },
- { { { 4, 0, 2 }, { 1, 10, 1 } } },
- { { { 4, 0, 1 }, { 1, 10, 0 } } },
- { { { 4, 0, 0 }, { 0, 12, 0 } } },
- { { { 4, 0, 1 }, { 0, 13, 1 } } },
- { { { 4, 0, 2 }, { 0, 13, 0 } } },
- { { { 4, 0, 3 }, { 0, 13, 1 } } },
- { { { 4, 0, 4 }, { 0, 14, 1 } } },
- { { { 5, 0, 3 }, { 0, 14, 0 } } },
- { { { 5, 0, 2 }, { 2, 11, 1 } } },
- { { { 5, 0, 1 }, { 2, 11, 0 } } },
- { { { 5, 0, 0 }, { 0, 15, 0 } } },
- { { { 5, 0, 1 }, { 1, 14, 1 } } },
- { { { 5, 0, 2 }, { 1, 14, 0 } } },
- { { { 5, 0, 3 }, { 0, 16, 0 } } },
- { { { 5, 0, 4 }, { 0, 17, 1 } } },
- { { { 6, 0, 3 }, { 0, 17, 0 } } },
- { { { 6, 0, 2 }, { 0, 17, 1 } } },
- { { { 6, 0, 1 }, { 0, 18, 1 } } },
- { { { 6, 0, 0 }, { 0, 18, 0 } } },
- { { { 6, 0, 1 }, { 2, 15, 1 } } },
- { { { 6, 0, 2 }, { 2, 15, 0 } } },
- { { { 6, 0, 3 }, { 0, 19, 0 } } },
- { { { 6, 0, 4 }, { 1, 18, 1 } } },
- { { { 7, 0, 3 }, { 1, 18, 0 } } },
- { { { 7, 0, 2 }, { 0, 20, 0 } } },
- { { { 7, 0, 1 }, { 0, 21, 1 } } },
- { { { 7, 0, 0 }, { 0, 21, 0 } } },
- { { { 7, 0, 1 }, { 0, 21, 1 } } },
- { { { 7, 0, 2 }, { 0, 22, 1 } } },
- { { { 7, 0, 3 }, { 0, 22, 0 } } },
- { { { 7, 0, 4 }, { 2, 19, 1 } } },
- { { { 8, 0, 4 }, { 2, 19, 0 } } },
- { { { 8, 0, 3 }, { 0, 23, 0 } } },
- { { { 8, 0, 2 }, { 1, 22, 1 } } },
- { { { 8, 0, 1 }, { 1, 22, 0 } } },
- { { { 8, 0, 0 }, { 0, 24, 0 } } },
- { { { 8, 0, 1 }, { 0, 25, 1 } } },
- { { { 8, 0, 2 }, { 0, 25, 0 } } },
- { { { 8, 0, 3 }, { 0, 25, 1 } } },
- { { { 8, 0, 4 }, { 0, 26, 1 } } },
- { { { 9, 0, 3 }, { 0, 26, 0 } } },
- { { { 9, 0, 2 }, { 2, 23, 1 } } },
- { { { 9, 0, 1 }, { 2, 23, 0 } } },
- { { { 9, 0, 0 }, { 0, 27, 0 } } },
- { { { 9, 0, 1 }, { 1, 26, 1 } } },
- { { { 9, 0, 2 }, { 1, 26, 0 } } },
- { { { 9, 0, 3 }, { 0, 28, 0 } } },
- { { { 9, 0, 4 }, { 0, 29, 1 } } },
- { { { 10, 0, 3 }, { 0, 29, 0 } } },
- { { { 10, 0, 2 }, { 0, 29, 1 } } },
- { { { 10, 0, 1 }, { 0, 30, 1 } } },
- { { { 10, 0, 0 }, { 0, 30, 0 } } },
- { { { 10, 0, 1 }, { 2, 27, 1 } } },
- { { { 10, 0, 2 }, { 2, 27, 0 } } },
- { { { 10, 0, 3 }, { 0, 31, 0 } } },
- { { { 10, 0, 4 }, { 1, 30, 1 } } },
- { { { 11, 0, 3 }, { 1, 30, 0 } } },
- { { { 11, 0, 2 }, { 4, 24, 0 } } },
- { { { 11, 0, 1 }, { 1, 31, 1 } } },
- { { { 11, 0, 0 }, { 1, 31, 0 } } },
- { { { 11, 0, 1 }, { 1, 31, 1 } } },
- { { { 11, 0, 2 }, { 2, 30, 1 } } },
- { { { 11, 0, 3 }, { 2, 30, 0 } } },
- { { { 11, 0, 4 }, { 2, 31, 1 } } },
- { { { 12, 0, 4 }, { 2, 31, 0 } } },
- { { { 12, 0, 3 }, { 4, 27, 0 } } },
- { { { 12, 0, 2 }, { 3, 30, 1 } } },
- { { { 12, 0, 1 }, { 3, 30, 0 } } },
- { { { 12, 0, 0 }, { 4, 28, 0 } } },
- { { { 12, 0, 1 }, { 3, 31, 1 } } },
- { { { 12, 0, 2 }, { 3, 31, 0 } } },
- { { { 12, 0, 3 }, { 3, 31, 1 } } },
- { { { 12, 0, 4 }, { 4, 30, 1 } } },
- { { { 13, 0, 3 }, { 4, 30, 0 } } },
- { { { 13, 0, 2 }, { 6, 27, 1 } } },
- { { { 13, 0, 1 }, { 6, 27, 0 } } },
- { { { 13, 0, 0 }, { 4, 31, 0 } } },
- { { { 13, 0, 1 }, { 5, 30, 1 } } },
- { { { 13, 0, 2 }, { 5, 30, 0 } } },
- { { { 13, 0, 3 }, { 8, 24, 0 } } },
- { { { 13, 0, 4 }, { 5, 31, 1 } } },
- { { { 14, 0, 3 }, { 5, 31, 0 } } },
- { { { 14, 0, 2 }, { 5, 31, 1 } } },
- { { { 14, 0, 1 }, { 6, 30, 1 } } },
- { { { 14, 0, 0 }, { 6, 30, 0 } } },
- { { { 14, 0, 1 }, { 6, 31, 1 } } },
- { { { 14, 0, 2 }, { 6, 31, 0 } } },
- { { { 14, 0, 3 }, { 8, 27, 0 } } },
- { { { 14, 0, 4 }, { 7, 30, 1 } } },
- { { { 15, 0, 3 }, { 7, 30, 0 } } },
- { { { 15, 0, 2 }, { 8, 28, 0 } } },
- { { { 15, 0, 1 }, { 7, 31, 1 } } },
- { { { 15, 0, 0 }, { 7, 31, 0 } } },
- { { { 15, 0, 1 }, { 7, 31, 1 } } },
- { { { 15, 0, 2 }, { 8, 30, 1 } } },
- { { { 15, 0, 3 }, { 8, 30, 0 } } },
- { { { 15, 0, 4 }, { 10, 27, 1 } } },
- { { { 16, 0, 4 }, { 10, 27, 0 } } },
- { { { 16, 0, 3 }, { 8, 31, 0 } } },
- { { { 16, 0, 2 }, { 9, 30, 1 } } },
- { { { 16, 0, 1 }, { 9, 30, 0 } } },
- { { { 16, 0, 0 }, { 12, 24, 0 } } },
- { { { 16, 0, 1 }, { 9, 31, 1 } } },
- { { { 16, 0, 2 }, { 9, 31, 0 } } },
- { { { 16, 0, 3 }, { 9, 31, 1 } } },
- { { { 16, 0, 4 }, { 10, 30, 1 } } },
- { { { 17, 0, 3 }, { 10, 30, 0 } } },
- { { { 17, 0, 2 }, { 10, 31, 1 } } },
- { { { 17, 0, 1 }, { 10, 31, 0 } } },
- { { { 17, 0, 0 }, { 12, 27, 0 } } },
- { { { 17, 0, 1 }, { 11, 30, 1 } } },
- { { { 17, 0, 2 }, { 11, 30, 0 } } },
- { { { 17, 0, 3 }, { 12, 28, 0 } } },
- { { { 17, 0, 4 }, { 11, 31, 1 } } },
- { { { 18, 0, 3 }, { 11, 31, 0 } } },
- { { { 18, 0, 2 }, { 11, 31, 1 } } },
- { { { 18, 0, 1 }, { 12, 30, 1 } } },
- { { { 18, 0, 0 }, { 12, 30, 0 } } },
- { { { 18, 0, 1 }, { 14, 27, 1 } } },
- { { { 18, 0, 2 }, { 14, 27, 0 } } },
- { { { 18, 0, 3 }, { 12, 31, 0 } } },
- { { { 18, 0, 4 }, { 13, 30, 1 } } },
- { { { 19, 0, 3 }, { 13, 30, 0 } } },
- { { { 19, 0, 2 }, { 16, 24, 0 } } },
- { { { 19, 0, 1 }, { 13, 31, 1 } } },
- { { { 19, 0, 0 }, { 13, 31, 0 } } },
- { { { 19, 0, 1 }, { 13, 31, 1 } } },
- { { { 19, 0, 2 }, { 14, 30, 1 } } },
- { { { 19, 0, 3 }, { 14, 30, 0 } } },
- { { { 19, 0, 4 }, { 14, 31, 1 } } },
- { { { 20, 0, 4 }, { 14, 31, 0 } } },
- { { { 20, 0, 3 }, { 16, 27, 0 } } },
- { { { 20, 0, 2 }, { 15, 30, 1 } } },
- { { { 20, 0, 1 }, { 15, 30, 0 } } },
- { { { 20, 0, 0 }, { 16, 28, 0 } } },
- { { { 20, 0, 1 }, { 15, 31, 1 } } },
- { { { 20, 0, 2 }, { 15, 31, 0 } } },
- { { { 20, 0, 3 }, { 15, 31, 1 } } },
- { { { 20, 0, 4 }, { 16, 30, 1 } } },
- { { { 21, 0, 3 }, { 16, 30, 0 } } },
- { { { 21, 0, 2 }, { 18, 27, 1 } } },
- { { { 21, 0, 1 }, { 18, 27, 0 } } },
- { { { 21, 0, 0 }, { 16, 31, 0 } } },
- { { { 21, 0, 1 }, { 17, 30, 1 } } },
- { { { 21, 0, 2 }, { 17, 30, 0 } } },
- { { { 21, 0, 3 }, { 20, 24, 0 } } },
- { { { 21, 0, 4 }, { 17, 31, 1 } } },
- { { { 22, 0, 3 }, { 17, 31, 0 } } },
- { { { 22, 0, 2 }, { 17, 31, 1 } } },
- { { { 22, 0, 1 }, { 18, 30, 1 } } },
- { { { 22, 0, 0 }, { 18, 30, 0 } } },
- { { { 22, 0, 1 }, { 18, 31, 1 } } },
- { { { 22, 0, 2 }, { 18, 31, 0 } } },
- { { { 22, 0, 3 }, { 20, 27, 0 } } },
- { { { 22, 0, 4 }, { 19, 30, 1 } } },
- { { { 23, 0, 3 }, { 19, 30, 0 } } },
- { { { 23, 0, 2 }, { 20, 28, 0 } } },
- { { { 23, 0, 1 }, { 19, 31, 1 } } },
- { { { 23, 0, 0 }, { 19, 31, 0 } } },
- { { { 23, 0, 1 }, { 19, 31, 1 } } },
- { { { 23, 0, 2 }, { 20, 30, 1 } } },
- { { { 23, 0, 3 }, { 20, 30, 0 } } },
- { { { 23, 0, 4 }, { 22, 27, 1 } } },
- { { { 24, 0, 4 }, { 22, 27, 0 } } },
- { { { 24, 0, 3 }, { 20, 31, 0 } } },
- { { { 24, 0, 2 }, { 21, 30, 1 } } },
- { { { 24, 0, 1 }, { 21, 30, 0 } } },
- { { { 24, 0, 0 }, { 24, 24, 0 } } },
- { { { 24, 0, 1 }, { 21, 31, 1 } } },
- { { { 24, 0, 2 }, { 21, 31, 0 } } },
- { { { 24, 0, 3 }, { 21, 31, 1 } } },
- { { { 24, 0, 4 }, { 22, 30, 1 } } },
- { { { 25, 0, 3 }, { 22, 30, 0 } } },
- { { { 25, 0, 2 }, { 22, 31, 1 } } },
- { { { 25, 0, 1 }, { 22, 31, 0 } } },
- { { { 25, 0, 0 }, { 24, 27, 0 } } },
- { { { 25, 0, 1 }, { 23, 30, 1 } } },
- { { { 25, 0, 2 }, { 23, 30, 0 } } },
- { { { 25, 0, 3 }, { 24, 28, 0 } } },
- { { { 25, 0, 4 }, { 23, 31, 1 } } },
- { { { 26, 0, 3 }, { 23, 31, 0 } } },
- { { { 26, 0, 2 }, { 23, 31, 1 } } },
- { { { 26, 0, 1 }, { 24, 30, 1 } } },
- { { { 26, 0, 0 }, { 24, 30, 0 } } },
- { { { 26, 0, 1 }, { 26, 27, 1 } } },
- { { { 26, 0, 2 }, { 26, 27, 0 } } },
- { { { 26, 0, 3 }, { 24, 31, 0 } } },
- { { { 26, 0, 4 }, { 25, 30, 1 } } },
- { { { 27, 0, 3 }, { 25, 30, 0 } } },
- { { { 27, 0, 2 }, { 28, 24, 0 } } },
- { { { 27, 0, 1 }, { 25, 31, 1 } } },
- { { { 27, 0, 0 }, { 25, 31, 0 } } },
- { { { 27, 0, 1 }, { 25, 31, 1 } } },
- { { { 27, 0, 2 }, { 26, 30, 1 } } },
- { { { 27, 0, 3 }, { 26, 30, 0 } } },
- { { { 27, 0, 4 }, { 26, 31, 1 } } },
- { { { 28, 0, 4 }, { 26, 31, 0 } } },
- { { { 28, 0, 3 }, { 28, 27, 0 } } },
- { { { 28, 0, 2 }, { 27, 30, 1 } } },
- { { { 28, 0, 1 }, { 27, 30, 0 } } },
- { { { 28, 0, 0 }, { 28, 28, 0 } } },
- { { { 28, 0, 1 }, { 27, 31, 1 } } },
- { { { 28, 0, 2 }, { 27, 31, 0 } } },
- { { { 28, 0, 3 }, { 27, 31, 1 } } },
- { { { 28, 0, 4 }, { 28, 30, 1 } } },
- { { { 29, 0, 3 }, { 28, 30, 0 } } },
- { { { 29, 0, 2 }, { 30, 27, 1 } } },
- { { { 29, 0, 1 }, { 30, 27, 0 } } },
- { { { 29, 0, 0 }, { 28, 31, 0 } } },
- { { { 29, 0, 1 }, { 29, 30, 1 } } },
- { { { 29, 0, 2 }, { 29, 30, 0 } } },
- { { { 29, 0, 3 }, { 29, 30, 1 } } },
- { { { 29, 0, 4 }, { 29, 31, 1 } } },
- { { { 30, 0, 3 }, { 29, 31, 0 } } },
- { { { 30, 0, 2 }, { 29, 31, 1 } } },
- { { { 30, 0, 1 }, { 30, 30, 1 } } },
- { { { 30, 0, 0 }, { 30, 30, 0 } } },
- { { { 30, 0, 1 }, { 30, 31, 1 } } },
- { { { 30, 0, 2 }, { 30, 31, 0 } } },
- { { { 30, 0, 3 }, { 30, 31, 1 } } },
- { { { 30, 0, 4 }, { 31, 30, 1 } } },
- { { { 31, 0, 3 }, { 31, 30, 0 } } },
- { { { 31, 0, 2 }, { 31, 30, 1 } } },
- { { { 31, 0, 1 }, { 31, 31, 1 } } },
- { { { 31, 0, 0 }, { 31, 31, 0 } } }
-};
-
-static SingleColourLookup const lookup_6_4[] =
-{
- { { { 0, 0, 0 }, { 0, 0, 0 } } },
- { { { 0, 0, 1 }, { 0, 1, 0 } } },
- { { { 0, 0, 2 }, { 0, 2, 0 } } },
- { { { 1, 0, 1 }, { 0, 3, 1 } } },
- { { { 1, 0, 0 }, { 0, 3, 0 } } },
- { { { 1, 0, 1 }, { 0, 4, 0 } } },
- { { { 1, 0, 2 }, { 0, 5, 0 } } },
- { { { 2, 0, 1 }, { 0, 6, 1 } } },
- { { { 2, 0, 0 }, { 0, 6, 0 } } },
- { { { 2, 0, 1 }, { 0, 7, 0 } } },
- { { { 2, 0, 2 }, { 0, 8, 0 } } },
- { { { 3, 0, 1 }, { 0, 9, 1 } } },
- { { { 3, 0, 0 }, { 0, 9, 0 } } },
- { { { 3, 0, 1 }, { 0, 10, 0 } } },
- { { { 3, 0, 2 }, { 0, 11, 0 } } },
- { { { 4, 0, 1 }, { 0, 12, 1 } } },
- { { { 4, 0, 0 }, { 0, 12, 0 } } },
- { { { 4, 0, 1 }, { 0, 13, 0 } } },
- { { { 4, 0, 2 }, { 0, 14, 0 } } },
- { { { 5, 0, 1 }, { 0, 15, 1 } } },
- { { { 5, 0, 0 }, { 0, 15, 0 } } },
- { { { 5, 0, 1 }, { 0, 16, 0 } } },
- { { { 5, 0, 2 }, { 1, 15, 0 } } },
- { { { 6, 0, 1 }, { 0, 17, 0 } } },
- { { { 6, 0, 0 }, { 0, 18, 0 } } },
- { { { 6, 0, 1 }, { 0, 19, 0 } } },
- { { { 6, 0, 2 }, { 3, 14, 0 } } },
- { { { 7, 0, 1 }, { 0, 20, 0 } } },
- { { { 7, 0, 0 }, { 0, 21, 0 } } },
- { { { 7, 0, 1 }, { 0, 22, 0 } } },
- { { { 7, 0, 2 }, { 4, 15, 0 } } },
- { { { 8, 0, 1 }, { 0, 23, 0 } } },
- { { { 8, 0, 0 }, { 0, 24, 0 } } },
- { { { 8, 0, 1 }, { 0, 25, 0 } } },
- { { { 8, 0, 2 }, { 6, 14, 0 } } },
- { { { 9, 0, 1 }, { 0, 26, 0 } } },
- { { { 9, 0, 0 }, { 0, 27, 0 } } },
- { { { 9, 0, 1 }, { 0, 28, 0 } } },
- { { { 9, 0, 2 }, { 7, 15, 0 } } },
- { { { 10, 0, 1 }, { 0, 29, 0 } } },
- { { { 10, 0, 0 }, { 0, 30, 0 } } },
- { { { 10, 0, 1 }, { 0, 31, 0 } } },
- { { { 10, 0, 2 }, { 9, 14, 0 } } },
- { { { 11, 0, 1 }, { 0, 32, 0 } } },
- { { { 11, 0, 0 }, { 0, 33, 0 } } },
- { { { 11, 0, 1 }, { 2, 30, 0 } } },
- { { { 11, 0, 2 }, { 0, 34, 0 } } },
- { { { 12, 0, 1 }, { 0, 35, 0 } } },
- { { { 12, 0, 0 }, { 0, 36, 0 } } },
- { { { 12, 0, 1 }, { 3, 31, 0 } } },
- { { { 12, 0, 2 }, { 0, 37, 0 } } },
- { { { 13, 0, 1 }, { 0, 38, 0 } } },
- { { { 13, 0, 0 }, { 0, 39, 0 } } },
- { { { 13, 0, 1 }, { 5, 30, 0 } } },
- { { { 13, 0, 2 }, { 0, 40, 0 } } },
- { { { 14, 0, 1 }, { 0, 41, 0 } } },
- { { { 14, 0, 0 }, { 0, 42, 0 } } },
- { { { 14, 0, 1 }, { 6, 31, 0 } } },
- { { { 14, 0, 2 }, { 0, 43, 0 } } },
- { { { 15, 0, 1 }, { 0, 44, 0 } } },
- { { { 15, 0, 0 }, { 0, 45, 0 } } },
- { { { 15, 0, 1 }, { 8, 30, 0 } } },
- { { { 15, 0, 2 }, { 0, 46, 0 } } },
- { { { 16, 0, 2 }, { 0, 47, 0 } } },
- { { { 16, 0, 1 }, { 1, 46, 0 } } },
- { { { 16, 0, 0 }, { 0, 48, 0 } } },
- { { { 16, 0, 1 }, { 0, 49, 0 } } },
- { { { 16, 0, 2 }, { 0, 50, 0 } } },
- { { { 17, 0, 1 }, { 2, 47, 0 } } },
- { { { 17, 0, 0 }, { 0, 51, 0 } } },
- { { { 17, 0, 1 }, { 0, 52, 0 } } },
- { { { 17, 0, 2 }, { 0, 53, 0 } } },
- { { { 18, 0, 1 }, { 4, 46, 0 } } },
- { { { 18, 0, 0 }, { 0, 54, 0 } } },
- { { { 18, 0, 1 }, { 0, 55, 0 } } },
- { { { 18, 0, 2 }, { 0, 56, 0 } } },
- { { { 19, 0, 1 }, { 5, 47, 0 } } },
- { { { 19, 0, 0 }, { 0, 57, 0 } } },
- { { { 19, 0, 1 }, { 0, 58, 0 } } },
- { { { 19, 0, 2 }, { 0, 59, 0 } } },
- { { { 20, 0, 1 }, { 7, 46, 0 } } },
- { { { 20, 0, 0 }, { 0, 60, 0 } } },
- { { { 20, 0, 1 }, { 0, 61, 0 } } },
- { { { 20, 0, 2 }, { 0, 62, 0 } } },
- { { { 21, 0, 1 }, { 8, 47, 0 } } },
- { { { 21, 0, 0 }, { 0, 63, 0 } } },
- { { { 21, 0, 1 }, { 1, 62, 0 } } },
- { { { 21, 0, 2 }, { 1, 63, 0 } } },
- { { { 22, 0, 1 }, { 10, 46, 0 } } },
- { { { 22, 0, 0 }, { 2, 62, 0 } } },
- { { { 22, 0, 1 }, { 2, 63, 0 } } },
- { { { 22, 0, 2 }, { 3, 62, 0 } } },
- { { { 23, 0, 1 }, { 11, 47, 0 } } },
- { { { 23, 0, 0 }, { 3, 63, 0 } } },
- { { { 23, 0, 1 }, { 4, 62, 0 } } },
- { { { 23, 0, 2 }, { 4, 63, 0 } } },
- { { { 24, 0, 1 }, { 13, 46, 0 } } },
- { { { 24, 0, 0 }, { 5, 62, 0 } } },
- { { { 24, 0, 1 }, { 5, 63, 0 } } },
- { { { 24, 0, 2 }, { 6, 62, 0 } } },
- { { { 25, 0, 1 }, { 14, 47, 0 } } },
- { { { 25, 0, 0 }, { 6, 63, 0 } } },
- { { { 25, 0, 1 }, { 7, 62, 0 } } },
- { { { 25, 0, 2 }, { 7, 63, 0 } } },
- { { { 26, 0, 1 }, { 16, 45, 0 } } },
- { { { 26, 0, 0 }, { 8, 62, 0 } } },
- { { { 26, 0, 1 }, { 8, 63, 0 } } },
- { { { 26, 0, 2 }, { 9, 62, 0 } } },
- { { { 27, 0, 1 }, { 16, 48, 0 } } },
- { { { 27, 0, 0 }, { 9, 63, 0 } } },
- { { { 27, 0, 1 }, { 10, 62, 0 } } },
- { { { 27, 0, 2 }, { 10, 63, 0 } } },
- { { { 28, 0, 1 }, { 16, 51, 0 } } },
- { { { 28, 0, 0 }, { 11, 62, 0 } } },
- { { { 28, 0, 1 }, { 11, 63, 0 } } },
- { { { 28, 0, 2 }, { 12, 62, 0 } } },
- { { { 29, 0, 1 }, { 16, 54, 0 } } },
- { { { 29, 0, 0 }, { 12, 63, 0 } } },
- { { { 29, 0, 1 }, { 13, 62, 0 } } },
- { { { 29, 0, 2 }, { 13, 63, 0 } } },
- { { { 30, 0, 1 }, { 16, 57, 0 } } },
- { { { 30, 0, 0 }, { 14, 62, 0 } } },
- { { { 30, 0, 1 }, { 14, 63, 0 } } },
- { { { 30, 0, 2 }, { 15, 62, 0 } } },
- { { { 31, 0, 1 }, { 16, 60, 0 } } },
- { { { 31, 0, 0 }, { 15, 63, 0 } } },
- { { { 31, 0, 1 }, { 24, 46, 0 } } },
- { { { 31, 0, 2 }, { 16, 62, 0 } } },
- { { { 32, 0, 2 }, { 16, 63, 0 } } },
- { { { 32, 0, 1 }, { 17, 62, 0 } } },
- { { { 32, 0, 0 }, { 25, 47, 0 } } },
- { { { 32, 0, 1 }, { 17, 63, 0 } } },
- { { { 32, 0, 2 }, { 18, 62, 0 } } },
- { { { 33, 0, 1 }, { 18, 63, 0 } } },
- { { { 33, 0, 0 }, { 27, 46, 0 } } },
- { { { 33, 0, 1 }, { 19, 62, 0 } } },
- { { { 33, 0, 2 }, { 19, 63, 0 } } },
- { { { 34, 0, 1 }, { 20, 62, 0 } } },
- { { { 34, 0, 0 }, { 28, 47, 0 } } },
- { { { 34, 0, 1 }, { 20, 63, 0 } } },
- { { { 34, 0, 2 }, { 21, 62, 0 } } },
- { { { 35, 0, 1 }, { 21, 63, 0 } } },
- { { { 35, 0, 0 }, { 30, 46, 0 } } },
- { { { 35, 0, 1 }, { 22, 62, 0 } } },
- { { { 35, 0, 2 }, { 22, 63, 0 } } },
- { { { 36, 0, 1 }, { 23, 62, 0 } } },
- { { { 36, 0, 0 }, { 31, 47, 0 } } },
- { { { 36, 0, 1 }, { 23, 63, 0 } } },
- { { { 36, 0, 2 }, { 24, 62, 0 } } },
- { { { 37, 0, 1 }, { 24, 63, 0 } } },
- { { { 37, 0, 0 }, { 32, 47, 0 } } },
- { { { 37, 0, 1 }, { 25, 62, 0 } } },
- { { { 37, 0, 2 }, { 25, 63, 0 } } },
- { { { 38, 0, 1 }, { 26, 62, 0 } } },
- { { { 38, 0, 0 }, { 32, 50, 0 } } },
- { { { 38, 0, 1 }, { 26, 63, 0 } } },
- { { { 38, 0, 2 }, { 27, 62, 0 } } },
- { { { 39, 0, 1 }, { 27, 63, 0 } } },
- { { { 39, 0, 0 }, { 32, 53, 0 } } },
- { { { 39, 0, 1 }, { 28, 62, 0 } } },
- { { { 39, 0, 2 }, { 28, 63, 0 } } },
- { { { 40, 0, 1 }, { 29, 62, 0 } } },
- { { { 40, 0, 0 }, { 32, 56, 0 } } },
- { { { 40, 0, 1 }, { 29, 63, 0 } } },
- { { { 40, 0, 2 }, { 30, 62, 0 } } },
- { { { 41, 0, 1 }, { 30, 63, 0 } } },
- { { { 41, 0, 0 }, { 32, 59, 0 } } },
- { { { 41, 0, 1 }, { 31, 62, 0 } } },
- { { { 41, 0, 2 }, { 31, 63, 0 } } },
- { { { 42, 0, 1 }, { 32, 61, 0 } } },
- { { { 42, 0, 0 }, { 32, 62, 0 } } },
- { { { 42, 0, 1 }, { 32, 63, 0 } } },
- { { { 42, 0, 2 }, { 41, 46, 0 } } },
- { { { 43, 0, 1 }, { 33, 62, 0 } } },
- { { { 43, 0, 0 }, { 33, 63, 0 } } },
- { { { 43, 0, 1 }, { 34, 62, 0 } } },
- { { { 43, 0, 2 }, { 42, 47, 0 } } },
- { { { 44, 0, 1 }, { 34, 63, 0 } } },
- { { { 44, 0, 0 }, { 35, 62, 0 } } },
- { { { 44, 0, 1 }, { 35, 63, 0 } } },
- { { { 44, 0, 2 }, { 44, 46, 0 } } },
- { { { 45, 0, 1 }, { 36, 62, 0 } } },
- { { { 45, 0, 0 }, { 36, 63, 0 } } },
- { { { 45, 0, 1 }, { 37, 62, 0 } } },
- { { { 45, 0, 2 }, { 45, 47, 0 } } },
- { { { 46, 0, 1 }, { 37, 63, 0 } } },
- { { { 46, 0, 0 }, { 38, 62, 0 } } },
- { { { 46, 0, 1 }, { 38, 63, 0 } } },
- { { { 46, 0, 2 }, { 47, 46, 0 } } },
- { { { 47, 0, 1 }, { 39, 62, 0 } } },
- { { { 47, 0, 0 }, { 39, 63, 0 } } },
- { { { 47, 0, 1 }, { 40, 62, 0 } } },
- { { { 47, 0, 2 }, { 48, 46, 0 } } },
- { { { 48, 0, 2 }, { 40, 63, 0 } } },
- { { { 48, 0, 1 }, { 41, 62, 0 } } },
- { { { 48, 0, 0 }, { 41, 63, 0 } } },
- { { { 48, 0, 1 }, { 48, 49, 0 } } },
- { { { 48, 0, 2 }, { 42, 62, 0 } } },
- { { { 49, 0, 1 }, { 42, 63, 0 } } },
- { { { 49, 0, 0 }, { 43, 62, 0 } } },
- { { { 49, 0, 1 }, { 48, 52, 0 } } },
- { { { 49, 0, 2 }, { 43, 63, 0 } } },
- { { { 50, 0, 1 }, { 44, 62, 0 } } },
- { { { 50, 0, 0 }, { 44, 63, 0 } } },
- { { { 50, 0, 1 }, { 48, 55, 0 } } },
- { { { 50, 0, 2 }, { 45, 62, 0 } } },
- { { { 51, 0, 1 }, { 45, 63, 0 } } },
- { { { 51, 0, 0 }, { 46, 62, 0 } } },
- { { { 51, 0, 1 }, { 48, 58, 0 } } },
- { { { 51, 0, 2 }, { 46, 63, 0 } } },
- { { { 52, 0, 1 }, { 47, 62, 0 } } },
- { { { 52, 0, 0 }, { 47, 63, 0 } } },
- { { { 52, 0, 1 }, { 48, 61, 0 } } },
- { { { 52, 0, 2 }, { 48, 62, 0 } } },
- { { { 53, 0, 1 }, { 56, 47, 0 } } },
- { { { 53, 0, 0 }, { 48, 63, 0 } } },
- { { { 53, 0, 1 }, { 49, 62, 0 } } },
- { { { 53, 0, 2 }, { 49, 63, 0 } } },
- { { { 54, 0, 1 }, { 58, 46, 0 } } },
- { { { 54, 0, 0 }, { 50, 62, 0 } } },
- { { { 54, 0, 1 }, { 50, 63, 0 } } },
- { { { 54, 0, 2 }, { 51, 62, 0 } } },
- { { { 55, 0, 1 }, { 59, 47, 0 } } },
- { { { 55, 0, 0 }, { 51, 63, 0 } } },
- { { { 55, 0, 1 }, { 52, 62, 0 } } },
- { { { 55, 0, 2 }, { 52, 63, 0 } } },
- { { { 56, 0, 1 }, { 61, 46, 0 } } },
- { { { 56, 0, 0 }, { 53, 62, 0 } } },
- { { { 56, 0, 1 }, { 53, 63, 0 } } },
- { { { 56, 0, 2 }, { 54, 62, 0 } } },
- { { { 57, 0, 1 }, { 62, 47, 0 } } },
- { { { 57, 0, 0 }, { 54, 63, 0 } } },
- { { { 57, 0, 1 }, { 55, 62, 0 } } },
- { { { 57, 0, 2 }, { 55, 63, 0 } } },
- { { { 58, 0, 1 }, { 56, 62, 1 } } },
- { { { 58, 0, 0 }, { 56, 62, 0 } } },
- { { { 58, 0, 1 }, { 56, 63, 0 } } },
- { { { 58, 0, 2 }, { 57, 62, 0 } } },
- { { { 59, 0, 1 }, { 57, 63, 1 } } },
- { { { 59, 0, 0 }, { 57, 63, 0 } } },
- { { { 59, 0, 1 }, { 58, 62, 0 } } },
- { { { 59, 0, 2 }, { 58, 63, 0 } } },
- { { { 60, 0, 1 }, { 59, 62, 1 } } },
- { { { 60, 0, 0 }, { 59, 62, 0 } } },
- { { { 60, 0, 1 }, { 59, 63, 0 } } },
- { { { 60, 0, 2 }, { 60, 62, 0 } } },
- { { { 61, 0, 1 }, { 60, 63, 1 } } },
- { { { 61, 0, 0 }, { 60, 63, 0 } } },
- { { { 61, 0, 1 }, { 61, 62, 0 } } },
- { { { 61, 0, 2 }, { 61, 63, 0 } } },
- { { { 62, 0, 1 }, { 62, 62, 1 } } },
- { { { 62, 0, 0 }, { 62, 62, 0 } } },
- { { { 62, 0, 1 }, { 62, 63, 0 } } },
- { { { 62, 0, 2 }, { 63, 62, 0 } } },
- { { { 63, 0, 1 }, { 63, 63, 1 } } },
- { { { 63, 0, 0 }, { 63, 63, 0 } } }
-};
diff --git a/thirdparty/squish/squish.cpp b/thirdparty/squish/squish.cpp
deleted file mode 100644
index 1de1da3e52..0000000000
--- a/thirdparty/squish/squish.cpp
+++ /dev/null
@@ -1,411 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#include <string.h>
-#include "squish.h"
-#include "colourset.h"
-#include "maths.h"
-#include "rangefit.h"
-#include "clusterfit.h"
-#include "colourblock.h"
-#include "alpha.h"
-#include "singlecolourfit.h"
-
-namespace squish {
-
-static int FixFlags( int flags )
-{
- // grab the flag bits
- int method = flags & ( kDxt1 | kDxt3 | kDxt5 | kBc4 | kBc5 );
- int fit = flags & ( kColourIterativeClusterFit | kColourClusterFit | kColourRangeFit );
- int extra = flags & kWeightColourByAlpha;
-
- // set defaults
- if ( method != kDxt3
- && method != kDxt5
- && method != kBc4
- && method != kBc5 )
- {
- method = kDxt1;
- }
- if( fit != kColourRangeFit && fit != kColourIterativeClusterFit )
- fit = kColourClusterFit;
-
- // done
- return method | fit | extra;
-}
-
-void CompressMasked( u8 const* rgba, int mask, void* block, int flags, float* metric )
-{
- // fix any bad flags
- flags = FixFlags( flags );
-
- if ( ( flags & ( kBc4 | kBc5 ) ) != 0 )
- {
- u8 alpha[16*4];
- for( int i = 0; i < 16; ++i )
- {
- alpha[i*4 + 3] = rgba[i*4 + 0]; // copy R to A
- }
-
- u8* rBlock = reinterpret_cast< u8* >( block );
- CompressAlphaDxt5( alpha, mask, rBlock );
-
- if ( ( flags & ( kBc5 ) ) != 0 )
- {
- for( int i = 0; i < 16; ++i )
- {
- alpha[i*4 + 3] = rgba[i*4 + 1]; // copy G to A
- }
-
- u8* gBlock = reinterpret_cast< u8* >( block ) + 8;
- CompressAlphaDxt5( alpha, mask, gBlock );
- }
-
- return;
- }
-
- // get the block locations
- void* colourBlock = block;
- void* alphaBlock = block;
- if( ( flags & ( kDxt3 | kDxt5 ) ) != 0 )
- colourBlock = reinterpret_cast< u8* >( block ) + 8;
-
- // create the minimal point set
- ColourSet colours( rgba, mask, flags );
-
- // check the compression type and compress colour
- if( colours.GetCount() == 1 )
- {
- // always do a single colour fit
- SingleColourFit fit( &colours, flags );
- fit.Compress( colourBlock );
- }
- else if( ( flags & kColourRangeFit ) != 0 || colours.GetCount() == 0 )
- {
- // do a range fit
- RangeFit fit( &colours, flags, metric );
- fit.Compress( colourBlock );
- }
- else
- {
- // default to a cluster fit (could be iterative or not)
- ClusterFit fit( &colours, flags, metric );
- fit.Compress( colourBlock );
- }
-
- // compress alpha separately if necessary
- if( ( flags & kDxt3 ) != 0 )
- CompressAlphaDxt3( rgba, mask, alphaBlock );
- else if( ( flags & kDxt5 ) != 0 )
- CompressAlphaDxt5( rgba, mask, alphaBlock );
-}
-
-void Decompress( u8* rgba, void const* block, int flags )
-{
- // fix any bad flags
- flags = FixFlags( flags );
-
- // get the block locations
- void const* colourBlock = block;
- void const* alphaBlock = block;
- if( ( flags & ( kDxt3 | kDxt5 ) ) != 0 )
- colourBlock = reinterpret_cast< u8 const* >( block ) + 8;
-
- // decompress colour
- // -- GODOT start --
- //DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 );
- if(( flags & ( kBc4 ) ) != 0)
- DecompressColourBc4( rgba, colourBlock);
- else if(( flags & ( kBc5 ) ) != 0)
- DecompressColourBc5( rgba, colourBlock);
- else
- DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 );
- // -- GODOT end --
-
- // decompress alpha separately if necessary
- if( ( flags & kDxt3 ) != 0 )
- DecompressAlphaDxt3( rgba, alphaBlock );
- else if( ( flags & kDxt5 ) != 0 )
- DecompressAlphaDxt5( rgba, alphaBlock );
-}
-
-int GetStorageRequirements( int width, int height, int flags )
-{
- // fix any bad flags
- flags = FixFlags( flags );
-
- // compute the storage requirements
- int blockcount = ( ( width + 3 )/4 ) * ( ( height + 3 )/4 );
- int blocksize = ( ( flags & ( kDxt1 | kBc4 ) ) != 0 ) ? 8 : 16;
- return blockcount*blocksize;
-}
-
-void CopyRGBA( u8 const* source, u8* dest, int flags )
-{
- if (flags & kSourceBGRA)
- {
- // convert from bgra to rgba
- dest[0] = source[2];
- dest[1] = source[1];
- dest[2] = source[0];
- dest[3] = source[3];
- }
- else
- {
- for( int i = 0; i < 4; ++i )
- *dest++ = *source++;
- }
-}
-
-void CompressImage( u8 const* rgba, int width, int height, int pitch, void* blocks, int flags, float* metric )
-{
- // fix any bad flags
- flags = FixFlags( flags );
-
- // loop over blocks
-#ifdef SQUISH_USE_OPENMP
-# pragma omp parallel for
-#endif
- for( int y = 0; y < height; y += 4 )
- {
- // initialise the block output
- u8* targetBlock = reinterpret_cast< u8* >( blocks );
- int bytesPerBlock = ( ( flags & ( kDxt1 | kBc4 ) ) != 0 ) ? 8 : 16;
- targetBlock += ( (y / 4) * ( (width + 3) / 4) ) * bytesPerBlock;
-
- for( int x = 0; x < width; x += 4 )
- {
- // build the 4x4 block of pixels
- u8 sourceRgba[16*4];
- u8* targetPixel = sourceRgba;
- int mask = 0;
- for( int py = 0; py < 4; ++py )
- {
- for( int px = 0; px < 4; ++px )
- {
- // get the source pixel in the image
- int sx = x + px;
- int sy = y + py;
-
- // enable if we're in the image
- if( sx < width && sy < height )
- {
- // copy the rgba value
- u8 const* sourcePixel = rgba + pitch*sy + 4*sx;
- CopyRGBA(sourcePixel, targetPixel, flags);
- // enable this pixel
- mask |= ( 1 << ( 4*py + px ) );
- }
-
- // advance to the next pixel
- targetPixel += 4;
- }
- }
-
- // compress it into the output
- CompressMasked( sourceRgba, mask, targetBlock, flags, metric );
-
- // advance
- targetBlock += bytesPerBlock;
- }
- }
-}
-
-void CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, float* metric )
-{
- CompressImage(rgba, width, height, width*4, blocks, flags, metric);
-}
-
-void DecompressImage( u8* rgba, int width, int height, int pitch, void const* blocks, int flags )
-{
- // fix any bad flags
- flags = FixFlags( flags );
-
- // loop over blocks
-#ifdef SQUISH_USE_OPENMP
-# pragma omp parallel for
-#endif
- for( int y = 0; y < height; y += 4 )
- {
- // initialise the block input
- u8 const* sourceBlock = reinterpret_cast< u8 const* >( blocks );
- int bytesPerBlock = ( ( flags & ( kDxt1 | kBc4 ) ) != 0 ) ? 8 : 16;
- sourceBlock += ( (y / 4) * ( (width + 3) / 4) ) * bytesPerBlock;
-
- for( int x = 0; x < width; x += 4 )
- {
- // decompress the block
- u8 targetRgba[4*16];
- Decompress( targetRgba, sourceBlock, flags );
-
- // write the decompressed pixels to the correct image locations
- u8 const* sourcePixel = targetRgba;
- for( int py = 0; py < 4; ++py )
- {
- for( int px = 0; px < 4; ++px )
- {
- // get the target location
- int sx = x + px;
- int sy = y + py;
-
- // write if we're in the image
- if( sx < width && sy < height )
- {
- // copy the rgba value
- u8* targetPixel = rgba + pitch*sy + 4*sx;
- CopyRGBA(sourcePixel, targetPixel, flags);
- }
-
- // advance to the next pixel
- sourcePixel += 4;
- }
- }
-
- // advance
- sourceBlock += bytesPerBlock;
- }
- }
-}
-
-void DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags )
-{
- DecompressImage( rgba, width, height, width*4, blocks, flags );
-}
-
-static double ErrorSq(double x, double y)
-{
- return (x - y) * (x - y);
-}
-
-static void ComputeBlockWMSE(u8 const *original, u8 const *compressed, unsigned int w, unsigned int h, double &cmse, double &amse)
-{
- // Computes the MSE for the block and weights it by the variance of the original block.
- // If the variance of the original block is less than 4 (i.e. a standard deviation of 1 per channel)
- // then the block is close to being a single colour. Quantisation errors in single colour blocks
- // are easier to see than similar errors in blocks that contain more colours, particularly when there
- // are many such blocks in a large area (eg a blue sky background) as they cause banding. Given that
- // banding is easier to see than small errors in "complex" blocks, we weight the errors by a factor
- // of 5. This implies that images with large, single colour areas will have a higher potential WMSE
- // than images with lots of detail.
-
- cmse = amse = 0;
- unsigned int sum_p[4]; // per channel sum of pixels
- unsigned int sum_p2[4]; // per channel sum of pixels squared
- memset(sum_p, 0, sizeof(sum_p));
- memset(sum_p2, 0, sizeof(sum_p2));
- for( unsigned int py = 0; py < 4; ++py )
- {
- for( unsigned int px = 0; px < 4; ++px )
- {
- if( px < w && py < h )
- {
- double pixelCMSE = 0;
- for( int i = 0; i < 3; ++i )
- {
- pixelCMSE += ErrorSq(original[i], compressed[i]);
- sum_p[i] += original[i];
- sum_p2[i] += (unsigned int)original[i]*original[i];
- }
- if( original[3] == 0 && compressed[3] == 0 )
- pixelCMSE = 0; // transparent in both, so colour is inconsequential
- amse += ErrorSq(original[3], compressed[3]);
- cmse += pixelCMSE;
- sum_p[3] += original[3];
- sum_p2[3] += (unsigned int)original[3]*original[3];
- }
- original += 4;
- compressed += 4;
- }
- }
- unsigned int variance = 0;
- for( int i = 0; i < 4; ++i )
- variance += w*h*sum_p2[i] - sum_p[i]*sum_p[i];
- if( variance < 4 * w * w * h * h )
- {
- amse *= 5;
- cmse *= 5;
- }
-}
-
-void ComputeMSE( u8 const *rgba, int width, int height, int pitch, u8 const *dxt, int flags, double &colourMSE, double &alphaMSE )
-{
- // fix any bad flags
- flags = FixFlags( flags );
- colourMSE = alphaMSE = 0;
-
- // initialise the block input
- squish::u8 const* sourceBlock = dxt;
- int bytesPerBlock = ( ( flags & squish::kDxt1 ) != 0 ) ? 8 : 16;
-
- // loop over blocks
- for( int y = 0; y < height; y += 4 )
- {
- for( int x = 0; x < width; x += 4 )
- {
- // decompress the block
- u8 targetRgba[4*16];
- Decompress( targetRgba, sourceBlock, flags );
- u8 const* sourcePixel = targetRgba;
-
- // copy across to a similar pixel block
- u8 originalRgba[4*16];
- u8* originalPixel = originalRgba;
-
- for( int py = 0; py < 4; ++py )
- {
- for( int px = 0; px < 4; ++px )
- {
- int sx = x + px;
- int sy = y + py;
- if( sx < width && sy < height )
- {
- u8 const* targetPixel = rgba + pitch*sy + 4*sx;
- CopyRGBA(targetPixel, originalPixel, flags);
- }
- sourcePixel += 4;
- originalPixel += 4;
- }
- }
-
- // compute the weighted MSE of the block
- double blockCMSE, blockAMSE;
- ComputeBlockWMSE(originalRgba, targetRgba, std::min(4, width - x), std::min(4, height - y), blockCMSE, blockAMSE);
- colourMSE += blockCMSE;
- alphaMSE += blockAMSE;
- // advance
- sourceBlock += bytesPerBlock;
- }
- }
- colourMSE /= (width * height * 3);
- alphaMSE /= (width * height);
-}
-
-void ComputeMSE( u8 const *rgba, int width, int height, u8 const *dxt, int flags, double &colourMSE, double &alphaMSE )
-{
- ComputeMSE(rgba, width, height, width*4, dxt, flags, colourMSE, alphaMSE);
-}
-
-} // namespace squish
diff --git a/thirdparty/squish/squish.h b/thirdparty/squish/squish.h
deleted file mode 100644
index 14c9bb59fb..0000000000
--- a/thirdparty/squish/squish.h
+++ /dev/null
@@ -1,309 +0,0 @@
-/* -----------------------------------------------------------------------------
-
- Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
- -------------------------------------------------------------------------- */
-
-#ifndef SQUISH_H
-#define SQUISH_H
-
-//! All squish API functions live in this namespace.
-namespace squish {
-
-// -----------------------------------------------------------------------------
-
-//! Typedef a quantity that is a single unsigned byte.
-typedef unsigned char u8;
-
-// -----------------------------------------------------------------------------
-
-enum
-{
- //! Use DXT1 compression.
- kDxt1 = ( 1 << 0 ),
-
- //! Use DXT3 compression.
- kDxt3 = ( 1 << 1 ),
-
- //! Use DXT5 compression.
- kDxt5 = ( 1 << 2 ),
-
- //! Use BC4 compression.
- kBc4 = ( 1 << 3 ),
-
- //! Use BC5 compression.
- kBc5 = ( 1 << 4 ),
-
- //! Use a slow but high quality colour compressor (the default).
- kColourClusterFit = ( 1 << 5 ),
-
- //! Use a fast but low quality colour compressor.
- kColourRangeFit = ( 1 << 6 ),
-
- //! Weight the colour by alpha during cluster fit (disabled by default).
- kWeightColourByAlpha = ( 1 << 7 ),
-
- //! Use a very slow but very high quality colour compressor.
- kColourIterativeClusterFit = ( 1 << 8 ),
-
- //! Source is BGRA rather than RGBA
- kSourceBGRA = ( 1 << 9 )
-};
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Compresses a 4x4 block of pixels.
-
- @param rgba The rgba values of the 16 source pixels.
- @param mask The valid pixel mask.
- @param block Storage for the compressed DXT block.
- @param flags Compression flags.
- @param metric An optional perceptual metric.
-
- The source pixels should be presented as a contiguous array of 16 rgba
- values, with each component as 1 byte each. In memory this should be:
-
- { r1, g1, b1, a1, .... , r16, g16, b16, a16 }
-
- The mask parameter enables only certain pixels within the block. The lowest
- bit enables the first pixel and so on up to the 16th bit. Bits beyond the
- 16th bit are ignored. Pixels that are not enabled are allowed to take
- arbitrary colours in the output block. An example of how this can be used
- is in the CompressImage function to disable pixels outside the bounds of
- the image when the width or height is not divisible by 4.
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. When using DXT1
- compression, 8 bytes of storage are required for the compressed DXT block.
- DXT3 and DXT5 compression require 16 bytes of storage per block.
-
- The flags parameter can also specify a preferred colour compressor to use
- when fitting the RGB components of the data. Possible colour compressors
- are: kColourClusterFit (the default), kColourRangeFit (very fast, low
- quality) or kColourIterativeClusterFit (slowest, best quality).
-
- When using kColourClusterFit or kColourIterativeClusterFit, an additional
- flag can be specified to weight the importance of each pixel by its alpha
- value. For images that are rendered using alpha blending, this can
- significantly increase the perceived quality.
-
- The metric parameter can be used to weight the relative importance of each
- colour channel, or pass NULL to use the default uniform weight of
- { 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that
- allowed either uniform or "perceptual" weights with the fixed values
- { 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a
- contiguous array of 3 floats.
-*/
-void CompressMasked( u8 const* rgba, int mask, void* block, int flags, float* metric = 0 );
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Compresses a 4x4 block of pixels.
-
- @param rgba The rgba values of the 16 source pixels.
- @param block Storage for the compressed DXT block.
- @param flags Compression flags.
- @param metric An optional perceptual metric.
-
- The source pixels should be presented as a contiguous array of 16 rgba
- values, with each component as 1 byte each. In memory this should be:
-
- { r1, g1, b1, a1, .... , r16, g16, b16, a16 }
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. When using DXT1
- compression, 8 bytes of storage are required for the compressed DXT block.
- DXT3 and DXT5 compression require 16 bytes of storage per block.
-
- The flags parameter can also specify a preferred colour compressor to use
- when fitting the RGB components of the data. Possible colour compressors
- are: kColourClusterFit (the default), kColourRangeFit (very fast, low
- quality) or kColourIterativeClusterFit (slowest, best quality).
-
- When using kColourClusterFit or kColourIterativeClusterFit, an additional
- flag can be specified to weight the importance of each pixel by its alpha
- value. For images that are rendered using alpha blending, this can
- significantly increase the perceived quality.
-
- The metric parameter can be used to weight the relative importance of each
- colour channel, or pass NULL to use the default uniform weight of
- { 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that
- allowed either uniform or "perceptual" weights with the fixed values
- { 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a
- contiguous array of 3 floats.
-
- This method is an inline that calls CompressMasked with a mask of 0xffff,
- provided for compatibility with older versions of squish.
-*/
-inline void Compress( u8 const* rgba, void* block, int flags, float* metric = 0 )
-{
- CompressMasked( rgba, 0xffff, block, flags, metric );
-}
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Decompresses a 4x4 block of pixels.
-
- @param rgba Storage for the 16 decompressed pixels.
- @param block The compressed DXT block.
- @param flags Compression flags.
-
- The decompressed pixels will be written as a contiguous array of 16 rgba
- values, with each component as 1 byte each. In memory this is:
-
- { r1, g1, b1, a1, .... , r16, g16, b16, a16 }
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. All other flags
- are ignored.
-*/
-void Decompress( u8* rgba, void const* block, int flags );
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Computes the amount of compressed storage required.
-
- @param width The width of the image.
- @param height The height of the image.
- @param flags Compression flags.
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. All other flags
- are ignored.
-
- Most DXT images will be a multiple of 4 in each dimension, but this
- function supports arbitrary size images by allowing the outer blocks to
- be only partially used.
-*/
-int GetStorageRequirements( int width, int height, int flags );
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Compresses an image in memory.
-
- @param rgba The pixels of the source.
- @param width The width of the source image.
- @param height The height of the source image.
- @param pitch The pitch of the source image.
- @param blocks Storage for the compressed output.
- @param flags Compression flags.
- @param metric An optional perceptual metric.
-
- The source pixels should be presented as a contiguous array of width*height
- rgba values, with each component as 1 byte each. In memory this should be:
-
- { r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. When using DXT1
- compression, 8 bytes of storage are required for each compressed DXT block.
- DXT3 and DXT5 compression require 16 bytes of storage per block.
-
- The flags parameter can also specify a preferred colour compressor to use
- when fitting the RGB components of the data. Possible colour compressors
- are: kColourClusterFit (the default), kColourRangeFit (very fast, low
- quality) or kColourIterativeClusterFit (slowest, best quality).
-
- When using kColourClusterFit or kColourIterativeClusterFit, an additional
- flag can be specified to weight the importance of each pixel by its alpha
- value. For images that are rendered using alpha blending, this can
- significantly increase the perceived quality.
-
- The metric parameter can be used to weight the relative importance of each
- colour channel, or pass NULL to use the default uniform weight of
- { 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that
- allowed either uniform or "perceptual" weights with the fixed values
- { 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a
- contiguous array of 3 floats.
-
- Internally this function calls squish::CompressMasked for each block, which
- allows for pixels outside the image to take arbitrary values. The function
- squish::GetStorageRequirements can be called to compute the amount of memory
- to allocate for the compressed output.
-
- Note on compression quality: When compressing textures with
- libsquish it is recommended to apply a gamma-correction
- beforehand. This will reduce the blockiness in dark areas. The
- level of necessary gamma-correction is platform dependent. For
- example, a gamma correction with gamma = 0.5 before compression
- and gamma = 2.0 after decompression yields good results on the
- Windows platform but for other platforms like MacOS X a different
- gamma value may be more suitable.
-*/
-void CompressImage( u8 const* rgba, int width, int height, int pitch, void* blocks, int flags, float* metric = 0 );
-void CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, float* metric = 0 );
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Decompresses an image in memory.
-
- @param rgba Storage for the decompressed pixels.
- @param width The width of the source image.
- @param height The height of the source image.
- @param pitch The pitch of the decompressed pixels.
- @param blocks The compressed DXT blocks.
- @param flags Compression flags.
-
- The decompressed pixels will be written as a contiguous array of width*height
- 16 rgba values, with each component as 1 byte each. In memory this is:
-
- { r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. All other flags
- are ignored.
-
- Internally this function calls squish::Decompress for each block.
-*/
-void DecompressImage( u8* rgba, int width, int height, int pitch, void const* blocks, int flags );
-void DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags );
-
-// -----------------------------------------------------------------------------
-
-/*! @brief Computes MSE of an compressed image in memory.
-
- @param rgba The original image pixels.
- @param width The width of the source image.
- @param height The height of the source image.
- @param pitch The pitch of the source image.
- @param dxt The compressed dxt blocks
- @param flags Compression flags.
- @param colourMSE The MSE of the colour values.
- @param alphaMSE The MSE of the alpha values.
-
- The colour MSE and alpha MSE are computed across all pixels. The colour MSE is
- averaged across all rgb values (i.e. colourMSE = sum sum_k ||dxt.k - rgba.k||/3)
-
- The flags parameter should specify kDxt1, kDxt3, kDxt5, kBc4, or kBc5 compression,
- however, DXT1 will be used by default if none is specified. All other flags
- are ignored.
-
- Internally this function calls squish::Decompress for each block.
-*/
-void ComputeMSE(u8 const *rgba, int width, int height, int pitch, u8 const *dxt, int flags, double &colourMSE, double &alphaMSE);
-void ComputeMSE(u8 const *rgba, int width, int height, u8 const *dxt, int flags, double &colourMSE, double &alphaMSE);
-
-// -----------------------------------------------------------------------------
-
-} // namespace squish
-
-#endif // ndef SQUISH_H
diff --git a/thirdparty/vulkan/patches/VKEnumStringHelper-use-volk.patch b/thirdparty/vulkan/patches/VKEnumStringHelper-use-godot-vulkan.patch
index 8517b277d0..6b56d60181 100644
--- a/thirdparty/vulkan/patches/VKEnumStringHelper-use-volk.patch
+++ b/thirdparty/vulkan/patches/VKEnumStringHelper-use-godot-vulkan.patch
@@ -1,17 +1,13 @@
diff --git a/thirdparty/vulkan/vk_enum_string_helper.h b/thirdparty/vulkan/vk_enum_string_helper.h
-index 9d2af46344..d61dbb1290 100644
+index 8026787ad4..7a54b12a38 100644
--- a/thirdparty/vulkan/vk_enum_string_helper.h
+++ b/thirdparty/vulkan/vk_enum_string_helper.h
-@@ -13,7 +13,11 @@
+@@ -13,7 +13,7 @@
#ifdef __cplusplus
#include <string>
#endif
-#include <vulkan/vulkan.h>
-+#ifdef USE_VOLK
-+ #include <volk.h>
-+#else
-+ #include <vulkan/vulkan.h>
-+#endif
++#include "drivers/vulkan/godot_vulkan.h"
static inline const char* string_VkResult(VkResult input_value) {
switch (input_value) {
case VK_SUCCESS:
diff --git a/thirdparty/vulkan/patches/VMA-use-volk.patch b/thirdparty/vulkan/patches/VMA-use-godot-vulkan.patch
index e2e5ea5ad4..a6c546e3d8 100644
--- a/thirdparty/vulkan/patches/VMA-use-volk.patch
+++ b/thirdparty/vulkan/patches/VMA-use-godot-vulkan.patch
@@ -1,17 +1,18 @@
diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
-index 711f486571..e5eaa80e74 100644
+index 2307325d4e..ecb84094b9 100644
--- a/thirdparty/vulkan/vk_mem_alloc.h
+++ b/thirdparty/vulkan/vk_mem_alloc.h
-@@ -127,7 +127,11 @@ See documentation chapter: \ref statistics.
+@@ -122,12 +122,12 @@ for user-defined purpose without allocating any real GPU memory.
+ See documentation chapter: \ref statistics.
+ */
+
++#include "drivers/vulkan/godot_vulkan.h"
+
+ #ifdef __cplusplus
extern "C" {
#endif
-#include <vulkan/vulkan.h>
-+#ifdef USE_VOLK
-+ #include <volk.h>
-+#else
-+ #include <vulkan/vulkan.h>
-+#endif
#if !defined(VMA_VULKAN_VERSION)
#if defined(VK_VERSION_1_3)
diff --git a/thirdparty/vulkan/vk_enum_string_helper.h b/thirdparty/vulkan/vk_enum_string_helper.h
index 598453e745..7a54b12a38 100644
--- a/thirdparty/vulkan/vk_enum_string_helper.h
+++ b/thirdparty/vulkan/vk_enum_string_helper.h
@@ -13,11 +13,7 @@
#ifdef __cplusplus
#include <string>
#endif
-#ifdef USE_VOLK
- #include <volk.h>
-#else
- #include <vulkan/vulkan.h>
-#endif
+#include "drivers/vulkan/godot_vulkan.h"
static inline const char* string_VkResult(VkResult input_value) {
switch (input_value) {
case VK_SUCCESS:
diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
index b39b73b17d..ecb84094b9 100644
--- a/thirdparty/vulkan/vk_mem_alloc.h
+++ b/thirdparty/vulkan/vk_mem_alloc.h
@@ -122,16 +122,12 @@ for user-defined purpose without allocating any real GPU memory.
See documentation chapter: \ref statistics.
*/
+#include "drivers/vulkan/godot_vulkan.h"
#ifdef __cplusplus
extern "C" {
#endif
-#ifdef USE_VOLK
- #include <volk.h>
-#else
- #include <vulkan/vulkan.h>
-#endif
#if !defined(VMA_VULKAN_VERSION)
#if defined(VK_VERSION_1_3)