├── test
├── worker_import.js
├── worker.html
├── client
│ ├── worker_loader.js
│ └── assets
│ │ └── webgpu_worker.js
├── atomic_array_buffer.html
├── shaderError.html
├── async_render_pipeline.html
├── pointer_lock.html
├── canvas_texture_error.html
├── compute_storage_texture.html
├── triangle.html
├── slow_triangle.html
├── msaa.html
├── render_bundle.html
├── widget
│ └── tree_widget.html
└── timestamp_query.html
├── .gitignore
├── docs
├── images
│ ├── capture.png
│ ├── inspect.png
│ ├── record.png
│ ├── capture_button.png
│ ├── frame_capture.png
│ ├── inspect_shader.png
│ ├── inspect_stats.png
│ ├── inspect_status.png
│ ├── inspector_info.png
│ ├── buffer_format_hex.png
│ ├── enable_extension.png
│ ├── inspect_objects.png
│ ├── inspect_texture.png
│ ├── inspector_start.png
│ ├── shader_debugger.png
│ ├── webgpu_inspector.png
│ ├── buffer_format_orig.png
│ ├── buffer_format_vec4f.png
│ ├── capture_frame_stats.png
│ ├── capture_stacktrace.png
│ ├── inspect_stacktrace.png
│ ├── buffer_data_inspection.png
│ ├── buffer_edit_format_hex.png
│ ├── buffer_format_modified.png
│ ├── capture_command_state.png
│ ├── capture_debug_groups.png
│ ├── capture_render_passes.png
│ ├── capture_specific_frame.png
│ ├── frame_capture_commands.png
│ ├── vertex_buffer_capture.png
│ ├── webgpu_inspector_gui.png
│ ├── webgpu_inspector_panel.png
│ ├── buffer_array_inspection.png
│ ├── buffer_edit_format_orig.png
│ ├── buffer_edit_format_vec4f.png
│ ├── shader_debugger_capture.png
│ ├── shader_debugger_controls.png
│ ├── webgpu_inspector_screen.png
│ ├── buffer_edit_format_modified.png
│ ├── inspect_shader_reflection.png
│ ├── shader_debugger_thread_id.png
│ ├── shader_debugger_controls_restart.png
│ ├── shader_debugger_controls_start.png
│ ├── webgpu_inspector_frame_capture.png
│ ├── shader_debugger_controls_step_into.png
│ ├── shader_debugger_controls_step_out.png
│ ├── shader_debugger_controls_step_over.png
│ └── shader_debugger_controls_play_pause.png
├── record.md
└── formatting_buffer_data.md
├── src
├── utils
│ ├── align.js
│ ├── flags.js
│ ├── base64.js
│ ├── rolling_average.js
│ ├── float.js
│ ├── stacktrace.js
│ ├── reflection_format.js
│ ├── actions.js
│ └── message_port.js
├── extension
│ ├── res
│ │ ├── webgpu_inspector_on-19.png
│ │ └── webgpu_inspector_on-38.png
│ ├── webgpu_inspector_devtools.js
│ ├── webgpu_inspector_devtools.html
│ ├── webgpu_inspector_panel.html
│ ├── img
│ │ ├── debug-pause.svg
│ │ ├── debug-stackframe.svg
│ │ ├── debug-step-out.svg
│ │ ├── debug-restart.svg
│ │ ├── debug-step-into.svg
│ │ ├── debug-stackframe-active.svg
│ │ ├── debug-continue-small.svg
│ │ ├── debug-step-over.svg
│ │ └── debug.svg
│ ├── firefox
│ │ └── manifest.json
│ ├── chrome
│ │ └── manifest.json
│ └── background.js
├── devtools
│ ├── gpu_objects
│ │ ├── buffer.js
│ │ ├── device.js
│ │ ├── adapter.js
│ │ ├── sampler.js
│ │ ├── pipeline_layout.js
│ │ ├── bind_group_layout.js
│ │ ├── compute_pipeline.js
│ │ ├── render_bundle.js
│ │ ├── texture_view.js
│ │ ├── validation_error.js
│ │ ├── bind_group.js
│ │ ├── gpu_object_ref.js
│ │ ├── render_pipeline.js
│ │ ├── index.js
│ │ ├── shader_module.js
│ │ └── gpu_object.js
│ ├── widget
│ │ ├── div.js
│ │ ├── span.js
│ │ ├── pointer.js
│ │ ├── img.js
│ │ ├── tab_page.js
│ │ ├── index.js
│ │ ├── label.js
│ │ ├── text_input.js
│ │ ├── checkbox.js
│ │ ├── button.js
│ │ ├── collapse_button.js
│ │ ├── text_area.js
│ │ ├── collapsable.js
│ │ ├── window.js
│ │ ├── tree_item.js
│ │ ├── dialog.js
│ │ ├── input.js
│ │ ├── split_bar.js
│ │ ├── tab_handle.js
│ │ ├── plot.js
│ │ ├── select.js
│ │ └── split.js
│ ├── stacktrace_viewer.js
│ └── shader_editor.js
├── webgpu_recorder_loader.js
├── webgpu_inspector_loader.js
└── webgpu_inspector_worker.js
├── extensions
├── safari
│ ├── Shared (App)
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── mac-icon-128@1x.png
│ │ │ │ ├── mac-icon-128@2x.png
│ │ │ │ ├── mac-icon-16@1x.png
│ │ │ │ ├── mac-icon-16@2x.png
│ │ │ │ ├── mac-icon-256@1x.png
│ │ │ │ ├── mac-icon-256@2x.png
│ │ │ │ ├── mac-icon-32@1x.png
│ │ │ │ ├── mac-icon-32@2x.png
│ │ │ │ ├── mac-icon-512@1x.png
│ │ │ │ ├── mac-icon-512@2x.png
│ │ │ │ ├── universal-icon-1024@1x.png
│ │ │ │ └── Contents.json
│ │ │ ├── LargeIcon.imageset
│ │ │ │ ├── webgpu_inspector_on-38.png
│ │ │ │ └── Contents.json
│ │ │ └── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ ├── Resources
│ │ │ ├── Icon.png
│ │ │ ├── Style.css
│ │ │ └── Script.js
│ │ ├── Base.lproj
│ │ │ └── Main.html
│ │ └── ViewController.swift
│ ├── WebGPU_Inspector.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ ├── xcuserdata
│ │ │ │ └── brendan.duncan.xcuserdatad
│ │ │ │ │ └── UserInterfaceState.xcuserstate
│ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ │ └── brendan.duncan.xcuserdatad
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── macOS (App)
│ │ ├── Info.plist
│ │ ├── WebGPU Inspector.entitlements
│ │ └── AppDelegate.swift
│ ├── macOS (Extension)
│ │ ├── WebGPU Inspector.entitlements
│ │ └── Info.plist
│ ├── iOS (App)
│ │ ├── SceneDelegate.swift
│ │ ├── AppDelegate.swift
│ │ ├── Info.plist
│ │ └── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ ├── iOS (Extension)
│ │ └── Info.plist
│ └── Shared (Extension)
│ │ └── SafariWebExtensionHandler.swift
├── chrome
│ ├── res
│ │ ├── webgpu_inspector_on-19.png
│ │ └── webgpu_inspector_on-38.png
│ ├── webgpu_inspector_devtools.js
│ ├── webgpu_inspector_devtools.html
│ ├── webgpu_inspector_panel.html
│ ├── img
│ │ ├── debug-pause.svg
│ │ ├── debug-stackframe.svg
│ │ ├── debug-step-out.svg
│ │ ├── debug-restart.svg
│ │ ├── debug-step-into.svg
│ │ ├── debug-stackframe-active.svg
│ │ ├── debug-continue-small.svg
│ │ ├── debug-step-over.svg
│ │ └── debug.svg
│ ├── webgpu_inspector_worker.js
│ ├── background.js
│ ├── manifest.json
│ └── content_script.js
└── firefox
│ ├── webgpu_inspector_0.17.0.zip
│ ├── res
│ ├── webgpu_inspector_on-19.png
│ └── webgpu_inspector_on-38.png
│ ├── webgpu_inspector_devtools.js
│ ├── webgpu_inspector_devtools.html
│ ├── webgpu_inspector_panel.html
│ ├── img
│ ├── debug-pause.svg
│ ├── debug-stackframe.svg
│ ├── debug-step-into.svg
│ ├── debug-step-out.svg
│ ├── debug-restart.svg
│ ├── debug-stackframe-active.svg
│ ├── debug-continue-small.svg
│ ├── debug-step-over.svg
│ └── debug.svg
│ ├── webgpu_inspector_worker.js
│ ├── manifest.json
│ ├── background.js
│ └── content_script.js
├── LICENSE
├── package.json
└── CHANGELOG.md
/test/worker_import.js:
--------------------------------------------------------------------------------
1 | export function foo() { return 0; }
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package-lock.json
3 | extensions/*.map
4 | extensions/**/*.map
5 | .vscode
6 |
--------------------------------------------------------------------------------
/docs/images/capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture.png
--------------------------------------------------------------------------------
/docs/images/inspect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect.png
--------------------------------------------------------------------------------
/docs/images/record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/record.png
--------------------------------------------------------------------------------
/docs/images/capture_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_button.png
--------------------------------------------------------------------------------
/docs/images/frame_capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/frame_capture.png
--------------------------------------------------------------------------------
/docs/images/inspect_shader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_shader.png
--------------------------------------------------------------------------------
/docs/images/inspect_stats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_stats.png
--------------------------------------------------------------------------------
/docs/images/inspect_status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_status.png
--------------------------------------------------------------------------------
/docs/images/inspector_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspector_info.png
--------------------------------------------------------------------------------
/docs/images/buffer_format_hex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_format_hex.png
--------------------------------------------------------------------------------
/docs/images/enable_extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/enable_extension.png
--------------------------------------------------------------------------------
/docs/images/inspect_objects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_objects.png
--------------------------------------------------------------------------------
/docs/images/inspect_texture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_texture.png
--------------------------------------------------------------------------------
/docs/images/inspector_start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspector_start.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger.png
--------------------------------------------------------------------------------
/docs/images/webgpu_inspector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/webgpu_inspector.png
--------------------------------------------------------------------------------
/src/utils/align.js:
--------------------------------------------------------------------------------
1 | export function alignTo(size, alignment) {
2 | return (size + alignment - 1) & ~(alignment - 1);
3 | }
4 |
--------------------------------------------------------------------------------
/docs/images/buffer_format_orig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_format_orig.png
--------------------------------------------------------------------------------
/docs/images/buffer_format_vec4f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_format_vec4f.png
--------------------------------------------------------------------------------
/docs/images/capture_frame_stats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_frame_stats.png
--------------------------------------------------------------------------------
/docs/images/capture_stacktrace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_stacktrace.png
--------------------------------------------------------------------------------
/docs/images/inspect_stacktrace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_stacktrace.png
--------------------------------------------------------------------------------
/docs/images/buffer_data_inspection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_data_inspection.png
--------------------------------------------------------------------------------
/docs/images/buffer_edit_format_hex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_edit_format_hex.png
--------------------------------------------------------------------------------
/docs/images/buffer_format_modified.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_format_modified.png
--------------------------------------------------------------------------------
/docs/images/capture_command_state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_command_state.png
--------------------------------------------------------------------------------
/docs/images/capture_debug_groups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_debug_groups.png
--------------------------------------------------------------------------------
/docs/images/capture_render_passes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_render_passes.png
--------------------------------------------------------------------------------
/docs/images/capture_specific_frame.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/capture_specific_frame.png
--------------------------------------------------------------------------------
/docs/images/frame_capture_commands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/frame_capture_commands.png
--------------------------------------------------------------------------------
/docs/images/vertex_buffer_capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/vertex_buffer_capture.png
--------------------------------------------------------------------------------
/docs/images/webgpu_inspector_gui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/webgpu_inspector_gui.png
--------------------------------------------------------------------------------
/docs/images/webgpu_inspector_panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/webgpu_inspector_panel.png
--------------------------------------------------------------------------------
/docs/images/buffer_array_inspection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_array_inspection.png
--------------------------------------------------------------------------------
/docs/images/buffer_edit_format_orig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_edit_format_orig.png
--------------------------------------------------------------------------------
/docs/images/buffer_edit_format_vec4f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_edit_format_vec4f.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_capture.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls.png
--------------------------------------------------------------------------------
/docs/images/webgpu_inspector_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/webgpu_inspector_screen.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/docs/images/buffer_edit_format_modified.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/buffer_edit_format_modified.png
--------------------------------------------------------------------------------
/docs/images/inspect_shader_reflection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/inspect_shader_reflection.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_thread_id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_thread_id.png
--------------------------------------------------------------------------------
/src/extension/res/webgpu_inspector_on-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/src/extension/res/webgpu_inspector_on-19.png
--------------------------------------------------------------------------------
/src/extension/res/webgpu_inspector_on-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/src/extension/res/webgpu_inspector_on-38.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls_restart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls_restart.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls_start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls_start.png
--------------------------------------------------------------------------------
/docs/images/webgpu_inspector_frame_capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/webgpu_inspector_frame_capture.png
--------------------------------------------------------------------------------
/extensions/chrome/res/webgpu_inspector_on-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/chrome/res/webgpu_inspector_on-19.png
--------------------------------------------------------------------------------
/extensions/chrome/res/webgpu_inspector_on-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/chrome/res/webgpu_inspector_on-38.png
--------------------------------------------------------------------------------
/extensions/firefox/webgpu_inspector_0.17.0.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/firefox/webgpu_inspector_0.17.0.zip
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls_step_into.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls_step_into.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls_step_out.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls_step_out.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls_step_over.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls_step_over.png
--------------------------------------------------------------------------------
/extensions/firefox/res/webgpu_inspector_on-19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/firefox/res/webgpu_inspector_on-19.png
--------------------------------------------------------------------------------
/extensions/firefox/res/webgpu_inspector_on-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/firefox/res/webgpu_inspector_on-38.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Resources/Icon.png
--------------------------------------------------------------------------------
/docs/images/shader_debugger_controls_play_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/docs/images/shader_debugger_controls_play_pause.png
--------------------------------------------------------------------------------
/src/extension/webgpu_inspector_devtools.js:
--------------------------------------------------------------------------------
1 | chrome.devtools.panels.create(
2 | "WebGPU Inspector",
3 | "/res/webgpu_inspector_on-38.png",
4 | "/webgpu_inspector_panel.html"
5 | );
6 |
--------------------------------------------------------------------------------
/extensions/chrome/webgpu_inspector_devtools.js:
--------------------------------------------------------------------------------
1 | chrome.devtools.panels.create(
2 | "WebGPU Inspector",
3 | "/res/webgpu_inspector_on-38.png",
4 | "/webgpu_inspector_panel.html"
5 | );
6 |
--------------------------------------------------------------------------------
/extensions/firefox/webgpu_inspector_devtools.js:
--------------------------------------------------------------------------------
1 | chrome.devtools.panels.create(
2 | "WebGPU Inspector",
3 | "/res/webgpu_inspector_on-38.png",
4 | "/webgpu_inspector_panel.html"
5 | );
6 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png
--------------------------------------------------------------------------------
/test/worker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/webgpu_inspector_on-38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/webgpu_inspector_on-38.png
--------------------------------------------------------------------------------
/extensions/safari/WebGPU_Inspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/extension/webgpu_inspector_devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/extensions/chrome/webgpu_inspector_devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/extensions/firefox/webgpu_inspector_devtools.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/extension/webgpu_inspector_panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/extensions/chrome/webgpu_inspector_panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/extensions/firefox/webgpu_inspector_panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/extension/img/debug-pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/buffer.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class Buffer extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | Buffer.className = "Buffer";
10 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/device.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class Device extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | Device.className = "Device";
10 |
--------------------------------------------------------------------------------
/extensions/safari/WebGPU_Inspector.xcodeproj/project.xcworkspace/xcuserdata/brendan.duncan.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brendan-duncan/webgpu_inspector/HEAD/extensions/safari/WebGPU_Inspector.xcodeproj/project.xcworkspace/xcuserdata/brendan.duncan.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/extensions/safari/macOS (App)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SFSafariWebExtensionConverterVersion
6 | 15.2
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/adapter.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class Adapter extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | Adapter.className = "Adapter";
10 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/sampler.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class Sampler extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | Sampler.className = "Sampler";
10 |
--------------------------------------------------------------------------------
/src/devtools/widget/div.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 |
3 | /**
4 | * A generic DIV element, usually used as a container for other widgets.
5 | */
6 | export class Div extends Widget {
7 | constructor(parent, options) {
8 | super('div', parent, options);
9 | }
10 | }
11 |
12 | Div._idPrefix = 'DIV';
13 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/pipeline_layout.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class PipelineLayout extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | PipelineLayout.className = "PipelineLayout";
10 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/bind_group_layout.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class BindGroupLayout extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | BindGroupLayout.className = "BindGroupLayout";
10 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/compute_pipeline.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class ComputePipeline extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 | }
9 | ComputePipeline.className = "ComputePipeline";
10 |
--------------------------------------------------------------------------------
/extensions/safari/WebGPU_Inspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/render_bundle.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class RenderBundle extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, stacktrace);
6 | this.descriptor = descriptor;
7 | this.commands = [];
8 | }
9 | }
10 | RenderBundle.className = "RenderBundle";
11 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/texture_view.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class TextureView extends GPUObject {
4 | constructor(id, texture, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | this.texture = texture;
8 | }
9 | }
10 | TextureView.className = "TextureView";
11 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/validation_error.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class ValidationError extends GPUObject {
4 | constructor(id, object, message, stacktrace) {
5 | super(id, null, stacktrace);
6 | this.message = message;
7 | this.object = object ?? 0;
8 | }
9 | }
10 | ValidationError.className = "ValidationError";
11 |
--------------------------------------------------------------------------------
/src/devtools/widget/span.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 |
3 | /**
4 | * A SPAN element widget.
5 | */
6 | export class Span extends Widget {
7 | constructor(parent, options) {
8 | super('span', parent, options);
9 | if (options?.text && !options?.tooltip) {
10 | this.tooltip = options.text;
11 | }
12 | }
13 | }
14 |
15 | Span._idPrefix = 'SPAN';
16 |
--------------------------------------------------------------------------------
/src/extension/img/debug-stackframe.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-stackframe.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-stackframe.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/utils/flags.js:
--------------------------------------------------------------------------------
1 | export function getFlagString(value, flags) {
2 | function _addFlagString(flags, flag) {
3 | return flags === "" ? flag : `${flags} | ${flag}`;
4 | }
5 | let flagStr = "";
6 | for (const flagName in flags) {
7 | const flag = flags[flagName];
8 | if (value & flag) {
9 | flagStr = _addFlagString(flagStr, flagName);
10 | }
11 | }
12 | return flagStr;
13 | }
14 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/bind_group.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class BindGroup extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 |
9 | get entries() {
10 | return this.descriptor?.entries;
11 | }
12 | }
13 | BindGroup.className = "BindGroup";
14 |
--------------------------------------------------------------------------------
/extensions/safari/macOS (Extension)/WebGPU Inspector.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/gpu_object_ref.js:
--------------------------------------------------------------------------------
1 | export class GPUObjectRef {
2 | constructor(object) {
3 | this.object = object;
4 | this.referenceCount = 1;
5 | }
6 |
7 | addReference() {
8 | this.referenceCount++;
9 | }
10 |
11 | removeReference() {
12 | this.referenceCount--;
13 | if (this.referenceCount === 0) {
14 | this.object.destroy();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/extension/img/debug-step-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-step-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/extension/img/debug-restart.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/extension/img/debug-step-into.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-restart.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-step-into.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-step-into.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-step-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/extension/img/debug-stackframe-active.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-stackframe-active.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-restart.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-stackframe-active.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/render_pipeline.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 |
3 | export class RenderPipeline extends GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | super(id, descriptor, stacktrace);
6 | this.descriptor = descriptor;
7 | }
8 |
9 | get topology() {
10 | return this.descriptor?.primitive?.topology ?? "triangle-list";
11 | }
12 | }
13 | RenderPipeline.className = "RenderPipeline";
14 |
--------------------------------------------------------------------------------
/src/extension/img/debug-continue-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-continue-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-continue-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/safari/macOS (App)/WebGPU Inspector.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "webgpu_inspector_on-38.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/src/devtools/widget/pointer.js:
--------------------------------------------------------------------------------
1 | export class Pointer {
2 | constructor(event) {
3 | this.event = event;
4 | this.pageX = event.pageX;
5 | this.pageY = event.pageY;
6 | this.clientX = event.clientX;
7 | this.clientY = event.clientY;
8 | this.id = event.pointerId;
9 | this.type = event.pointerType;
10 | this.buttons = event.buttons ?? -1;
11 | }
12 |
13 | getCoalesced() {
14 | return this.event.getCoalescedEvents().map((p) => new Pointer(p));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/extensions/safari/iOS (App)/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // iOS (App)
4 | //
5 | // Created by Brendan Duncan on 2/12/24.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
15 | guard let _ = (scene as? UIWindowScene) else { return }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/extensions/safari/iOS (Extension)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.Safari.web-extension
9 | NSExtensionPrincipalClass
10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/extensions/safari/macOS (Extension)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.Safari.web-extension
9 | NSExtensionPrincipalClass
10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/devtools/widget/img.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 |
3 | export class Img extends Widget {
4 | constructor(parent, options) {
5 | super('img', parent, options);
6 | }
7 |
8 | get src() {
9 | return this.element.src;
10 | }
11 |
12 | set src(v) {
13 | this.element.src = v;
14 | }
15 |
16 | configure(options) {
17 | if (!options) {
18 | return;
19 | }
20 | super.configure(options);
21 | if (options.src !== undefined) {
22 | this.element.src = options.src;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/extension/img/debug-step-over.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug-step-over.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug-step-over.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/utils/base64.js:
--------------------------------------------------------------------------------
1 | export async function encodeDataUrl(bytes, type = "application/octet-stream") {
2 | return await new Promise((resolve, reject) => {
3 | const reader = Object.assign(new FileReader(), {
4 | onload: () => resolve(reader.result),
5 | onerror: () => reject(reader.error),
6 | });
7 | reader.readAsDataURL(new File([bytes], "", { type }));
8 | });
9 | }
10 |
11 | export async function decodeDataUrl(dataUrl) {
12 | const res = await fetch(dataUrl);
13 | return new Uint8Array(await res.arrayBuffer());
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/extensions/safari/macOS (App)/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // macOS (App)
4 | //
5 | // Created by Brendan Duncan on 2/12/24.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ notification: Notification) {
14 | // Override point for customization after application launch.
15 | }
16 |
17 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
18 | return true
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/rolling_average.js:
--------------------------------------------------------------------------------
1 | export class RollingAverage {
2 | constructor(windowSize) {
3 | this.windowSize = windowSize;
4 | this.buffer = [];
5 | this.sum = 0;
6 | }
7 |
8 | add(frameTime) {
9 | this.buffer.push(frameTime);
10 | if (this.buffer.length > this.windowSize) {
11 | this.sum -= this.buffer.shift();
12 | }
13 | this.sum += frameTime;
14 | }
15 |
16 | get average() {
17 | if (this.buffer.length === 0) {
18 | return 0;
19 | }
20 | return this.sum / this.buffer.length;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/devtools/widget/tab_page.js:
--------------------------------------------------------------------------------
1 | import { Div } from './div.js';
2 |
3 | /**
4 | * A single content area with multiple panels, each associated with a header in a list.
5 | */
6 | export class TabPage extends Div {
7 | constructor(panel, parent, options) {
8 | super(parent, options);
9 | this.classList.add('tab-page');
10 | this.style.display = 'none';
11 | this.panel = panel;
12 | if (panel) {
13 | panel.parent = this;
14 | //panel.style.width = '100%';
15 | }
16 | }
17 | }
18 |
19 | TabPage._idPrefix = 'TABPAGE';
20 | TabPage.isTabPage = true;
21 |
--------------------------------------------------------------------------------
/extensions/chrome/webgpu_inspector_worker.js:
--------------------------------------------------------------------------------
1 | var exports;(exports={}).webgpuInspectorWorker=e=>{if(!e.__webgpuInspector){if(e.__webgpuInspector=!0,e.terminate){const t=e.terminate;e.terminate=function(){const n=t.call(e,...arguments);return e.__webgpuInspector=!1,n}}e.addEventListener('message',e=>{e.data.__webgpuInspector&&window.dispatchEvent(new CustomEvent('__WebGPUInspector',{detail:e.data}))}),window.addEventListener('__WebGPUInspector',t=>{e.__webgpuInspector&&t.detail.__webgpuInspector&&!t.detail.__webgpuInspectorPage&&e.postMessage(t.detail)})}},Object.defineProperty(exports,'__esModule',{value:!0});
2 | //# sourceMappingURL=webgpu_inspector_worker.js.map
3 |
--------------------------------------------------------------------------------
/extensions/firefox/webgpu_inspector_worker.js:
--------------------------------------------------------------------------------
1 | var exports;(exports={}).webgpuInspectorWorker=e=>{if(!e.__webgpuInspector){if(e.__webgpuInspector=!0,e.terminate){const t=e.terminate;e.terminate=function(){const n=t.call(e,...arguments);return e.__webgpuInspector=!1,n}}e.addEventListener('message',e=>{e.data.__webgpuInspector&&window.dispatchEvent(new CustomEvent('__WebGPUInspector',{detail:e.data}))}),window.addEventListener('__WebGPUInspector',t=>{e.__webgpuInspector&&t.detail.__webgpuInspector&&!t.detail.__webgpuInspectorPage&&e.postMessage(t.detail)})}},Object.defineProperty(exports,'__esModule',{value:!0});
2 | //# sourceMappingURL=webgpu_inspector_worker.js.map
3 |
--------------------------------------------------------------------------------
/extensions/safari/WebGPU_Inspector.xcodeproj/xcuserdata/brendan.duncan.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | WebGPU Inspector (iOS).xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | WebGPU Inspector (macOS).xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/devtools/widget/index.js:
--------------------------------------------------------------------------------
1 | export * from './button.js';
2 | export * from './checkbox.js';
3 | export * from "./collapsable.js";
4 | export * from './div.js';
5 | export * from './img.js';
6 | export * from './input.js';
7 | export * from './label.js';
8 | export * from "./log.js";
9 | export * from './mouse_capture.js';
10 | export * from './number_input.js';
11 | export * from './signal.js';
12 | export * from './span.js';
13 | export * from './tab_handle.js';
14 | export * from './tab_page.js';
15 | export * from './tab_widget.js';
16 | export * from './text_area.js';
17 | export * from './text_input.js';
18 | export * from './widget.js';
19 | export * from './window.js';
20 |
--------------------------------------------------------------------------------
/src/devtools/widget/label.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 |
3 | export class Label extends Widget {
4 | constructor(text, parent, options) {
5 | super('label', parent, options);
6 | this.classList.add('label');
7 | this.text = text;
8 | }
9 |
10 | configure(options) {
11 | if (!options) {
12 | return;
13 | }
14 | super.configure(options);
15 | if (options.for) {
16 | this.for = options.for;
17 | }
18 | }
19 |
20 | get for() {
21 | return this._element.htmlFor;
22 | }
23 |
24 | set for(v) {
25 | if (!v) {
26 | this._element.htmlFor = '';
27 | } else if (v.constructor === String) {
28 | this._element.htmlFor = v;
29 | } else {
30 | this._element.htmlFor = v.id;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/extension/img/debug.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/chrome/img/debug.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/extensions/firefox/img/debug.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/index.js:
--------------------------------------------------------------------------------
1 | export { Adapter } from "./adapter.js";
2 | export { BindGroupLayout } from "./bind_group_layout.js";
3 | export { BindGroup } from "./bind_group.js";
4 | export { Buffer } from "./buffer.js";
5 | export { ComputePipeline } from "./compute_pipeline.js";
6 | export { Device } from "./device.js";
7 | export { GPUObject } from "./gpu_object.js";
8 | export { PipelineLayout } from "./pipeline_layout.js";
9 | export { RenderPipeline } from "./render_pipeline.js";
10 | export { Sampler } from "./sampler.js";
11 | export { ShaderModule } from "./shader_module.js";
12 | export { TextureView } from "./texture_view.js";
13 | export { Texture } from "./texture.js";
14 | export { ValidationError } from "./validation_error.js";
15 | export { RenderBundle } from "./render_bundle.js";
16 |
--------------------------------------------------------------------------------
/src/devtools/widget/text_input.js:
--------------------------------------------------------------------------------
1 | import { Input } from './input.js';
2 |
3 | export class TextInput extends Input {
4 | constructor(parent, options) {
5 | super(parent, options);
6 | this.classList.add('text-input');
7 | this.type = 'text';
8 |
9 | this.enableKeyPressEvent();
10 | }
11 |
12 | configure(options) {
13 | if (!options) return;
14 | super.configure(options);
15 | if (options.placeholder) {
16 | this.placeholder = options.placeholder;
17 | }
18 | }
19 |
20 | get placeholder() {
21 | return this.element.placeholder;
22 | }
23 |
24 | set placeholder(v) {
25 | this.element.placeholder = v;
26 | }
27 |
28 | keyPressEvent(e) {
29 | if (e.keyCode === 27) {
30 | // Escape
31 | e.target.blur();
32 | }
33 | return true;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/client/worker_loader.js:
--------------------------------------------------------------------------------
1 | const canvas = document.getElementById("webgpu_canvas");
2 | const offscreenCanvas = canvas.transferControlToOffscreen();
3 |
4 | //const url = new URL('./assets/webgpu_worker.js', import.meta.url);
5 | //console.log(url.href);
6 | const worker = new Worker('/test/client/assets/webgpu_worker.js', { type: 'module' });
7 | //const worker = new Worker('assets/webgpu_worker.js', { type: 'module' });
8 | //const worker = new Worker(url.href, { type: 'module' });
9 | //const worker = new Worker(url, { type: 'module' });
10 |
11 | const devicePixelRatio = window.devicePixelRatio;
12 | offscreenCanvas.width = canvas.clientWidth * devicePixelRatio;
13 | offscreenCanvas.height = canvas.clientHeight * devicePixelRatio;
14 | worker.postMessage({ type: 'init', offscreenCanvas }, [offscreenCanvas]);
15 |
--------------------------------------------------------------------------------
/extensions/firefox/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 |
4 | "name": "WebGPU Inspector",
5 | "short_name": "webgpu_inspector",
6 | "version": "0.18.0",
7 |
8 | "description": "WebGPU Inspector Debugging Tools",
9 | "author": "Brendan Duncan",
10 | "icons": {
11 | "19": "res/webgpu_inspector_on-19.png",
12 | "38": "res/webgpu_inspector_on-38.png"
13 | },
14 |
15 | "background": {
16 | "scripts": ["background.js"],
17 | "persistent": true
18 | },
19 | "devtools_page": "webgpu_inspector_devtools.html",
20 | "content_scripts": [
21 | {
22 | "js": ["content_script.js"],
23 | "matches": [
24 | "http://*/*",
25 | "https://*/*",
26 | "file://*/*"],
27 | "run_at" : "document_start",
28 | "all_frames" : true
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/extension/firefox/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 |
4 | "name": "WebGPU Inspector",
5 | "short_name": "webgpu_inspector",
6 | "version": "0.18.0",
7 |
8 | "description": "WebGPU Inspector Debugging Tools",
9 | "author": "Brendan Duncan",
10 | "icons": {
11 | "19": "res/webgpu_inspector_on-19.png",
12 | "38": "res/webgpu_inspector_on-38.png"
13 | },
14 |
15 | "background": {
16 | "scripts": ["background.js"],
17 | "persistent": true
18 | },
19 | "devtools_page": "webgpu_inspector_devtools.html",
20 | "content_scripts": [
21 | {
22 | "js": ["content_script.js"],
23 | "matches": [
24 | "http://*/*",
25 | "https://*/*",
26 | "file://*/*"],
27 | "run_at" : "document_start",
28 | "all_frames" : true
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/extensions/safari/iOS (App)/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iOS (App)
4 | //
5 | // Created by Brendan Duncan on 2/12/24.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/extensions/safari/iOS (App)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SFSafariWebExtensionConverterVersion
6 | 15.2
7 | UIApplicationSceneManifest
8 |
9 | UIApplicationSupportsMultipleScenes
10 |
11 | UISceneConfigurations
12 |
13 | UIWindowSceneSessionRoleApplication
14 |
15 |
16 | UISceneConfigurationName
17 | Default Configuration
18 | UISceneDelegateClassName
19 | $(PRODUCT_MODULE_NAME).SceneDelegate
20 | UISceneStoryboardFile
21 | Main
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/webgpu_recorder_loader.js:
--------------------------------------------------------------------------------
1 | // WebGPU Recorder code comes from https://github.com/brendan-duncan/webgpu_recorder.
2 | // It is injected here by the npm rollup build process.
3 | import coreLoader from "webgpu_recorder_core_func";
4 |
5 | const webgpuRecorderLoadedKey = "WEBGPU_RECORDER_LOADED";
6 | const recorderMessage = sessionStorage.getItem(webgpuRecorderLoadedKey);
7 |
8 | if (recorderMessage) {
9 | sessionStorage.removeItem(webgpuRecorderLoadedKey);
10 |
11 | const data = recorderMessage.split("%");
12 | const frames = data[0];
13 | const filename = data[1];
14 | const dl = data[2];
15 | const removeUnusedResources = true;
16 | const messageRecording = true;
17 |
18 | const download = dl === null ? true : dl === "false" ? false : dl === "true" ? true : dl;
19 |
20 | self._webgpu_recorder_init = {
21 | filename,
22 | frames,
23 | download,
24 | removeUnusedResources,
25 | messageRecording
26 | };
27 |
28 | self.__webgpu_src = coreLoader;
29 | self.__webgpu_src();
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/float.js:
--------------------------------------------------------------------------------
1 | // From https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript
2 | export function float16ToFloat32(float16) {
3 | var s = (float16 & 0x8000) >> 15;
4 | var e = (float16 & 0x7C00) >> 10;
5 | var f = float16 & 0x03FF;
6 |
7 | if (e == 0) {
8 | return (s ? -1:1) * Math.pow(2, -14) * (f / Math.pow(2, 10));
9 | } else if (e == 0x1F) {
10 | return f ? NaN : ((s ? -1 : 1) * Infinity);
11 | }
12 |
13 | return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + (f / Math.pow(2, 10)));
14 | }
15 |
16 | const uint32 = new Uint32Array(1);
17 | const uint32ToFloat32 = new Float32Array(uint32.buffer, 0, 1);
18 |
19 | export function float11ToFloat32(f11) {
20 | const u32 = (((((f11) >> 6) & 0x1F) + (127 - 15)) << 23) | (((f11) & 0x3F) << 17);
21 | uint32[0] = u32;
22 | return uint32ToFloat32[0];
23 | }
24 |
25 | export function float10ToFloat32(f10) {
26 | const u32 = (((((f10) >> 5) & 0x1F) + (127 - 15)) << 23) | (((f10) & 0x1F) << 18);
27 | uint32[0] = u32;
28 | return uint32ToFloat32[0];
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Brendan Duncan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/webgpu_inspector_loader.js:
--------------------------------------------------------------------------------
1 | // WebGPU Inspector code is injected here by the npm rollup build process.
2 | import coreLoader from "webgpu_inspector_core_func";
3 |
4 | const webgpuInspectorLoadedKey = "WEBGPU_INSPECTOR_LOADED";
5 | const webgpuInspectorCaptureFrameKey = "WEBGPU_INSPECTOR_CAPTURE_FRAME";
6 | const inspectMessage = sessionStorage.getItem(webgpuInspectorLoadedKey);
7 |
8 | if (inspectMessage) {
9 | sessionStorage.removeItem(webgpuInspectorLoadedKey);
10 |
11 | if (inspectMessage !== "true") {
12 | sessionStorage.setItem(webgpuInspectorCaptureFrameKey, inspectMessage);
13 | }
14 |
15 | self.__webgpu_src = coreLoader;
16 | self.__webgpu_src();
17 | }
18 |
19 | if (window) {
20 | window.addEventListener("__WebGPUInspector", (event) => {
21 | const message = event.detail || event.data;
22 | if (typeof message !== "object" || !message.__webgpuInspector) {
23 | return;
24 | }
25 | if (message.action === "webgpu_inspector_start_inspection") {
26 | if (!self.__webgpu_src) {
27 | self.__webgpu_src = coreLoader;
28 | self.__webgpu_src();
29 | }
30 | }
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/extensions/chrome/background.js:
--------------------------------------------------------------------------------
1 | (()=>{const e=new Map;function n(n,t){const o=function(n){return e.has(n)||e.set(n,new Map),e.get(n)}(t);o.has(n.name)||o.set(n.name,[]);const s=o.get(n.name);s.includes(n)||(s.push(n),n.onDisconnect.addListener(()=>{console.log(`[WebGPU Inspector] Port disconnected: ${n.name} (tab ${t})`),s.includes(n)&&s.splice(s.indexOf(n),1),0===s.length&&o.delete(n.name),0===o.size&&e.delete(t)}),console.log(`[WebGPU Inspector] Port registered: ${n.name} (tab ${t})`))}chrome.runtime.onConnect.addListener(t=>{console.log(`[WebGPU Inspector] Port connecting: ${t.name}`);let o=!1;t.onMessage.addListener((t,s)=>{const a=void 0!==t.tabId?t.tabId:s.sender?.tab?.id??0;o||(n(s,a),o=!0);const c=e.get(a);if(!c)return void console.error(`[WebGPU Inspector] No port map found for tab ${a}`);const r=(e,n)=>{e.forEach(e=>{try{e.postMessage(n)}catch(n){console.error(`[WebGPU Inspector] Failed to post message to port ${e.name}:`,n)}})};'webgpu-inspector-panel'===s.name&&c.has('webgpu-inspector-page')&&r(c.get('webgpu-inspector-page'),t),'webgpu-inspector-page'===s.name&&c.has('webgpu-inspector-panel')&&r(c.get('webgpu-inspector-panel'),t)})})})();
2 | //# sourceMappingURL=background.js.map
3 |
--------------------------------------------------------------------------------
/extensions/firefox/background.js:
--------------------------------------------------------------------------------
1 | (()=>{const e=new Map;function n(n,t){const o=function(n){return e.has(n)||e.set(n,new Map),e.get(n)}(t);o.has(n.name)||o.set(n.name,[]);const s=o.get(n.name);s.includes(n)||(s.push(n),n.onDisconnect.addListener(()=>{console.log(`[WebGPU Inspector] Port disconnected: ${n.name} (tab ${t})`),s.includes(n)&&s.splice(s.indexOf(n),1),0===s.length&&o.delete(n.name),0===o.size&&e.delete(t)}),console.log(`[WebGPU Inspector] Port registered: ${n.name} (tab ${t})`))}chrome.runtime.onConnect.addListener(t=>{console.log(`[WebGPU Inspector] Port connecting: ${t.name}`);let o=!1;t.onMessage.addListener((t,s)=>{const a=void 0!==t.tabId?t.tabId:s.sender?.tab?.id??0;o||(n(s,a),o=!0);const c=e.get(a);if(!c)return void console.error(`[WebGPU Inspector] No port map found for tab ${a}`);const r=(e,n)=>{e.forEach(e=>{try{e.postMessage(n)}catch(n){console.error(`[WebGPU Inspector] Failed to post message to port ${e.name}:`,n)}})};'webgpu-inspector-panel'===s.name&&c.has('webgpu-inspector-page')&&r(c.get('webgpu-inspector-page'),t),'webgpu-inspector-page'===s.name&&c.has('webgpu-inspector-panel')&&r(c.get('webgpu-inspector-panel'),t)})})})();
2 | //# sourceMappingURL=background.js.map
3 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Base.lproj/Main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | You can turn on WebGPU Inspector’s Safari extension in Settings.
15 | You can turn on WebGPU Inspector’s extension in Safari Extensions preferences.
16 | WebGPU Inspector’s extension is currently on. You can turn it off in Safari Extensions preferences.
17 | WebGPU Inspector’s extension is currently off. You can turn it on in Safari Extensions preferences.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/devtools/widget/checkbox.js:
--------------------------------------------------------------------------------
1 | import { Input } from './input.js';
2 | import { Label } from './label.js';
3 | import { Span } from './span.js';
4 |
5 | export class Checkbox extends Span {
6 | constructor(parent, options) {
7 | super(parent, options);
8 | this.classList.add('styled-checkbox-container');
9 |
10 | this.input = new Input(this, options);
11 | this.input.type = 'checkbox';
12 | this.input.classList.add('styled-checkbox');
13 |
14 | if (!this.label) {
15 | this.label = new Label('', this, { for: this });
16 | }
17 |
18 | if (options?.tooltip) {
19 | this.input.title = options.tooltip;
20 | this.label.title = options.tooltip;
21 | }
22 | }
23 |
24 | get checked() {
25 | return this.input.checked;
26 | }
27 |
28 | set checked(v) {
29 | this.input.checked = v;
30 | }
31 |
32 | get label() {
33 | return this.input.label;
34 | }
35 |
36 | set label(v) {
37 | this.input.label = v;
38 | }
39 |
40 | get indeterminate() {
41 | return this.input.indeterminate;
42 | }
43 |
44 | set indeterminate(v) {
45 | this.input.indeterminate = v;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/shader_module.js:
--------------------------------------------------------------------------------
1 | import { GPUObject } from "./gpu_object.js";
2 | import { WgslReflect } from "wgsl_reflect/wgsl_reflect.module.js";
3 |
4 | export class ShaderModule extends GPUObject {
5 | constructor(id, descriptor, stacktrace) {
6 | super(id, descriptor, stacktrace);
7 | this._reflection = null;
8 | this.descriptor = descriptor;
9 | this.hasVertexEntries = descriptor?.code ? descriptor.code.indexOf("@vertex") != -1 : false;
10 | this.hasFragmentEntries = descriptor?.code ? descriptor.code.indexOf("@fragment") != -1 : false;
11 | this.hasComputeEntries = descriptor?.code ? descriptor.code.indexOf("@compute") != -1 : false;
12 | this.replacementCode = null;
13 |
14 | this.isDestroyed = false;
15 | }
16 |
17 | get code() {
18 | return this.replacementCode ?? this.descriptor?.code ?? "";
19 | }
20 |
21 | get reflection() {
22 | if (this._reflection === null) {
23 | try {
24 | this._reflection = new WgslReflect(this.code);
25 | } catch (e) {
26 | this._reflection = null;
27 | }
28 | }
29 | return this._reflection;
30 | }
31 | }
32 | ShaderModule.className = "ShaderModule";
33 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Resources/Style.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-user-select: none;
3 | -webkit-user-drag: none;
4 | cursor: default;
5 | }
6 |
7 | :root {
8 | color-scheme: light dark;
9 |
10 | --spacing: 20px;
11 | }
12 |
13 | html {
14 | height: 100%;
15 | }
16 |
17 | body {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | flex-direction: column;
22 |
23 | gap: var(--spacing);
24 | margin: 0 calc(var(--spacing) * 2);
25 | height: 100%;
26 |
27 | font: -apple-system-short-body;
28 | text-align: center;
29 | }
30 |
31 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
32 | display: none;
33 | }
34 |
35 | body.platform-ios .platform-mac {
36 | display: none;
37 | }
38 |
39 | body.platform-mac .platform-ios {
40 | display: none;
41 | }
42 |
43 | body.platform-ios .platform-mac {
44 | display: none;
45 | }
46 |
47 | body:not(.state-on, .state-off) :is(.state-on, .state-off) {
48 | display: none;
49 | }
50 |
51 | body.state-on :is(.state-off, .state-unknown) {
52 | display: none;
53 | }
54 |
55 | body.state-off :is(.state-on, .state-unknown) {
56 | display: none;
57 | }
58 |
59 | button {
60 | font-size: 1em;
61 | }
62 |
--------------------------------------------------------------------------------
/src/webgpu_inspector_worker.js:
--------------------------------------------------------------------------------
1 | export function webgpuInspectorWorker(worker) {
2 | if (worker.__webgpuInspector) {
3 | return;
4 | }
5 |
6 | worker.__webgpuInspector = true;
7 |
8 | // Intercept worker termination and remove it from list so we don't send
9 | // messages to a terminated worker.
10 | if (worker.terminate) {
11 | const __terminate = worker.terminate;
12 | worker.terminate = function () {
13 | const result = __terminate.call(worker, ...arguments);
14 | worker.__webgpuInspector = false;
15 | return result;
16 | };
17 | }
18 |
19 | worker.addEventListener("message", (event) => {
20 | if (event.data.__webgpuInspector) {
21 | window.dispatchEvent(new CustomEvent("__WebGPUInspector", { detail: event.data }));
22 | }
23 | });
24 |
25 | window.addEventListener("__WebGPUInspector", (event) => {
26 | // Forward messages from the page to the worker, if the worker hasn't been terminated,
27 | // the message is from the inspector, and the message is not from the worker.
28 | if (worker.__webgpuInspector && event.detail.__webgpuInspector &&
29 | !event.detail.__webgpuInspectorPage) {
30 | worker.postMessage(event.detail);
31 | }
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (Extension)/SafariWebExtensionHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariWebExtensionHandler.swift
3 | // Shared (Extension)
4 | //
5 | // Created by Brendan Duncan on 2/12/24.
6 | //
7 |
8 | import SafariServices
9 | import os.log
10 |
11 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
12 |
13 | func beginRequest(with context: NSExtensionContext) {
14 | let request = context.inputItems.first as? NSExtensionItem
15 |
16 | let profile: UUID?
17 | if #available(iOS 17.0, macOS 14.0, *) {
18 | profile = request?.userInfo?[SFExtensionProfileKey] as? UUID
19 | } else {
20 | profile = request?.userInfo?["profile"] as? UUID
21 | }
22 |
23 | let message: Any?
24 | if #available(iOS 17.0, macOS 14.0, *) {
25 | message = request?.userInfo?[SFExtensionMessageKey]
26 | } else {
27 | message = request?.userInfo?["message"]
28 | }
29 |
30 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@ (profile: %@)", String(describing: message), profile?.uuidString ?? "none")
31 |
32 | let response = NSExtensionItem()
33 | response.userInfo = [ SFExtensionMessageKey: [ "echo": message ] ]
34 |
35 | context.completeRequest(returningItems: [ response ], completionHandler: nil)
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/utils/stacktrace.js:
--------------------------------------------------------------------------------
1 | export function getStacktrace() {
2 | if (!Error.captureStackTrace) {
3 | return "";
4 | }
5 | const stacktrace = {};
6 | Error.captureStackTrace(stacktrace, getStacktrace);
7 | if (!stacktrace.stack) {
8 | return "";
9 | }
10 | let stack = stacktrace.stack
11 | .split("\n")
12 | .map((line) => line.split("at ")[1])
13 | .slice(2) // Skip the Error line and the GPU.* line.
14 | .filter((line) => line && !line.includes("webgpu_inspector_loader.js"));
15 |
16 | return stack.join("\n");
17 | }
18 |
19 | // Cache stacktraces since many objects will have the same stacktrace.
20 | // Used as a singleton.
21 | export class StacktraceCache {
22 | constructor() {
23 | this._cache = [];
24 | }
25 |
26 | _getStacktrace(id) {
27 | return id < 0 ? "" : this._cache[id] ?? "";
28 | }
29 |
30 | _setStacktrace(stacktrace) {
31 | if (!stacktrace) {
32 | return -1;
33 | }
34 | const id = this._cache.indexOf(stacktrace);
35 | if (id !== -1) {
36 | return id;
37 | }
38 | this._cache.push(stacktrace);
39 | return this._cache.length - 1;
40 | }
41 |
42 | static getStacktrace(id) {
43 | return StacktraceCache._global._getStacktrace(id);
44 | }
45 |
46 | static setStacktrace(stacktrace) {
47 | return StacktraceCache._global._setStacktrace(stacktrace);
48 | }
49 | }
50 |
51 | StacktraceCache._global = new StacktraceCache();
52 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Resources/Script.js:
--------------------------------------------------------------------------------
1 | function show(platform, enabled, useSettingsInsteadOfPreferences) {
2 | document.body.classList.add(`platform-${platform}`);
3 |
4 | if (useSettingsInsteadOfPreferences) {
5 | document.getElementsByClassName('platform-mac state-on')[0].innerText = "WebGPU Inspector’s extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
6 | document.getElementsByClassName('platform-mac state-off')[0].innerText = "WebGPU Inspector’s extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
7 | document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on WebGPU Inspector’s extension in the Extensions section of Safari Settings.";
8 | document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
9 | }
10 |
11 | if (typeof enabled === "boolean") {
12 | document.body.classList.toggle(`state-on`, enabled);
13 | document.body.classList.toggle(`state-off`, !enabled);
14 | } else {
15 | document.body.classList.remove(`state-on`);
16 | document.body.classList.remove(`state-off`);
17 | }
18 | }
19 |
20 | function openPreferences() {
21 | webkit.messageHandlers.controller.postMessage("open-preferences");
22 | }
23 |
24 | document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
25 |
--------------------------------------------------------------------------------
/src/devtools/widget/button.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 |
3 | export class Button extends Widget {
4 | constructor(parent, options) {
5 | super('button', parent);
6 | this.classList.add('button');
7 |
8 | this.callback = null;
9 | this.onMouseDown = null;
10 | this.onMouseUp = null;
11 |
12 | this._click = this.click.bind(this);
13 | this._mouseDown = this.mouseDown.bind(this);
14 | this._mouseUp = this.mouseUp.bind(this);
15 |
16 | this.element.addEventListener('click', this._click);
17 | this.element.addEventListener('mousedown', this._mouseDown);
18 | this.element.addEventListener('mouseup', this._mouseUp);
19 |
20 | if (options) {
21 | this.configure(options);
22 | }
23 | }
24 |
25 | configure(options) {
26 | super.configure(options);
27 | if (options.callback) {
28 | this.callback = options.callback;
29 | }
30 | if (options.mouseDown) {
31 | this.onMouseDown = options.mouseDown;
32 | }
33 | if (options.mouseUp) {
34 | this.onMouseUp = options.mouseUp;
35 | }
36 | if (options.label) {
37 | this.text = options.label;
38 | }
39 | }
40 |
41 | click(event) {
42 | if (this.callback) {
43 | this.callback.call(this, event);
44 | }
45 | }
46 |
47 | mouseDown(event) {
48 | if (this.onMouseDown) {
49 | this.onMouseDown.call(this, event);
50 | }
51 | }
52 |
53 | mouseUp(event) {
54 | if (this.onMouseUp) {
55 | this.onMouseUp.call(this, event);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webgpu_inspector",
3 | "version": "0.18.0",
4 | "description": "WebGPU Inspection Debugger",
5 | "author": "Brendan Duncan",
6 | "license": "MIT",
7 | "homepage": "https://github.com/brendan-duncan/webgpu_inspector",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/brendan-duncan/webgpu_inspector"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/brendan-duncan/wgsl_reflect/issues"
14 | },
15 | "sideEffects": false,
16 | "scripts": {
17 | "build": "rollup -c rollup.config.js",
18 | "watch": "npm run watch:js",
19 | "watch:js": "rollup -c rollup.config.js --watch"
20 | },
21 | "keywords": [
22 | "webgpu_inspector",
23 | "webgpu",
24 | "debugger",
25 | "javascript",
26 | "html5"
27 | ],
28 | "dependencies": {
29 | "@codemirror/autocomplete": "^6.0.0",
30 | "@codemirror/commands": "^6.0.0",
31 | "@codemirror/language": "^6.0.0",
32 | "@codemirror/lint": "^6.0.0",
33 | "@codemirror/search": "^6.0.0",
34 | "@codemirror/state": "^6.0.0",
35 | "@codemirror/view": "^6.0.0",
36 | "codemirror": "^6.0.1",
37 | "thememirror": "^2.0.1",
38 | "webgpu_recorder": "brendan-duncan/webgpu_recorder",
39 | "wgsl_reflect": "brendan-duncan/wgsl_reflect"
40 | },
41 | "devDependencies": {
42 | "@rollup/plugin-node-resolve": "^15.2.3",
43 | "rollup": "^2.79.1",
44 | "rollup-plugin-copy": "^3.5.0",
45 | "@rollup/plugin-terser": "^0.4.4",
46 | "source-map": "^0.7.4"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/reflection_format.js:
--------------------------------------------------------------------------------
1 | function getTypeName(type) {
2 | if (type.isArray) {
3 | return `array<${getTypeName(type.format)}>`;
4 | }
5 | if (type.isTemplate) {
6 | return `${type.name}<${getTypeName(type.format)}>`;
7 | }
8 | return type.name;
9 | }
10 |
11 | function getNestedStructs(type) {
12 | if (type.isStruct) {
13 | const nested = [];
14 | for (const member of type.members) {
15 | const structs = getNestedStructs(member.type);
16 | nested.push(...structs);
17 | }
18 | nested.push(type);
19 | return nested;
20 | } else if (type.format !== undefined) {
21 | return getNestedStructs(type.format);
22 | }
23 | return [];
24 | }
25 |
26 | function _getStructFormat(struct) {
27 | if (!struct?.members) {
28 | return "";
29 | }
30 | let format = `struct ${struct.name} {\n`;
31 | for (const member of struct.members) {
32 | format += ` ${member.name}: ${getTypeName(member.type)},\n`;
33 | }
34 | format += "}\n";
35 | return format;
36 | }
37 |
38 | export function getFormatFromReflection(type) {
39 | if (type.isArray) {
40 | return `array<${getFormatFromReflection(type.format)}>`;
41 | }
42 |
43 | if (type.isStruct) {
44 | const structs = getNestedStructs(type);
45 | let format = "";
46 | for (const s of structs) {
47 | format += _getStructFormat(s);
48 | }
49 | return format;
50 | }
51 |
52 | if (type.isTemplate) {
53 | return `${type.name}<${getFormatFromReflection(type.format)}>`;
54 | }
55 |
56 | return type.name;
57 | }
58 |
--------------------------------------------------------------------------------
/extensions/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WebGPU Inspector",
3 | "short_name": "webgpu_inspector",
4 | "version": "0.18.0",
5 | "manifest_version": 3,
6 | "description": "WebGPU Inspector Debugging Tools",
7 | "author": "Brendan Duncan",
8 | "minimum_chrome_version": "116",
9 | "icons": {
10 | "19": "res/webgpu_inspector_on-19.png",
11 | "38": "res/webgpu_inspector_on-38.png"
12 | },
13 | "background": {
14 | "service_worker": "background.js"
15 | },
16 | "devtools_page": "webgpu_inspector_devtools.html",
17 | "content_scripts": [
18 | {
19 | "js": ["content_script.js"],
20 | "matches": ["http://*/*",
21 | "https://*/*",
22 | "file://*/*"],
23 | "run_at" : "document_start",
24 | "all_frames" : true
25 | },
26 | {
27 | "js": ["webgpu_inspector_loader.js"],
28 | "matches": ["http://*/*",
29 | "https://*/*",
30 | "file://*/*"],
31 | "run_at" : "document_start",
32 | "all_frames" : true,
33 | "world": "MAIN"
34 | },
35 | {
36 | "js": ["webgpu_recorder_loader.js"],
37 | "matches": ["http://*/*",
38 | "https://*/*",
39 | "file://*/*"],
40 | "run_at" : "document_start",
41 | "all_frames" : true,
42 | "world": "MAIN"
43 | }
44 | ],
45 | "web_accessible_resources": [
46 | {
47 | "resources": [
48 | "webgpu_recorder_loader.js",
49 | "webgpu_recorder.js",
50 | "webgpu_inspector_loader.js"
51 | ],
52 | "matches": ["http://*/*",
53 | "https://*/*",
54 | "file://*/*"]
55 | }
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/src/extension/chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WebGPU Inspector",
3 | "short_name": "webgpu_inspector",
4 | "version": "0.18.0",
5 | "manifest_version": 3,
6 | "description": "WebGPU Inspector Debugging Tools",
7 | "author": "Brendan Duncan",
8 | "minimum_chrome_version": "116",
9 | "icons": {
10 | "19": "res/webgpu_inspector_on-19.png",
11 | "38": "res/webgpu_inspector_on-38.png"
12 | },
13 | "background": {
14 | "service_worker": "background.js"
15 | },
16 | "devtools_page": "webgpu_inspector_devtools.html",
17 | "content_scripts": [
18 | {
19 | "js": ["content_script.js"],
20 | "matches": ["http://*/*",
21 | "https://*/*",
22 | "file://*/*"],
23 | "run_at" : "document_start",
24 | "all_frames" : true
25 | },
26 | {
27 | "js": ["webgpu_inspector_loader.js"],
28 | "matches": ["http://*/*",
29 | "https://*/*",
30 | "file://*/*"],
31 | "run_at" : "document_start",
32 | "all_frames" : true,
33 | "world": "MAIN"
34 | },
35 | {
36 | "js": ["webgpu_recorder_loader.js"],
37 | "matches": ["http://*/*",
38 | "https://*/*",
39 | "file://*/*"],
40 | "run_at" : "document_start",
41 | "all_frames" : true,
42 | "world": "MAIN"
43 | }
44 | ],
45 | "web_accessible_resources": [
46 | {
47 | "resources": [
48 | "webgpu_recorder_loader.js",
49 | "webgpu_recorder.js",
50 | "webgpu_inspector_loader.js"
51 | ],
52 | "matches": ["http://*/*",
53 | "https://*/*",
54 | "file://*/*"]
55 | }
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/src/devtools/gpu_objects/gpu_object.js:
--------------------------------------------------------------------------------
1 | import { StacktraceCache } from "../../utils/stacktrace.js";
2 |
3 | export class GPUObject {
4 | constructor(id, descriptor, stacktrace) {
5 | this.id = id;
6 | this.label = descriptor?.label ?? "";
7 | this._stacktrace = StacktraceCache.setStacktrace(stacktrace ?? "");
8 | this._deletionTime = 0;
9 | this._referenceCount = 1;
10 | this.dependencies = [];
11 | }
12 |
13 | get name() {
14 | return this.label || this.constructor.className;
15 | }
16 |
17 | get stacktrace() {
18 | return StacktraceCache.getStacktrace(this._stacktrace);
19 | }
20 |
21 | get isDeleted() {
22 | return this._deletionTime > 0;
23 | }
24 |
25 | get idName() {
26 | return this.id < 0 ? "CANVAS" : this.id;
27 | }
28 |
29 | get referenceCount() {
30 | return this._referenceCount;
31 | }
32 |
33 | addDependency(dependency) {
34 | if (dependency) {
35 | this.dependencies.push(dependency);
36 | }
37 | }
38 |
39 | incrementDepenencyReferenceCount() {
40 | for (const dependency of this.dependencies) {
41 | dependency._referenceCount++;
42 | dependency.incrementDepenencyReferenceCount();
43 | }
44 | }
45 |
46 | incrementReferenceCount() {
47 | this._referenceCount++;
48 | }
49 |
50 | decrementReferenceCount(deleteCallback) {
51 | this._referenceCount--;
52 | if (this._referenceCount <= 0) {
53 | if (deleteCallback) {
54 | deleteCallback(this);
55 | }
56 | }
57 | for (const dependency of this.dependencies) {
58 | dependency.decrementReferenceCount(deleteCallback);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/utils/actions.js:
--------------------------------------------------------------------------------
1 | export const Actions = {
2 | CaptureBufferData: "webgpu_inspect_capture_buffer_data",
3 | CaptureBuffers: "webgpu_inspect_capture_buffers",
4 | DeleteObjects: "webgpu_inspect_delete_objects",
5 | ValidationError: "webgpu_inspect_validation_error",
6 | MemoryLeakWarning: "webgpu_inspect_memory_leak_warning",
7 | DeltaTime: "webgpu_inspect_delta_time",
8 | CaptureFrameResults: "webgpu_inspect_capture_frame_results",
9 | CaptureFrameCommands: "webgpu_inspect_capture_frame_commands",
10 | ObjectSetLabel: "webgpu_inspect_object_set_label",
11 | AddObject: "webgpu_inspect_add_object",
12 | ResolveAsyncObject: "webgpu_inspect_resolve_async_object",
13 | DeleteObject: "webgpu_inspect_delete_object",
14 | CaptureTextureFrames: "webgpu_inspect_capture_texture_frames",
15 | CaptureTextureData: "webgpu_inspect_capture_texture_data",
16 | CaptureBufferData: "webgpu_inspect_capture_buffer_data",
17 | WriteBuffer: "wrebgpu_inspect_write_buffer",
18 |
19 | Recording: "webgpu_record_recording",
20 | RecordingCommand: "webgpu_record_command",
21 | RecordingDataCount: "webgpu_record_data_count",
22 | RecordingData: "webgpu_record_data",
23 |
24 | // Connection handshake actions
25 | PageReady: "webgpu_inspect_page_ready",
26 | PanelReady: "webgpu_inspect_panel_ready",
27 | ConnectionAck: "webgpu_inspect_connection_ack"
28 | };
29 |
30 | Actions.values = new Set(Object.values(Actions));
31 |
32 | export const PanelActions = {
33 | RequestTexture: "webgpu_inspect_request_texture",
34 | CompileShader: "webgpu_inspect_compile_shader",
35 | RevertShader: "webgpu_inspect_revert_shader",
36 | Capture: "webgpu_inspector_capture",
37 | InitializeInspector: "webgpu_initialize_inspector",
38 | InitializeRecorder: "webgpu_initialize_recorder"
39 | };
40 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "1024x1024",
5 | "idiom" : "universal",
6 | "filename" : "universal-icon-1024@1x.png",
7 | "platform" : "ios"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "mac-icon-16@1x.png",
13 | "scale" : "1x"
14 | },
15 | {
16 | "size" : "16x16",
17 | "idiom" : "mac",
18 | "filename" : "mac-icon-16@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "mac-icon-32@1x.png",
25 | "scale" : "1x"
26 | },
27 | {
28 | "size" : "32x32",
29 | "idiom" : "mac",
30 | "filename" : "mac-icon-32@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "mac-icon-128@1x.png",
37 | "scale" : "1x"
38 | },
39 | {
40 | "size" : "128x128",
41 | "idiom" : "mac",
42 | "filename" : "mac-icon-128@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "mac-icon-256@1x.png",
49 | "scale" : "1x"
50 | },
51 | {
52 | "size" : "256x256",
53 | "idiom" : "mac",
54 | "filename" : "mac-icon-256@2x.png",
55 | "scale" : "2x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "mac-icon-512@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "512x512",
65 | "idiom" : "mac",
66 | "filename" : "mac-icon-512@2x.png",
67 | "scale" : "2x"
68 | }
69 | ],
70 | "info" : {
71 | "version" : 1,
72 | "author" : "xcode"
73 | }
74 | }
--------------------------------------------------------------------------------
/src/devtools/widget/collapse_button.js:
--------------------------------------------------------------------------------
1 | import { Span } from "./span.js";
2 |
3 | export class CollapseButton extends Span {
4 | constructor(state, onChange, parent, options) {
5 | super(parent, options);
6 | this.onChange = onChange;
7 |
8 | this.classList.add("collapse-button", state ? "collapse-button-open" : "collapse-button-closed");
9 | this.element.innerHTML = state ? "▼" : "►";
10 | this.element.dataset["value"] = state ? "open" : "closed";
11 |
12 | this.addEventListener("click", onClick);
13 | const self = this;
14 | function onClick(e) {
15 | self.value = this.dataset["value"] === "open" ? false : true;
16 | if (self.stopPropagation) {
17 | e.stopPropagation;
18 | }
19 | }
20 | }
21 |
22 | setEmpty(v) {
23 | if (v) {
24 | this.classList.add("empty");
25 | } else {
26 | this.classList.remove("empty");
27 | }
28 | }
29 |
30 | expand() {
31 | this.value = true;
32 | }
33 |
34 | collapse() {
35 | this.value = false;
36 | }
37 |
38 | set value(v) {
39 | if (this.dataset["value"] == (v ? "open" : "closed")) {
40 | return;
41 | }
42 |
43 | if (!v) {
44 | this.dataset["value"] = "closed";
45 | this.element.innerHTML = "►";
46 | this.classList.remove("collapse-button-open");
47 | this.classList.add("collapse-button-closed");
48 | } else {
49 | this.dataset["value"] = "open";
50 | this.element.innerHTML = "▼";
51 | this.classList.add("collapse-button-open");
52 | this.classList.remove("collapse-button-closed");
53 | }
54 |
55 | if (this.onChange) {
56 | this.onChange(this.dataset["value"]);
57 | }
58 | }
59 |
60 | get value() {
61 | return this.dataset["value"];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/docs/record.md:
--------------------------------------------------------------------------------
1 | [Overview](../README.md) . [Inspect](inspect.md) . [Capture](capture.md)
2 |
3 | # Record
4 |
5 | Records all rendering commands and associated data, generating a self-contained HTML file with Javascript that recreates the render.
6 |
7 | In order to be able to recreate the render, it needs to record all commands and data from the start of the app. This differs from the Capture tool, which can record any frame for inspecting, but doesn't have enough information to re-create a render.
8 |
9 |
10 |
11 |
12 |
13 | ## Options
14 |
15 | **Frames**: The number of frames to record. The Recorder uses requestAnimationFrame to determine what a frame is. It currently doesn't work with apps that don't use requestAnimationFrame.
16 |
17 | **Download**: If checked, the Recorder will generate an html file with self contained javascript code to re-create the render. All buffer and texture data is embedded into the Javasript.
18 |
19 | **Name**: The filename to use for the downloaded html, as \.html.
20 |
21 | ## Downloaded HTML Recording File
22 |
23 | IF Download option is checked, the Recorder will download an HTML file with embedded javascript and resource data. This recreates rendered frame(s) without any engine code, and is useful for submitting isolated bug reproductions, and render debugging. You can open the recording HTML in a browser, edit the Javascript, and diagnose problems or experiment with the rendering code outside of your engine.
24 |
25 | ## Recording Command List
26 |
27 | The Recorder will also present the captured commands for each frame in the UI, and the resulting render. Selecting a command from the panel will execute all of the recorded commands up to the selected command. This lets you see the render at any stage of the frame.
28 |
--------------------------------------------------------------------------------
/src/devtools/widget/text_area.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 | import { Signal } from '../../utils/signal.js';
3 |
4 | export class TextArea extends Widget {
5 | constructor(parent, options) {
6 | super('textArea', parent, options);
7 | this.element.spellcheck = false;
8 | this.classList.add('text-area');
9 |
10 | this.onChange = new Signal();
11 | this.onEdit = new Signal();
12 |
13 | const self = this;
14 | this.element.addEventListener('change', function () {
15 | let v = self.value;
16 | self.onChange.emit(v);
17 | if (self._onChange) {
18 | self._onChange(v);
19 | }
20 | });
21 |
22 | this.element.addEventListener('input', function () {
23 | let v = self.value;
24 | self.onEdit.emit(v);
25 | if (self._onEdit) {
26 | self._onEdit(v);
27 | }
28 | });
29 | }
30 |
31 | configure(options) {
32 | if (!options) {
33 | return;
34 | }
35 | super.configure(options);
36 |
37 | if (options.value) {
38 | this.value = options.value;
39 | }
40 |
41 | if (options.placeholder) {
42 | this.placeholder = options.placeholder;
43 | }
44 |
45 | if (options.readOnly !== undefined) {
46 | this.readOnly = options.readOnly;
47 | }
48 |
49 | if (options.onChange !== undefined) {
50 | this._onChange = options.onChange;
51 | }
52 |
53 | if (options.onEdit !== undefined) {
54 | this._onEdit = options.onEdit;
55 | }
56 | }
57 |
58 | get value() {
59 | return this._element.value;
60 | }
61 |
62 | set value(t) {
63 | this._element.value = t;
64 | }
65 |
66 | get placeholder() {
67 | return this._element.placeholder;
68 | }
69 |
70 | set placeholder(v) {
71 | this._element.placeholder = v;
72 | }
73 |
74 | get readOnly() {
75 | return this._element.readOnly;
76 | }
77 |
78 | set readOnly(v) {
79 | this._element.readOnly = v;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/devtools/widget/collapsable.js:
--------------------------------------------------------------------------------
1 | import { Div } from './div.js';
2 | import { Span } from './span.js';
3 | import { Widget } from './widget.js';
4 | import { Signal } from '../../utils/signal.js';
5 |
6 | /**
7 | * A collapsable widget with a header and a body.
8 | */
9 | export class Collapsable extends Widget {
10 | constructor(parent, options) {
11 | super('div', parent, options);
12 |
13 | const collapsed = options.collapsed ?? false;
14 |
15 | this.titleBar = new Div(this, { class: "title_bar" });
16 | this.collapseButton = new Span(this.titleBar, { class: "collapsable_button", text: collapsed ? "+" : "-", style: "margin-right: 10px;" })
17 | this.label = new Span(this.titleBar, { class: "object_type", text: options?.label ?? "" });
18 | this.onExpanded = new Signal();
19 | this.onCollapsed = new Signal();
20 |
21 | this.body = new Div(this, { class: ["collapsable_body"] });
22 | if (collapsed) {
23 | this.body.element.className = "collapsable_body collapsed";
24 | }
25 |
26 | const self = this;
27 |
28 | this.titleBar.element.onclick = function() {
29 | if (self.collapseButton.text == "-") {
30 | self.collapseButton.text = "+";
31 | self.body.element.className = "collapsable_body collapsed";
32 | self.onCollapsed.emit();
33 | } else {
34 | self.collapseButton.text = "-";
35 | self.body.element.className = "collapsable_body";
36 | self.onExpanded.emit();
37 | }
38 | };
39 | }
40 |
41 | expand() {
42 | this.collapsed = false;
43 | }
44 |
45 | get collapsed() {
46 | return this.collapseButton.text == "+";
47 | }
48 |
49 | set collapsed(value) {
50 | if (this.collapsed == value) {
51 | return;
52 | }
53 | if (value) {
54 | this.collapseButton.text = "+";
55 | this.body.element.className = "collapsable_body collapsed";
56 | this.onCollapsed.emit();
57 | } else {
58 | this.collapseButton.text = "-";
59 | this.body.element.className = "collapsable_body";
60 | this.onExpanded.emit();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/devtools/widget/window.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 | import { Signal } from '../../utils/signal.js';
3 |
4 | /**
5 | * A window widget fills the entire browser window. It will resize with the
6 | * browser. A Window can have an Overlay, which is a [Widget] that will be
7 | * resized to fill the entire window, and can be used to create full screen
8 | * modal editors.
9 | */
10 | export class Window extends Widget {
11 | constructor(options) {
12 | super(document.body, options);
13 | this._overlay = null;
14 | this._onResizeCB = this.windowResized.bind(this);
15 | window.addEventListener('resize', this._onResizeCB);
16 | this.onWindowResized = new Signal();
17 | Widget.window = this;
18 | }
19 |
20 | windowResized() {
21 | this._onResize(window.innerWidth, window.innerHeight);
22 | }
23 |
24 | /**
25 | * @property {number} width The width of the widget.
26 | */
27 | get width() {
28 | return window.innerWidth;
29 | }
30 |
31 | /**
32 | * @property {number} heihgt The height of the widget.
33 | */
34 | get height() {
35 | return window.innerHeight;
36 | }
37 |
38 | /**
39 | * @property {Widget?} overlay The active overlay widget, which covers the entire window
40 | * temporarily.
41 | */
42 | get overlay() {
43 | return this._overlay;
44 | }
45 |
46 | set overlay(v) {
47 | if (this._overlay === v) {
48 | return;
49 | }
50 |
51 | if (this._overlay !== null) {
52 | this._element.removeChild(this._overlay._element);
53 | }
54 |
55 | this._overlay = v;
56 |
57 | if (this._overlay) {
58 | this._element.appendChild(this._overlay._element);
59 | this._overlay.setPosition(0, 0, 'absolute');
60 | this._overlay.resize(window.innerWidth, window.innerHeight);
61 | }
62 | }
63 |
64 | /**
65 | * The widget has been resized.
66 | * @param {number} width
67 | * @param {number} height
68 | */
69 | _onResize(width, height) {
70 | this.onWindowResized.emit();
71 | this.repaint();
72 | if (this._element) {
73 | if (this._overlay) {
74 | this._overlay.resize(width, height);
75 | }
76 | }
77 | this.onResize();
78 | }
79 | }
80 |
81 | Window.isWindow = true;
82 | Window._idPrefix = 'WINDOW';
83 |
--------------------------------------------------------------------------------
/test/atomic_array_buffer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/test/shaderError.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/test/async_render_pipeline.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/test/pointer_lock.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pointer Lock Example
5 |
18 |
19 |
20 |
21 | Click to Get Pointer Lock
22 |
23 |
24 | Click here to lock the pointer!
25 |
26 |
27 | Status: Pointer is unlocked.
28 |
29 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/extensions/safari/iOS (App)/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
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 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/extensions/safari/iOS (App)/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
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 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/canvas_texture_error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/extensions/safari/Shared (App)/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Shared (App)
4 | //
5 | // Created by Brendan Duncan on 2/12/24.
6 | //
7 |
8 | import WebKit
9 |
10 | #if os(iOS)
11 | import UIKit
12 | typealias PlatformViewController = UIViewController
13 | #elseif os(macOS)
14 | import Cocoa
15 | import SafariServices
16 | typealias PlatformViewController = NSViewController
17 | #endif
18 |
19 | let extensionBundleIdentifier = "com.yourCompany.WebGPU-Inspector.Extension"
20 |
21 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler {
22 |
23 | @IBOutlet var webView: WKWebView!
24 |
25 | override func viewDidLoad() {
26 | super.viewDidLoad()
27 |
28 | self.webView.navigationDelegate = self
29 |
30 | #if os(iOS)
31 | self.webView.scrollView.isScrollEnabled = false
32 | #endif
33 |
34 | self.webView.configuration.userContentController.add(self, name: "controller")
35 |
36 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
37 | }
38 |
39 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
40 | #if os(iOS)
41 | webView.evaluateJavaScript("show('ios')")
42 | #elseif os(macOS)
43 | webView.evaluateJavaScript("show('mac')")
44 |
45 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
46 | guard let state = state, error == nil else {
47 | // Insert code to inform the user that something went wrong.
48 | return
49 | }
50 |
51 | DispatchQueue.main.async {
52 | if #available(macOS 13, *) {
53 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)")
54 | } else {
55 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)")
56 | }
57 | }
58 | }
59 | #endif
60 | }
61 |
62 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
63 | #if os(macOS)
64 | if (message.body as! String != "open-preferences") {
65 | return
66 | }
67 |
68 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
69 | guard error == nil else {
70 | // Insert code to inform the user that something went wrong.
71 | return
72 | }
73 |
74 | DispatchQueue.main.async {
75 | NSApp.terminate(self)
76 | }
77 | }
78 | #endif
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/devtools/widget/tree_item.js:
--------------------------------------------------------------------------------
1 | import { Signal } from '../../utils/signal.js';
2 | import { Widget } from "./widget.js";
3 | import { Div } from "./div.js";
4 | import { Span } from "./span.js";
5 |
6 | export class TreeItem extends Widget {
7 | constructor(parent, options) {
8 | if (parent && parent.constructor === Object) {
9 | options = parent;
10 | parent = null;
11 | }
12 |
13 | super("li", parent, options);
14 |
15 | options = options || {};
16 | this.data = options.data || {};
17 | this.itemId = this.data.id || "";
18 | this.level = options.level || 0;
19 | this.collapseButton = null;
20 | this.onClick = new Signal();
21 | this.data.item = this;
22 |
23 | this.titleElement = new Div(this, {class: "tree-item-title"});
24 | this.preContent = new Span(this.titleElement, {class:"precontent"});
25 | this.indent = new Span(this.titleElement, {class:"indent"});
26 | this.collapseButtonArea = new Span(this.titleElement, {class:"tree-collapse"});
27 | this.icon = new Span(this.titleElement, {class:"icon"});
28 | this.content = new Span(this.titleElement, {class:"content"});
29 | this.postContent = new Span(this.titleElement, {class:"postcontent"});
30 |
31 | if (this.data.className) {
32 | this.titleElement.classList.add(this.data.className);
33 | }
34 |
35 | if (this.data.icon) {
36 | if (this.data.icon.constructor === Function) {
37 | this.data.icon(this.icon, this.data);
38 | } else {
39 | this.icon.element.innerHTML = this.data.icon;
40 | }
41 | }
42 |
43 | if (this.data.content) {
44 | if (this.data.content.constructor === Function) {
45 | this.data.content(this.content, this.data);
46 | } else if (this.data.content instanceof Span) {
47 | this.content.appendChild(this.data.content);
48 | } else {
49 | this.content.text = this.data.content || this.data.id || "";
50 | }
51 | } else {
52 | this.content.element.text = this.data.id || "";
53 | }
54 |
55 | if (this.data.precontent) {
56 | if (this.data.precontent.constructor === Function) {
57 | this.data.precontent(this.preContent, this.data);
58 | } else {
59 | this.preContent.element.innerHTML = this.data.precontent;
60 | }
61 | }
62 |
63 | if (this.data.postcontent) {
64 | if (this.data.postcontent.constructor === Function) {
65 | this.data.postcontent(this.postContent, this.data);
66 | } else {
67 | this.postContent.element.innerHTML = this.data.postcontent;
68 | }
69 | }
70 |
71 | if (this.data.visible === false) {
72 | this.style.display = "none";
73 | }
74 | }
75 | }
76 |
77 | TreeItem.isTreeItem = true;
78 |
--------------------------------------------------------------------------------
/src/devtools/widget/dialog.js:
--------------------------------------------------------------------------------
1 | import { Div } from './div.js';
2 | import { Button } from './button.js';
3 | import { Span } from './span.js';
4 | import { Window } from './window.js';
5 |
6 | /**
7 | * Base class for modal windows. A modal window takes over the main window while it is active.
8 | */
9 | export class Dialog extends Div {
10 | constructor(options) {
11 | const title = options?.title ?? 'Dialog';
12 |
13 | super({ class: options?.windowClass ?? 'dialog', title: title });
14 |
15 | if (options?.width) {
16 | this.style.width = `${options.width}px`;
17 | }
18 |
19 | const background =
20 | options?.parent ?? new Div({ class: 'dialog-background' });
21 |
22 | this.parent = background;
23 |
24 | const header = new Div(this, { class: 'dialog-header', title });
25 |
26 | if (!options?.noCloseButton) {
27 | const closeButton = new Button(header, {
28 | class: 'dialog-close-button',
29 | text: 'x',
30 | title: 'Close',
31 | });
32 |
33 | closeButton.addEventListener('mouseup', function (e) {
34 | if (e.target === closeButton.element) {
35 | Window.window.overlay = null;
36 | }
37 | });
38 | }
39 |
40 | this.title = new Span(header, { class: 'dialog-title', text: title });
41 |
42 | this.body = new Div(this, { class: 'dialog-body' });
43 |
44 | if (options?.body) {
45 | this.body.appendChild(options.body);
46 | }
47 |
48 | if (!options?.parent) {
49 | Window.window.overlay = background;
50 | }
51 |
52 | if (options?.draggable) {
53 | let isDragging = false;
54 | const rect = this.getBoundingClientRect();
55 | let x = rect ? rect.left : 0;
56 | let y = rect ? rect.top : 0;
57 | this.style.position = 'absolute';
58 | this.style.left = `${x}px`;
59 | this.style.top = `${y}px`;
60 |
61 | let prevX = 0;
62 | let prevY = 0;
63 | header.addEventListener('mousedown', function (e) {
64 | isDragging = true;
65 | prevX = e.clientX;
66 | prevY = e.clientY;
67 | });
68 |
69 | const self = this;
70 | document.addEventListener('mousemove', function (e) {
71 | if (!isDragging) {
72 | return;
73 | }
74 | const dx = e.clientX - prevX;
75 | const dy = e.clientY - prevY;
76 | x += dx;
77 | y += dy;
78 | prevX = e.clientX;
79 | prevY = e.clientY;
80 | self.style.left = `${x}px`;
81 | self.style.top = `${y}px`;
82 | });
83 |
84 | document.addEventListener('mouseup', function () {
85 | isDragging = false;
86 | });
87 | }
88 | }
89 |
90 | close() {
91 | Window.window.overlay = null;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/devtools/widget/input.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 | import { Label } from './label.js';
3 | import { Signal } from '../../utils/signal.js';
4 |
5 | export class Input extends Widget {
6 | constructor(parent, options) {
7 | super('input', parent, options);
8 | this.onChange = new Signal();
9 | this.onEdit = new Signal();
10 | const self = this;
11 |
12 | this.element.addEventListener('change', function () {
13 | let v = self.type === 'checkbox' ? self.checked : self.value;
14 | self.onChange.emit(v);
15 | if (self._onChange) {
16 | self._onChange(v);
17 | }
18 | });
19 |
20 | this.element.addEventListener('input', function () {
21 | let v = self.type === 'checkbox' ? self.checked : self.value;
22 | self.onEdit.emit(v);
23 | if (self._onEdit) {
24 | self._onEdit(v);
25 | }
26 | });
27 | }
28 |
29 | configure(options) {
30 | if (!options) {
31 | return;
32 | }
33 | super.configure(options);
34 |
35 | if (options.type !== undefined) {
36 | this.type = options.type;
37 | }
38 |
39 | if (options.checked !== undefined) {
40 | this.checked = options.checked;
41 | }
42 |
43 | if (options.value !== undefined) {
44 | this.value = options.value;
45 | }
46 |
47 | if (options.label !== undefined) {
48 | if (options.label.constructor === String) {
49 | this.label = new Label(options.label, this.parent, {
50 | for: this,
51 | });
52 | } else {
53 | this.label = options.label;
54 | this.label.for = this.id;
55 | }
56 | }
57 |
58 | if (options.readOnly !== undefined) {
59 | this.readOnly = options.readOnly;
60 | }
61 |
62 | if (options.onChange !== undefined) {
63 | this._onChange = options.onChange;
64 | }
65 |
66 | if (options.onEdit !== undefined) {
67 | this._onEdit = options.onEdit;
68 | }
69 | }
70 |
71 | get type() {
72 | return this._element.type;
73 | }
74 |
75 | set type(v) {
76 | this._element.type = v;
77 | }
78 |
79 | get checked() {
80 | return this._element.checked;
81 | }
82 |
83 | set checked(v) {
84 | this._element.checked = v;
85 | }
86 |
87 | get indeterminate() {
88 | return this._element.indeterminate;
89 | }
90 |
91 | set indeterminate(v) {
92 | this._element.indeterminate = v;
93 | }
94 |
95 | get value() {
96 | return this._element.value;
97 | }
98 |
99 | set value(v) {
100 | this._element.value = v;
101 | }
102 |
103 | get readOnly() {
104 | return this._element.readOnly;
105 | }
106 |
107 | set readOnly(v) {
108 | this._element.readOnly = v;
109 | }
110 |
111 | focus() {
112 | this._element.focus();
113 | }
114 |
115 | blur() {
116 | this._element.blur();
117 | }
118 |
119 | select() {
120 | this._element.select();
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/extension/background.js:
--------------------------------------------------------------------------------
1 | const connections = new Map();
2 |
3 | // Helper function to get or create tab connection map
4 | function getOrCreateTabConnection(tabId) {
5 | if (!connections.has(tabId)) {
6 | connections.set(tabId, new Map());
7 | }
8 | return connections.get(tabId);
9 | }
10 |
11 | // Helper function to register a port
12 | function registerPort(port, tabId) {
13 | const portMap = getOrCreateTabConnection(tabId);
14 |
15 | // Can be multiple content scripts per tab
16 | // for example if a web page includes iframe.
17 | // So manage ports as an array.
18 | if (!portMap.has(port.name)) {
19 | portMap.set(port.name, []);
20 | }
21 |
22 | const ports = portMap.get(port.name);
23 | if (!ports.includes(port)) {
24 | ports.push(port);
25 |
26 | port.onDisconnect.addListener(() => {
27 | console.log(`[WebGPU Inspector] Port disconnected: ${port.name} (tab ${tabId})`);
28 | if (ports.includes(port)) {
29 | ports.splice(ports.indexOf(port), 1);
30 | }
31 | if (ports.length === 0) {
32 | portMap.delete(port.name);
33 | }
34 | if (portMap.size === 0) {
35 | connections.delete(tabId);
36 | }
37 | });
38 |
39 | console.log(`[WebGPU Inspector] Port registered: ${port.name} (tab ${tabId})`);
40 | }
41 | }
42 |
43 | chrome.runtime.onConnect.addListener((port) => {
44 | console.log(`[WebGPU Inspector] Port connecting: ${port.name}`);
45 |
46 | // Register the port immediately on connect, before any messages
47 | // This is done in the first onMessage handler because we need the tabId
48 | let registered = false;
49 |
50 | port.onMessage.addListener((message, port) => {
51 | // Get tabId from message or port sender
52 | const tabId = message.tabId !== undefined ? message.tabId : (port.sender?.tab?.id ?? 0);
53 |
54 | // Register port on first message if not already registered
55 | if (!registered) {
56 | registerPort(port, tabId);
57 | registered = true;
58 | }
59 |
60 | const portMap = connections.get(tabId);
61 | if (!portMap) {
62 | console.error(`[WebGPU Inspector] No port map found for tab ${tabId}`);
63 | return;
64 | }
65 |
66 | const postMessageToPorts = (ports, message) => {
67 | ports.forEach((p) => {
68 | try {
69 | p.postMessage(message);
70 | } catch (e) {
71 | console.error(`[WebGPU Inspector] Failed to post message to port ${p.name}:`, e);
72 | }
73 | });
74 | };
75 |
76 | // transfer message between panel and contentScripts of the same tab
77 | if (port.name === "webgpu-inspector-panel" && portMap.has("webgpu-inspector-page")) {
78 | postMessageToPorts(portMap.get("webgpu-inspector-page"), message);
79 | }
80 |
81 | if (port.name === "webgpu-inspector-page" && portMap.has("webgpu-inspector-panel")) {
82 | postMessageToPorts(portMap.get("webgpu-inspector-panel"), message);
83 | }
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/src/devtools/widget/split_bar.js:
--------------------------------------------------------------------------------
1 | import { Div } from './div.js';
2 | import { Widget } from './widget.js';
3 |
4 | /**
5 | * A draggable bar to adjust sizes of elements in a splitter.
6 | */
7 | export class SplitBar extends Div {
8 | constructor(orientation, parent, options) {
9 | super(parent, options);
10 |
11 | this.orientation = orientation;
12 | this._mousePressed = false;
13 | this._mouseX = 0;
14 | this._mouseY = 0;
15 | this._prevWidget = null;
16 | this._nextWidget = null;
17 | this._splitIndex = 0;
18 |
19 | this._element.classList.add('splitbar');
20 |
21 | if (this.orientation == SplitBar.Horizontal) {
22 | this._element.style.height = `${SplitBar.size}px`;
23 | this._element.style.width = '100%';
24 | this._element.style.cursor = 'n-resize';
25 | } else {
26 | this._element.style.width = `${SplitBar.size}px`;
27 | this._element.style.height = '100%';
28 | this._element.style.cursor = 'e-resize';
29 | }
30 |
31 | this.enablePointerEvents();
32 | }
33 |
34 | pointerDownEvent(e) {
35 | this._mousePressed = true;
36 | this._mouseX = e.clientX;
37 | this._mouseY = e.clientY;
38 | for (let i = 0; i < this.parent.children.length; ++i) {
39 | let w = this.parent.children[i];
40 | if (w === this) {
41 | this._splitIndex = i;
42 | this._prevWidget = this.parent.children[i - 1];
43 | this._nextWidget = this.parent.children[i + 1];
44 | break;
45 | }
46 | }
47 | if (this._prevWidget) {
48 | this._prevWidget._startResize();
49 | }
50 | if (this._nextWidget) {
51 | this._nextWidget._startResize();
52 | }
53 |
54 | this.element.setPointerCapture(e.pointerId);
55 | }
56 |
57 | pointerMoveEvent(e) {
58 | if (!this._mousePressed) {
59 | return;
60 | }
61 |
62 | if (this.orientation === SplitBar.Horizontal) {
63 | const dy = e.clientY - this._mouseY;
64 | if (dy != 0) {
65 | if (this.parent.mode === 0) {
66 | const pct = dy / this.parent.height;
67 | this.parent.position += pct;
68 | } else {
69 | this.parent.position += dy;
70 | }
71 | }
72 | } else {
73 | const dx = e.clientX - this._mouseX;
74 | if (dx != 0) {
75 | if (this.parent.mode === 0) {
76 | const pct = dx / this.parent.width;
77 | this.parent.position += pct;
78 | } else {
79 | this.parent.position += dx;
80 | }
81 | }
82 | }
83 |
84 | this._mouseX = e.clientX;
85 | this._mouseY = e.clientY;
86 |
87 | return false;
88 | }
89 |
90 | pointerUpEvent() {
91 | this._prevWidget = null;
92 | this._nextWidget = null;
93 | this._mousePressed = false;
94 | Widget.disablePaintingOnResize = false;
95 | for (let w of this.parent.children) {
96 | w.repaint(true);
97 | }
98 | return false;
99 | }
100 | }
101 |
102 | SplitBar.isSplitBar = true;
103 | SplitBar.Horizontal = 0;
104 | SplitBar.Vertical = 1;
105 | SplitBar.size = 6;
106 |
--------------------------------------------------------------------------------
/test/compute_storage_texture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/test/triangle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/test/slow_triangle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/src/devtools/stacktrace_viewer.js:
--------------------------------------------------------------------------------
1 | import { Div } from "./widget/div.js";
2 | import { Widget } from "./widget/widget.js";
3 |
4 | export class StacktraceViewer extends Div {
5 | constructor(panel, parent, object, stacktrace) {
6 | super(parent);
7 |
8 | const patterns = {
9 | // JavaScript: functionName (http://url:line:column)
10 | jsWithParens: /^(.+?)\s+\((https?:\/\/.+?):(\d+):(\d+)\)$/,
11 |
12 | // JavaScript without function name: http://url:line:column
13 | jsNoFunction: /^(https?:\/\/.+?):(\d+):(\d+)$/,
14 |
15 | // WASM: functionName (http://url:wasm-function[index]:offset)
16 | wasmWithOffset: /^(.+?)\s+\((https?:\/\/.+?):wasm-function\[(\d+)\]:(0x[0-9a-f]+)\)$/,
17 |
18 | // WASM alternative: functionName (http://url:line:column)
19 | wasmWithLine: /^(.+?)\s+\((https?:\/\/.+?\.wasm):(\d+):(\d+)\)$/
20 | };
21 |
22 | const stacktraceGrp = panel._getCollapsableWithState(this, object, "stacktraceCollapsed", "Stacktrace", true);
23 | const stacktraceBody = new Widget("ol", stacktraceGrp.body, { class: "inspector-stacktrace" });
24 | const stacktraceLines = stacktrace.split("\n");
25 | for (const stackLine of stacktraceLines) {
26 | const trimmed = stackLine.trim();
27 |
28 | let filePath = null;
29 | let functionName = null;
30 | let line = null;
31 | let column = null;
32 | let columnStr = null;
33 | let match = null;
34 |
35 | // Try JavaScript with function name and parentheses
36 | if ((match = trimmed.match(patterns.jsWithParens))) {
37 | filePath = match[2];
38 | functionName = match[1];
39 | line = Math.max(parseInt(match[3], 10) - 1, 0);
40 | columnStr = match[4];
41 | column = parseInt(columnStr, 10) - 1;
42 | }
43 | // Try WASM with offset
44 | else if ((match = trimmed.match(patterns.wasmWithOffset))) {
45 | filePath = match[2];
46 | functionName = match[1];
47 | columnStr = match[4];
48 | column = parseInt(columnStr, 16); // wasm offset
49 | }
50 | // Try WASM with line number
51 | else if ((match = trimmed.match(patterns.wasmWithLine))) {
52 | filePath = match[2];
53 | functionName = match[1];
54 | line = Math.max(parseInt(match[3], 10) - 1, 0);
55 | columnStr = match[4];
56 | column = parseInt(columnStr, 10) - 1;
57 | }
58 | // Try JavaScript without function name
59 | else if ((match = trimmed.match(patterns.jsNoFunction))) {
60 | filePath = match[1];
61 | line = Math.max(parseInt(match[2], 10) - 1, 0);
62 | columnStr = match[3];
63 | column = parseInt(columnStr, 10) - 1;
64 | }
65 |
66 | if (filePath === null) {
67 | new Widget("li", stacktraceBody, { text: stackLine });
68 | return;
69 | }
70 |
71 | const lineDiv = new Widget("li", stacktraceBody);
72 |
73 | const title = `${functionName || ""} (${filePath}${line !== null ? `:${line}` : ""}${columnStr !== null ? `:${columnStr}` : ""})`;
74 |
75 | const link = new Widget("a", lineDiv, { text: stackLine, title: title });
76 | link.addEventListener("click", (evt) => {
77 | evt.preventDefault();
78 | if (chrome?.devtools?.panels) {
79 | chrome.devtools.panels.openResource(filePath, line || 0, column);
80 | } else {
81 | window.open(filePath, "_blank");
82 | }
83 | });
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/test/msaa.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/test/client/assets/webgpu_worker.js:
--------------------------------------------------------------------------------
1 | import { foo } from "../../worker_import.js";
2 |
3 | self.addEventListener('message', (ev) => {
4 | switch (ev.data.type) {
5 | case 'init': {
6 | try {
7 | init(ev.data.offscreenCanvas);
8 | } catch (err) {
9 | console.error(`Error while initializing WebGPU in worker process: ${err.message}`);
10 | }
11 | break;
12 | }
13 | }
14 | });
15 |
16 | async function init(canvas) {
17 | if (!navigator.gpu) {
18 | return;
19 | }
20 |
21 | const response = await fetch('triangle.html');
22 | const text = await response.text();
23 | console.log(foo());
24 |
25 | const adapter = await navigator.gpu.requestAdapter();
26 | if (!adapter) {
27 | return;
28 | }
29 | const device = await adapter.requestDevice();
30 |
31 | const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
32 |
33 | const context = canvas.getContext("webgpu");
34 |
35 | context.configure({ device: device, format: presentationFormat });
36 |
37 | const depthTexture = device.createTexture({
38 | size: [canvas.width, canvas.height],
39 | sampleCount: 1,
40 | format: "depth24plus",
41 | usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
42 | });
43 |
44 | const depthTextureView = depthTexture.createView();
45 |
46 | const redTriangleShader = device.createShaderModule({
47 | code: `
48 | @vertex
49 | fn vertexMain(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 {
50 | var pos = array, 3>(
51 | vec2(0.0, 0.5),
52 | vec2(-0.5, -0.5),
53 | vec2(0.5, -0.5));
54 | return vec4(pos[VertexIndex], 0.5, 1.0);
55 | }
56 |
57 | @fragment
58 | fn fragmentMain() -> @location(0) vec4 {
59 | return vec4(0.0, 1.0, 0.0, 1.0);
60 | }`,
61 | });
62 |
63 | const pipeline = device.createRenderPipeline({
64 | layout: "auto",
65 | vertex: {
66 | module: redTriangleShader,
67 | entryPoint: "vertexMain",
68 | },
69 | fragment: {
70 | module: redTriangleShader,
71 | entryPoint: "fragmentMain",
72 | targets: [{ format: presentationFormat } ]
73 | },
74 | primitive: { topology: "triangle-list" },
75 | depthStencil: {
76 | depthWriteEnabled: true,
77 | depthCompare: 'less',
78 | format: 'depth24plus',
79 | },
80 | });
81 |
82 | function frame() {
83 | const commandEncoder = device.createCommandEncoder();
84 |
85 | const canvasTextureView = context.getCurrentTexture().createView();
86 |
87 | const passEncoder = commandEncoder.beginRenderPass({
88 | colorAttachments: [
89 | {
90 | view: canvasTextureView,
91 | clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
92 | loadOp: "clear",
93 | storeOp: "store",
94 | },
95 | ],
96 | depthStencilAttachment: {
97 | view: depthTextureView,
98 | depthClearValue: 1.0,
99 | depthLoadOp: "clear",
100 | depthStoreOp: "store",
101 | }
102 | });
103 | passEncoder.setPipeline(pipeline);
104 | passEncoder.draw(3);
105 | passEncoder.end();
106 |
107 | device.queue.submit([commandEncoder.finish()]);
108 |
109 | requestAnimationFrame(frame);
110 | }
111 |
112 | requestAnimationFrame(frame);
113 | };
114 |
--------------------------------------------------------------------------------
/src/devtools/widget/tab_handle.js:
--------------------------------------------------------------------------------
1 | import { Div } from "./div.js";
2 | import { Widget } from "./widget.js";
3 |
4 | /**
5 | * The handle widget for a tab panel.
6 | */
7 | export class TabHandle extends Div {
8 | constructor(title, page, parentWidget, parent, options) {
9 | super(parent);
10 |
11 | this.title = title;
12 | this.page = page;
13 | this.parentWidget = parentWidget;
14 |
15 | this.classList.add("tab-handle", "disable-selection");
16 |
17 | this.textElement = new Div(this, {
18 | class: "tab-handle-text",
19 | text: title,
20 | });
21 |
22 | this.draggable = true;
23 |
24 | this.enableMouseEvents();
25 | this.enableDoubleClickEvent();
26 |
27 | this.configure(options);
28 |
29 | this.enableDropEvents();
30 | }
31 |
32 | dragStartEvent() {
33 | TabHandle.DragWidget = this;
34 | }
35 |
36 | dragEndEvent() {
37 | TabHandle.DragWidget = null;
38 | }
39 |
40 | dragOverEvent(e) {
41 | if (!TabHandle.DragWidget) {
42 | return;
43 | }
44 |
45 | if (e.srcElement.classList.contains("tab-handle") &&
46 | this !== TabHandle.DragWidget) {
47 | if (e.layerX < this.width * 0.5) {
48 | e.preventDefault();
49 | this.style.borderRight = "";
50 | this.style.borderLeft = "4px solid #fff";
51 | } else {
52 | e.preventDefault();
53 | this.style.borderLeft = "";
54 | this.style.borderRight = "4px solid #fff";
55 | }
56 | }
57 | }
58 |
59 | dropEvent(e) {
60 | this.style.borderLeft = "";
61 | this.style.borderRight = "";
62 | if (e.srcElement.classList.contains("tab-handle")) {
63 | if (e.layerX < this.width * 0.5) {
64 | console.log("Insert Before");
65 | } else {
66 | console.log("Insert After");
67 | }
68 | }
69 | }
70 |
71 | dragEnterEvent() {
72 | this.style.borderLeft = "";
73 | this.style.borderRight = "";
74 | }
75 |
76 | dragLeaveEvent() {
77 | this.style.borderLeft = "";
78 | this.style.borderRight = "";
79 | }
80 |
81 | configure(options) {
82 | if (!options) {
83 | return;
84 | }
85 | super.configure(options);
86 | if (options.displayCloseButton) {
87 | this.closeButton = new Div(this, {
88 | class: "tab-handle-close-button",
89 | });
90 | this.closeButton.title = "Close Tab";
91 |
92 | // Set the close button text
93 | const closeIcon = "icon-remove-sign";
94 | this.closeButton.element.innerHTML = `x`;
95 | this.closeButton.addEventListener("click", () => {
96 | this.parentWidget.closeTabHandle(this);
97 | });
98 | }
99 | }
100 |
101 | /**
102 | * Is this tab currently active?
103 | */
104 | get isActive() {
105 | return this.classList.contains("tab-handle-selected");
106 | }
107 |
108 | /**
109 | * Set the active state of the tab (does not affect other tabs, which should
110 | * be set as inactive).
111 | */
112 | set isActive(a) {
113 | if (a == this.isActive) {
114 | return;
115 | }
116 |
117 | if (a) {
118 | this.classList.add("tab-handle-selected");
119 | this.page.style.display = "block";
120 | this.style.zIndex = "10";
121 | } else {
122 | this.classList.remove("tab-handle-selected");
123 | this.page.style.display = "none";
124 | this.style.zIndex = "0";
125 | }
126 | }
127 |
128 | mousePressEvent(e) {
129 | this.parentWidget.setHandleActive(this);
130 | }
131 |
132 | doubleClickEvent() {
133 | //this.maximizePanel();
134 | }
135 |
136 | maximizePanel() {
137 | //Widget.window.maximizePanelToggle(this.title, this.page.panel);
138 | }
139 | }
140 |
141 | TabHandle._idPrefix = "TAB";
142 |
--------------------------------------------------------------------------------
/src/devtools/widget/plot.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 | import { Div } from './div.js';
3 |
4 | // Uses a circular buffer to store data for a plot.
5 | export class PlotData {
6 | constructor(name, size) {
7 | this.name = name;
8 | this._size = size;
9 | this.data = new Float32Array(size);
10 | this.index = 0;
11 | this.count = 0;
12 | this.min = 1.0e10;
13 | this.max = -1.0e10;
14 | }
15 |
16 | reset() {
17 | this.index = 0;
18 | this.count = 0;
19 | this.min = 1.0e10;
20 | this.max = -1.0e10;
21 | }
22 |
23 | get size() {
24 | return this._size;
25 | }
26 |
27 | set size(value) {
28 | if (value === this._size) {
29 | return;
30 | }
31 | const oldData = this.data;
32 | this._size = value;
33 | this.data = new Float32Array(value);
34 | for (let i = 0; i < this.count; ++i) {
35 | this.data[i] = oldData[i];
36 | }
37 | }
38 |
39 | add(value) {
40 | this.data[this.index] = value;
41 | this.index = (this.index + 1) % this._size;
42 | this.count = Math.min(this.count + 1, this._size);
43 | this.min = Math.min(this.min, value);
44 | this.max = Math.max(this.max, value);
45 | }
46 |
47 | get(index) {
48 | if (this.count < this._size) {
49 | return this.data[index];
50 | }
51 | return this.data[(this.index + index) % this._size];
52 | }
53 | }
54 |
55 | export class Plot extends Div {
56 | constructor(parent, options) {
57 | options ??= {};
58 | options.class = options.class ? options.class + " plot" : "plot";
59 | super(parent, options);
60 |
61 | this.canvas = new Widget("canvas", this);
62 | this.context = this.canvas.element.getContext("2d");
63 |
64 | this.data = new Map();
65 |
66 | this.suffix = options.suffix ?? "";
67 | this.precision = options.precision ?? 0;
68 |
69 | this.onResize();
70 | this.draw();
71 | }
72 |
73 | reset() {
74 | for (const data of this.data.values()) {
75 | data.reset();
76 | }
77 | }
78 |
79 | onResize() {
80 | if (this.canvas) {
81 | this.canvas.element.width = this.width;
82 | this.canvas.element.height = this.height;
83 | for (const data of this.data.values()) {
84 | data.size = this.width;
85 | }
86 | }
87 | }
88 |
89 | addData(name) {
90 | const data = new PlotData(name, this.width);
91 | this.data.set(name, data);
92 | return data;
93 | }
94 |
95 | getData(name) {
96 | return this.data.get(name);
97 | }
98 |
99 | draw() {
100 | const ctx = this.context;
101 | ctx.fillStyle = "#333";
102 | ctx.fillRect(0, 0, this.width, this.height);
103 |
104 | for (const data of this.data.values()) {
105 | this._drawData(data);
106 | }
107 | }
108 |
109 | _drawData(data) {
110 | const ctx = this.context;
111 | ctx.strokeStyle = "#999";
112 | const h = this.height;
113 | let min = 1.0e10;
114 | let max = -1.0e10;
115 | const count = data.count;
116 | for (let i = 0; i < count; ++i) {
117 | let v = data.get(i);
118 | if (v < min) {
119 | min = v;
120 | }
121 | if (v > max) {
122 | max = v;
123 | }
124 | }
125 |
126 | if (count == 0) {
127 | return;
128 | }
129 |
130 | ctx.fillStyle = "#fff";
131 | ctx.fillText(`${max.toFixed(this.precision)}${this.suffix}`, 2, 10);
132 | ctx.fillText(`${min.toFixed(this.precision)}${this.suffix}`, 2, h - 1);
133 |
134 | if (max === min) {
135 | min -= 1;
136 | max += 1;
137 | }
138 |
139 | ctx.beginPath();
140 | let v = data.get(0);
141 | v = ((v - min) / (max - min)) * h;
142 | ctx.moveTo(0, h - v);
143 | for (let i = 1; i < data.count; ++i) {
144 | v = data.get(i);
145 | v = ((v - min) / (max - min)) * h;
146 | ctx.lineTo(i, h - v);
147 | }
148 | ctx.stroke();
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/docs/formatting_buffer_data.md:
--------------------------------------------------------------------------------
1 | [Overview](../README.md) . [Inspect](inspect.md) . [Capture](capture.md) . [Record](record.md)
2 |
3 | # Formatting Buffer Data
4 |
5 | ###### [Back to capture](capture.md/#uniform-and-storage-buffer-inspection)
6 |
7 | By default WebGPU Inspector will present the data of captured storage and uniform buffers based on parsed reflection information from the shader bound to the render state of the draw or dispatch call. In some cases you may want to view this data differently. WebGPU Inspector lets you override the format the buffer data is viewed as.
8 |
9 | Example:
10 |
11 | The following storage buffer is defined as an array of unsigned int numbers in the shader. The shader bitcast's these numbers to floats. The Format tool can be used to change the view of this data from u32's to f32's, to see that float representation of these values.
12 |
13 | 
14 |
15 | Pressing the **Format** button will bring up the Buffer Format editor.
16 |
17 | 
18 |
19 | The buffer format is the WebGPU Shading Language type of the buffer variable. If you edit the format and press apply, it will parse the new format text and use the reflection information from that to interpret the buffer data.
20 |
21 | In this case we could interpret the buffer as an array of floats instead of an array of unsigned ints. In this case, change the u32 to an f32.
22 |
23 | 
24 |
25 | The **Apply** button applies the changes you made to the view of the buffer.
26 |
27 | **Revert** will remove any edits you made and view the buffer with the original type.
28 |
29 | **Cancel** will close the editor without making any changes.
30 |
31 | Applying the changes to the buffer format, the buffer data view will be updated with the new format.
32 |
33 | 
34 |
35 | You can change the format of the data in any way as long as it's a valid WGSL type. For example, we can view the data as an array of vec4f values.
36 |
37 | 
38 |
39 | Which would then present the buffer data as:
40 |
41 | 
42 |
43 | ## Radix Views
44 |
45 | Not only can you change the format of the data, you can change the number radix of the display, from **Decimal**, **Hexidecimal**, **Octal**, or **Binary**. Changing the Radix to Hexidecimal will display all numbers as hexidecimal.
46 |
47 | 
48 | 
49 |
50 | ## Format data types
51 |
52 | Format types are specified as WGSL variable types. These can be either struct, array, or basic types. See the WGSL spec for more information.
53 |
54 | ### Basic Types
55 |
56 | WGSL supprots the following basic types:
57 | * i32 - signed 32-bit integer
58 | * u32 - unsigned 32-bit integer
59 | * f32 - 32-bit floating-point number
60 | * f16 - 16-bit floating-point number
61 | * bool - boolean
62 | * atomic\ - an atomic basic type, interpreted as the basic type itself.
63 |
64 | ### Arrays
65 |
66 | WGSL supports arrays of any type T, where T is a basic type, another array, or structure.
67 |
68 | * array\ - fixed sized array of type T.
69 | * array\ - runtime sized array of type T.
70 |
71 | ### Structures
72 |
73 | WGSL supports structure types.
74 |
75 | * struct name { member: T, ... }
76 |
77 | For struct types, if the name of the struct in the format matches the name of the original type of the buffer, that struct will be used. Otherwise, the last struct defined in the format will be used.
78 |
79 | For non-struct types, due to how the reflection information works, these types will be automatically wrapped in a struct.
80 |
81 | For example, the format `array` will be automatically wrapped in a struct `struct _array { _: array }`.
82 |
83 |
--------------------------------------------------------------------------------
/src/devtools/shader_editor.js:
--------------------------------------------------------------------------------
1 | import { EditorView } from "codemirror";
2 | import { keymap, highlightSpecialChars, drawSelection, dropCursor,
3 | crosshairCursor, lineNumbers, highlightActiveLineGutter } from "@codemirror/view";
4 | import { EditorState } from "@codemirror/state";
5 | import { defaultHighlightStyle, syntaxHighlighting, indentOnInput, bracketMatching,
6 | foldGutter, foldKeymap } from "@codemirror/language";
7 | import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
8 | import { searchKeymap } from "@codemirror/search";
9 | import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
10 | import { lintKeymap } from "@codemirror/lint";
11 | import { wgsl } from "../thirdparty/codemirror_lang_wgsl.js";
12 | import { cobalt } from 'thememirror';
13 | import { Button } from "./widget/button.js";
14 | import { Div } from "./widget/div.js";
15 | import { PanelActions } from "../utils/actions.js";
16 |
17 | const shaderEditorSetup = (() => [
18 | lineNumbers(),
19 | highlightActiveLineGutter(),
20 | highlightSpecialChars(),
21 | history(),
22 | foldGutter(),
23 | drawSelection(),
24 | dropCursor(),
25 | EditorState.allowMultipleSelections.of(true),
26 | indentOnInput(),
27 | syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
28 | bracketMatching(),
29 | closeBrackets(),
30 | autocompletion(),
31 | crosshairCursor(),
32 | cobalt,
33 | wgsl(),
34 | keymap.of([
35 | indentWithTab,
36 | ...closeBracketsKeymap,
37 | ...defaultKeymap,
38 | ...searchKeymap,
39 | ...historyKeymap,
40 | ...foldKeymap,
41 | ...completionKeymap,
42 | ...lintKeymap
43 | ])
44 | ])();
45 |
46 | export class ShaderEditor extends Div {
47 | constructor(panel, parent, object, onRefresh) {
48 | super(parent);
49 | this.panel = panel;
50 |
51 | const text = object.replacementCode || object.descriptor.code;
52 |
53 | const isModified = object.replacementCode && object.replacementCode !== object.descriptor.code;
54 | const compileRow = new Div(parent);
55 | const compileButton = new Button(compileRow, { label: "Compile", style: "background-color: rgb(200, 150, 51);" });
56 | const revertButton = isModified ? new Button(compileRow, { label: "Revert", style: "background-color: rgb(200, 150, 51);" }) : null;
57 |
58 | const editor = new EditorView({
59 | doc: text,
60 | extensions: [ shaderEditorSetup ],
61 | parent: parent.element,
62 | });
63 |
64 | compileButton.callback = () => {
65 | const code = editor.state.doc.toString();
66 | if (code === object.descriptor.code) {
67 | this._revertShader(object);
68 | object.replacementCode = null;
69 | if (onRefresh) {
70 | onRefresh(object);
71 | }
72 | } else {
73 | this._compileShader(object, code);
74 | object.replacementCode = code;
75 | if (onRefresh) {
76 | onRefresh(object);
77 | }
78 | }
79 | };
80 |
81 | if (revertButton) {
82 | revertButton.callback = () => {
83 | this._revertShader(object);
84 | object.replacementCode = null;
85 | if (onRefresh) {
86 | onRefresh(object);
87 | }
88 | };
89 | }
90 | }
91 |
92 | _revertShader(object) {
93 | this.panel.port.postMessage({ action: PanelActions.RevertShader, id: object.id });
94 | }
95 |
96 | _compileShader(object, code) {
97 | if (code === object.code) {
98 | return;
99 | }
100 | if (object.widget) {
101 | object.widget.element.classList.remove("error");
102 | object.widget.tooltip = "";
103 | for (const child of object.widget.children) {
104 | child.tooltip = "";
105 | }
106 | }
107 | this.panel.database.removeErrorsForObject(object.id);
108 | this.panel.port.postMessage({ action: PanelActions.CompileShader, id: object.id, code });
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/utils/message_port.js:
--------------------------------------------------------------------------------
1 | export class MessagePort {
2 | constructor(name, tabId, listener) {
3 | this.name = name;
4 | this.tabId = tabId ?? 0;
5 | this.listeners = [];
6 | if (listener) {
7 | this.listeners.push(listener);
8 | }
9 | this._port = null;
10 | this._messageQueue = [];
11 | this._isConnected = false;
12 | this._isConnecting = false;
13 | this.reset();
14 | }
15 |
16 | reset() {
17 | //console.log(`[WebGPU Inspector] MessagePort ${this.name} resetting connection`);
18 |
19 | const self = this;
20 | this._isConnected = false;
21 | this._isConnecting = true;
22 |
23 | try {
24 | this._port = chrome.runtime.connect({ name: this.name });
25 |
26 | this._port.onDisconnect.addListener(() => {
27 | //console.warn(`[WebGPU Inspector] MessagePort ${self.name} disconnected`);
28 | self._isConnected = false;
29 | self._isConnecting = false;
30 |
31 | // Attempt to reconnect after a short delay
32 | setTimeout(() => {
33 | self.reset();
34 | }, 100);
35 | });
36 |
37 | this._port.onMessage.addListener((message) => {
38 | // Handle connection handshake acknowledgment
39 | if (message.action === "ConnectionAck") {
40 | self._handleConnectionAck();
41 | return;
42 | }
43 |
44 | for (const listener of self.listeners) {
45 | try {
46 | listener(message);
47 | } catch (e) {
48 | console.error(`[WebGPU Inspector] Error in message listener for port ${self.name}:`, e);
49 | }
50 | }
51 | });
52 |
53 | // Mark as connected immediately for now
54 | // Will be updated to wait for handshake in next step
55 | this._isConnecting = false;
56 | this._isConnected = true;
57 | this._flushMessageQueue();
58 |
59 | //console.log(`[WebGPU Inspector] MessagePort ${this.name} connected`);
60 | } catch (e) {
61 | //console.error(`[WebGPU Inspector] Failed to connect MessagePort ${this.name}:`, e);
62 | this._isConnecting = false;
63 | this._isConnected = false;
64 |
65 | // Retry connection after delay
66 | setTimeout(() => {
67 | self.reset();
68 | }, 1000);
69 | }
70 | }
71 |
72 | _handleConnectionAck() {
73 | //console.log(`[WebGPU Inspector] MessagePort ${this.name} received connection acknowledgment`);
74 | this._isConnecting = false;
75 | this._isConnected = true;
76 | this._flushMessageQueue();
77 | }
78 |
79 | _flushMessageQueue() {
80 | if (!this._isConnected || this._messageQueue.length === 0) {
81 | return;
82 | }
83 |
84 | //console.log(`[WebGPU Inspector] Flushing ${this._messageQueue.length} queued messages for port ${this.name}`);
85 |
86 | const queue = this._messageQueue.slice();
87 | this._messageQueue = [];
88 |
89 | for (const message of queue) {
90 | this._sendMessage(message);
91 | }
92 | }
93 |
94 | _sendMessage(message) {
95 | try {
96 | this._port.postMessage(message);
97 | } catch (e) {
98 | console.error(`[WebGPU Inspector] Failed to send message on port ${this.name}:`, e);
99 | // Queue the message and reset connection
100 | this._messageQueue.push(message);
101 | this._isConnected = false;
102 | this.reset();
103 | }
104 | }
105 |
106 | addListener(listener) {
107 | this.listeners.push(listener);
108 | }
109 |
110 | postMessage(message) {
111 | message.__webgpuInspector = true;
112 | if (this.tabId) {
113 | message.tabId = this.tabId;
114 | }
115 |
116 | // If not connected yet, queue the message
117 | if (!this._isConnected) {
118 | //console.log(`[WebGPU Inspector] Queuing message (port ${this.name} not connected yet)`);
119 | this._messageQueue.push(message);
120 | return;
121 | }
122 |
123 | this._sendMessage(message);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## v0.18.0 -
2 |
3 | * Improve connection from page to devtools panel.
4 | * Fix capturing frames from multithreaded Wasm.
5 |
6 | ## v0.17.0 - December 17, 2025
7 |
8 | * Zoomable texture views.
9 | * Stacktrace lines link to source files.
10 | * Fix text color for light color mode.
11 | * Validation error text is now selectable.
12 | * Improvements to capturing specific frames.
13 |
14 | ## v0.16.0
15 |
16 | * Fix shader recompile on Firefox.
17 | * Add back/forward inspection history navigation.
18 |
19 | ## v0.15.0
20 |
21 | * Various fixes from wgsl_reflect for shader reflection and debugging.
22 | * Fix issues with sites that have Workers.
23 |
24 | ## v0.14.0
25 |
26 | * Fix shader debugger for shaders that have multiple entry points with differing sets of bindings. [33](https://github.com/brendan-duncan/webgpu_inspector/issues/33)
27 | * UI improvements for frame captures:
28 | [29](https://github.com/brendan-duncan/webgpu_inspector/issues/29)
29 | [31](https://github.com/brendan-duncan/webgpu_inspector/issues/31)
30 | * Fix setBindGroup being displayed multiple times in capture. [30](https://github.com/brendan-duncan/webgpu_inspector/issues/30)
31 | * Inspector shows all info from Adapter.
32 |
33 | ## v0.13.0
34 |
35 | * Fix frame capture for content running in a worker thread.
36 |
37 | ## v0.12.0
38 |
39 | * Various shader reflection library fixes.
40 |
41 | ## v0.11.0
42 |
43 | * Compute shader debugger (experimental)
44 | * Fix worker inspection error from importScripts, causing some pages to fail when inspected.
45 |
46 | ## v0.10.0
47 |
48 | * Fix shader parse issue with literal values like "-1f".
49 | * Capture can now record commands for compute-only programs, and for programs that do not use requestAnimationFrame.
50 |
51 | ## v0.9.0
52 |
53 | * Fix issue with capturing dynamic offset uniform buffers.
54 |
55 | ## v0.8.0
56 |
57 | * Inspect 3D textures.
58 |
59 | ## v0.5.0
60 |
61 | * Automatically inject into WebWorkers.
62 | * Inspect and capture RenderBundles.
63 | * Improve capture display of vertex buffers.
64 |
65 | ## v0.4.0
66 |
67 | * Fix Record for pages that use no buffer or texture data.
68 | * Display depth texture min and max range values in Inspector.
69 | * Recorder updates.
70 |
71 | ## v0.3.0
72 |
73 | * Normalized depth texture preview, so depth textures can be visualized even if they are in compressed ranges.
74 |
75 | ## v0.2.0
76 |
77 | * Use colors to improve readability of nested debug groups in a capture.
78 | * Fix issue with capturing buffer data.
79 | * Limit captured array buffer data view to improve UI performance and readability, with an Offset and Count to control the view of the array.
80 |
81 | ## v0.1.0
82 |
83 | * Add ability to change radix of number views for buffer data.
84 |
85 | ## v0.0.9
86 |
87 | * Make buffer data view more compact for basic types.
88 | * Report memory leaks as a validation error (buffer/texture/device garbage collected without explicit destroy).
89 | * You can edit the format used for viewing uniform and storage buffer data.
90 |
91 | ## v0.0.8
92 |
93 | * Report an error if a canvas texture is used after it has expired.
94 | * Ability to capture a specific frame.
95 | * Inspect indirect buffers for draw*Indirect calls.
96 | * Add filter for capture command list.
97 |
98 | ## v0.0.7
99 |
100 | * Fix error when capturing frames when a BindGroup buffer uses a static offset.
101 |
102 | ## v0.0.6
103 |
104 | * Improve Capture render and compute pass organization when passes use different command encoders.
105 | * Inspect RG11B10 texture pixel values
106 | * Preview depth textures
107 |
108 | ## v0.0.5
109 |
110 | * Expand Inspector Descriptor display size
111 | * Capture buffers when BindGroup doesn't have an explicit BindGroupLayout
112 |
113 | ## v0.0.4
114 |
115 | * Fix multisampled texture preview
116 | * Mark objects with errors as red in Object List
117 | * Fix inspecting async render and compute pipelines
118 | * Auto load textures in inspector
119 | * Add channel select to texture viewer
120 | * Refactor extension code, add Firefox support
121 | * Move extension code to src
122 | * Build **extensions/chrome** and **extensions/firefox**
123 | * Firefox only supports manifest v2, and Chrome supports manifest v3
124 |
125 |
--------------------------------------------------------------------------------
/test/render_bundle.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/src/devtools/widget/select.js:
--------------------------------------------------------------------------------
1 | import { Widget } from './widget.js';
2 | import { Label } from './label.js';
3 | import { TextInput } from './text_input.js';
4 | import { Signal } from '../../utils/signal.js';
5 |
6 | export class Select extends Widget {
7 | constructor(parent, options) {
8 | super('span', parent);
9 | this.classList.add('select');
10 |
11 | this.select = new Widget('select', this);
12 | this.select.style.width = '100%';
13 | this.select.style.height = '20px';
14 | this.select.style.border = 'none';
15 | this.select.style.display = 'inline-block';
16 | this.select.classList.add('select');
17 | this.onChange = new Signal();
18 |
19 | const self = this;
20 | this.select.element.addEventListener('change', function () {
21 | if (self.selectEdit) {
22 | self.selectEdit.value = self.select.element.value;
23 | } else {
24 | self.onChange.emit(self.value, self.index);
25 | if (self._onChange) {
26 | self._onChange(self.value, self.index);
27 | }
28 | }
29 | });
30 |
31 | if (options && options.editable) {
32 | this.selectEdit = new TextInput(this, {
33 | value: options.options[0],
34 | style:
35 | 'position: absolute; top: 2px; left: 0px; width: calc(100% - 20px); height: 20px; border: none;',
36 | });
37 | this.selectEdit.onChange.addListener(function () {
38 | self.onChange.emit(self.value);
39 | if (self._onChange) {
40 | self._onChange(self.value, self.index);
41 | }
42 | });
43 | }
44 |
45 | if (options) {
46 | this.configure(options);
47 | }
48 |
49 | this.style.height = '20px';
50 | this.style.position = 'relative';
51 | this.style.minWidth = '50px';
52 | }
53 |
54 | get disabled() {
55 | return super.disabled;
56 | }
57 |
58 | set disabled(v) {
59 | super.disabled = v;
60 | this.select.disabled = v;
61 | if (this.selectEdit) {
62 | this.selectEdit.disabled = v;
63 | }
64 | }
65 |
66 | configure(options) {
67 | if (!options) {
68 | return;
69 | }
70 | super.configure(options);
71 |
72 | if (options.options) {
73 | for (const o of options.options) {
74 | this.addOption(o);
75 | }
76 | }
77 |
78 | if (options.label !== undefined) {
79 | if (options.label.constructor === String) {
80 | this.label = new Label(options.label, this.parent, {
81 | fixedSize: 0,
82 | for: this,
83 | });
84 | } else {
85 | this.label = options.label;
86 | this.label.for = this.id;
87 | if (!this.label.parent) {
88 | this.label.parent = this.parent;
89 | }
90 | }
91 | }
92 |
93 | if (options.value !== undefined) {
94 | this.select.element.value = options.value;
95 | }
96 |
97 | if (options.index !== undefined) {
98 | this.select.element.selectedIndex = options.index;
99 | if (this.selectEdit) {
100 | this.selectEdit.value = this.select.element.value;
101 | }
102 | }
103 |
104 | if (options.onChange !== undefined) {
105 | this._onChange = options.onChange;
106 | }
107 | }
108 |
109 | get index() {
110 | return this.select.element.selectedIndex;
111 | }
112 |
113 | set index(v) {
114 | this.select.element.selectedIndex = v;
115 | }
116 |
117 | get value() {
118 | if (this.selectEdit) {
119 | return this.selectEdit.value;
120 | }
121 | return this.select.element.value;
122 | }
123 |
124 | set value(v) {
125 | if (this.selectEdit) {
126 | this.selectEdit.value = v;
127 | } else {
128 | this.select.element.value = v;
129 | }
130 | }
131 |
132 | addOption(text) {
133 | const o = document.createElement('option');
134 | o.innerText = text;
135 | this.select.element.add(o);
136 | }
137 |
138 | resize(width, height) {
139 | if (!this._element) {
140 | return;
141 | }
142 |
143 | // SELECT elements behave differently than other elements with resizing.
144 | this.select.element.style.width = `${width}px`;
145 | this.select.element.style.height = `${height}px`;
146 |
147 | this.onResize();
148 |
149 | if (!Widget.disablePaintingOnResize) {
150 | this.paintEvent();
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/test/widget/tree_widget.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
94 |
--------------------------------------------------------------------------------
/src/devtools/widget/split.js:
--------------------------------------------------------------------------------
1 | import { Div } from './div.js';
2 | import { SplitBar } from './split_bar.js';
3 |
4 | /**
5 | * The children of this widget are arranged horizontally or vertically and separated by a
6 | * draggable SplitBar.
7 | */
8 | export class Split extends Div {
9 | constructor(parent, options) {
10 | super(parent);
11 | this.classList.add('split', 'disable-selection');
12 |
13 | this._direction = Split.Horizontal;
14 | this._position = 0.5;
15 | this.mode = Split.Percentage;
16 |
17 | if (options) {
18 | this.configure(options);
19 | }
20 |
21 | if (this._direction === Split.Horizontal) {
22 | this.classList.add('hsplit');
23 | } else {
24 | this.classList.add('vsplit');
25 | }
26 | }
27 |
28 | configure(options) {
29 | if (options.direction !== undefined) {
30 | this._direction = options.direction;
31 | }
32 |
33 | super.configure(options);
34 |
35 | if (options.position !== undefined) {
36 | this.position = options.position;
37 | if (this.position > 1) {
38 | this.mode = Split.Pixel;
39 | }
40 | }
41 | }
42 |
43 | get direction() {
44 | return this._direction;
45 | }
46 |
47 | get position() {
48 | return this._position;
49 | }
50 |
51 | set position(pos) {
52 | this._position = pos;
53 | this.updatePosition();
54 | }
55 |
56 | updatePosition() {
57 | if (this.children.length < 3) {
58 | return;
59 | }
60 |
61 | const numSplitBars = this.children.length - 2;
62 | const splitBarSize = numSplitBars * SplitBar.size;
63 |
64 | let splitPos;
65 | let splitPos2;
66 | if (this._position < 1) {
67 | const pct = this._position * 100;
68 | splitPos = `${pct}%`;
69 | splitPos2 = `calc(${100 - pct}% - ${splitBarSize}px)`;
70 | } else {
71 | splitPos = `${this._position}px`;
72 | splitPos2 = `calc(100% - ${this._position}px - ${splitBarSize}px)`;
73 | }
74 |
75 | if (this._direction == Split.Horizontal) {
76 | this.children[0].style.width = splitPos;
77 | this.children[0].element.width = '0';
78 |
79 | this.children[2].style.width = splitPos2;
80 | this.children[2].element.width = '0';
81 | } else {
82 | this.children[0].element.height = '0';
83 | this.children[0].style.height = splitPos;
84 |
85 | this.children[2].style.height = splitPos2;
86 | this.children[2].element.height = '0';
87 | }
88 |
89 | this.onResize();
90 | }
91 |
92 | appendChild(child) {
93 | if (this.direction == Split.Horizontal)
94 | child.style.display = 'inline-block';
95 |
96 | if (this.children.length == 0 || child.constructor.isSplitBar) {
97 | if (this.children.length == 0) {
98 | child.style.width = '100%';
99 | child.style.height = '100%';
100 | } else {
101 | if (this._direction == Split.Horizontal) {
102 | child.style.height = '100%';
103 | } else {
104 | child.style.width = '100%';
105 | }
106 | }
107 | super.appendChild(child);
108 | return;
109 | }
110 |
111 | const percent = (1 / (this.children.length + 1)) * 100;
112 |
113 | new SplitBar(
114 | this._direction == Split.Horizontal
115 | ? SplitBar.Vertical
116 | : SplitBar.Horizontal,
117 | this
118 | );
119 |
120 | super.appendChild(child);
121 |
122 | const numSplitBars = this.children.length - 2;
123 | const splitBarSize = numSplitBars * SplitBar.size;
124 |
125 | for (const c of this.children) {
126 | if (!c.constructor.isSplitBar) {
127 | if (c === this.children[this.children.length - 1]) {
128 | if (this._direction == Split.Horizontal) {
129 | c.element.width = '0';
130 | c.style.height = '100%';
131 | c.style.width = `calc(${100 - percent}% - ${splitBarSize}px)`;
132 | } else {
133 | c.element.height = '0';
134 | c.style.width = '100%';
135 | c.style.height = `calc(${100 - percent}% - ${splitBarSize}px)`;
136 | }
137 | } else {
138 | if (this._direction == Split.Horizontal) {
139 | c.style.width = `${percent}%`;
140 | } else {
141 | c.style.height = `${percent}%`;
142 | }
143 | }
144 | }
145 |
146 | c.onResize();
147 | }
148 |
149 | if (this._position != 0.5) {
150 | this.updatePosition();
151 | }
152 | }
153 | }
154 |
155 | Split.Horizontal = 0;
156 | Split.Vertical = 1;
157 | Split.Percentage = 0;
158 | Split.Pixel = 1;
159 |
--------------------------------------------------------------------------------
/extensions/chrome/content_script.js:
--------------------------------------------------------------------------------
1 | !function(){const e={CaptureBufferData:'webgpu_inspect_capture_buffer_data',CaptureBuffers:'webgpu_inspect_capture_buffers',DeleteObjects:'webgpu_inspect_delete_objects',ValidationError:'webgpu_inspect_validation_error',MemoryLeakWarning:'webgpu_inspect_memory_leak_warning',DeltaTime:'webgpu_inspect_delta_time',CaptureFrameResults:'webgpu_inspect_capture_frame_results',CaptureFrameCommands:'webgpu_inspect_capture_frame_commands',ObjectSetLabel:'webgpu_inspect_object_set_label',AddObject:'webgpu_inspect_add_object',ResolveAsyncObject:'webgpu_inspect_resolve_async_object',DeleteObject:'webgpu_inspect_delete_object',CaptureTextureFrames:'webgpu_inspect_capture_texture_frames',CaptureTextureData:'webgpu_inspect_capture_texture_data',CaptureBufferData:'webgpu_inspect_capture_buffer_data',WriteBuffer:'wrebgpu_inspect_write_buffer',Recording:'webgpu_record_recording',RecordingCommand:'webgpu_record_command',RecordingDataCount:'webgpu_record_data_count',RecordingData:'webgpu_record_data',PageReady:'webgpu_inspect_page_ready',PanelReady:'webgpu_inspect_panel_ready',ConnectionAck:'webgpu_inspect_connection_ack'};e.values=new Set(Object.values(e));const t='webgpu_inspect_request_texture',s='webgpu_inspect_compile_shader',n='webgpu_inspect_revert_shader',i='webgpu_inspector_capture',o='webgpu_initialize_inspector',r='webgpu_initialize_recorder',a='WEBGPU_INSPECTOR_LOADED',c='WEBGPU_RECORDER_LOADED',_=navigator.userAgent.toLowerCase().indexOf('firefox')>-1,u=new class p{constructor(e,t,s){this.name=e,this.tabId=t??0,this.listeners=[],s&&this.listeners.push(s),this._port=null,this._messageQueue=[],this._isConnected=!1,this._isConnecting=!1,this.reset()}reset(){const e=this;this._isConnected=!1,this._isConnecting=!0;try{this._port=chrome.runtime.connect({name:this.name}),this._port.onDisconnect.addListener(()=>{e._isConnected=!1,e._isConnecting=!1,setTimeout(()=>{e.reset()},100)}),this._port.onMessage.addListener(t=>{if('ConnectionAck'!==t.action)for(const s of e.listeners)try{s(t)}catch(t){console.error(`[WebGPU Inspector] Error in message listener for port ${e.name}:`,t)}else e._handleConnectionAck()}),this._isConnecting=!1,this._isConnected=!0,this._flushMessageQueue()}catch(t){this._isConnecting=!1,this._isConnected=!1,setTimeout(()=>{e.reset()},1e3)}}_handleConnectionAck(){this._isConnecting=!1,this._isConnected=!0,this._flushMessageQueue()}_flushMessageQueue(){if(!this._isConnected||0===this._messageQueue.length)return;const e=this._messageQueue.slice();this._messageQueue=[];for(const t of e)this._sendMessage(t)}_sendMessage(e){try{this._port.postMessage(e)}catch(t){console.error(`[WebGPU Inspector] Failed to send message on port ${this.name}:`,t),this._messageQueue.push(e),this._isConnected=!1,this.reset()}}addListener(e){this.listeners.push(e)}postMessage(e){e.__webgpuInspector=!0,this.tabId&&(e.tabId=this.tabId),this._isConnected?this._sendMessage(e):this._messageQueue.push(e)}}('webgpu-inspector-page',0,p=>{let d=p.action;if(!d)return;if(d===e.PanelReady)return void u.postMessage({action:e.ConnectionAck});if(d===t||d===s||d===n){const e=_?cloneInto(p,document.defaultView):p;return void window.dispatchEvent(new CustomEvent('__WebGPUInspector',{detail:e}))}if(d===r)return sessionStorage.setItem(c,`${p.frames}%${p.filename}%${p.download}`),void setTimeout(()=>{window.location.reload()},50);let g='true';if(d===i){const e=JSON.stringify(p);if(p.frame>=0)d=o,g=e;else{sessionStorage.setItem('WEBGPU_INSPECTOR_CAPTURE_FRAME',e);const t={__webgpuInspector:!0,__webgpuInspectorPanel:!0,action:i,data:e},s=_?cloneInto(t,document.defaultView):t;window.dispatchEvent(new CustomEvent('__WebGPUInspector',{detail:s}))}}d===o&&(sessionStorage.setItem(a,g),setTimeout(()=>{window.location.reload()},50))});function d(e,t,s){const n=document.createElement('script');if(n.id=e,n.src=t,s)for(const e in s)n.setAttribute(e,s[e]);(document.head||document.documentElement).appendChild(n)}if(window.addEventListener('pageshow',e=>{e.persisted&&u.reset()}),window.addEventListener('__WebGPUInspector',t=>{const s=t.detail;if('object'!=typeof s||null===s)return;const n=s.action;if(e.values.has(n))try{u.postMessage(s)}catch(e){console.error('[WebGPU Inspector] Error sending message from page:',e)}}),window.addEventListener('__WebGPURecorder',t=>{const s=t.detail;if('object'!=typeof s||null===s)return;const n=s.action;if(e.values.has(n))try{u.postMessage(s)}catch(e){console.error('[WebGPU Inspector] Error sending message from page:',e)}}),-1===navigator.userAgent.indexOf('Chrom')&&(-1!==navigator.userAgent.indexOf('Safari')||-1!==navigator.userAgent.indexOf('Firefox'))){sessionStorage.getItem(a)&&d('__webgpu_inspector',chrome.runtime.getURL('webgpu_inspector_loader.js'));const e=sessionStorage.getItem(c);if(e){const t=e.split('%');d('__webgpu_recorder',chrome.runtime.getURL('webgpu_recorder_loader.js'),{filename:t[1],frames:t[0],download:t[2],removeUnusedResources:1,messageRecording:1})}}u.postMessage({action:e.PageReady})}();
2 | //# sourceMappingURL=content_script.js.map
3 |
--------------------------------------------------------------------------------
/extensions/firefox/content_script.js:
--------------------------------------------------------------------------------
1 | !function(){const e={CaptureBufferData:'webgpu_inspect_capture_buffer_data',CaptureBuffers:'webgpu_inspect_capture_buffers',DeleteObjects:'webgpu_inspect_delete_objects',ValidationError:'webgpu_inspect_validation_error',MemoryLeakWarning:'webgpu_inspect_memory_leak_warning',DeltaTime:'webgpu_inspect_delta_time',CaptureFrameResults:'webgpu_inspect_capture_frame_results',CaptureFrameCommands:'webgpu_inspect_capture_frame_commands',ObjectSetLabel:'webgpu_inspect_object_set_label',AddObject:'webgpu_inspect_add_object',ResolveAsyncObject:'webgpu_inspect_resolve_async_object',DeleteObject:'webgpu_inspect_delete_object',CaptureTextureFrames:'webgpu_inspect_capture_texture_frames',CaptureTextureData:'webgpu_inspect_capture_texture_data',CaptureBufferData:'webgpu_inspect_capture_buffer_data',WriteBuffer:'wrebgpu_inspect_write_buffer',Recording:'webgpu_record_recording',RecordingCommand:'webgpu_record_command',RecordingDataCount:'webgpu_record_data_count',RecordingData:'webgpu_record_data',PageReady:'webgpu_inspect_page_ready',PanelReady:'webgpu_inspect_panel_ready',ConnectionAck:'webgpu_inspect_connection_ack'};e.values=new Set(Object.values(e));const t='webgpu_inspect_request_texture',s='webgpu_inspect_compile_shader',n='webgpu_inspect_revert_shader',i='webgpu_inspector_capture',o='webgpu_initialize_inspector',r='webgpu_initialize_recorder',a='WEBGPU_INSPECTOR_LOADED',c='WEBGPU_RECORDER_LOADED',_=navigator.userAgent.toLowerCase().indexOf('firefox')>-1,u=new class p{constructor(e,t,s){this.name=e,this.tabId=t??0,this.listeners=[],s&&this.listeners.push(s),this._port=null,this._messageQueue=[],this._isConnected=!1,this._isConnecting=!1,this.reset()}reset(){const e=this;this._isConnected=!1,this._isConnecting=!0;try{this._port=chrome.runtime.connect({name:this.name}),this._port.onDisconnect.addListener(()=>{e._isConnected=!1,e._isConnecting=!1,setTimeout(()=>{e.reset()},100)}),this._port.onMessage.addListener(t=>{if('ConnectionAck'!==t.action)for(const s of e.listeners)try{s(t)}catch(t){console.error(`[WebGPU Inspector] Error in message listener for port ${e.name}:`,t)}else e._handleConnectionAck()}),this._isConnecting=!1,this._isConnected=!0,this._flushMessageQueue()}catch(t){this._isConnecting=!1,this._isConnected=!1,setTimeout(()=>{e.reset()},1e3)}}_handleConnectionAck(){this._isConnecting=!1,this._isConnected=!0,this._flushMessageQueue()}_flushMessageQueue(){if(!this._isConnected||0===this._messageQueue.length)return;const e=this._messageQueue.slice();this._messageQueue=[];for(const t of e)this._sendMessage(t)}_sendMessage(e){try{this._port.postMessage(e)}catch(t){console.error(`[WebGPU Inspector] Failed to send message on port ${this.name}:`,t),this._messageQueue.push(e),this._isConnected=!1,this.reset()}}addListener(e){this.listeners.push(e)}postMessage(e){e.__webgpuInspector=!0,this.tabId&&(e.tabId=this.tabId),this._isConnected?this._sendMessage(e):this._messageQueue.push(e)}}('webgpu-inspector-page',0,p=>{let d=p.action;if(!d)return;if(d===e.PanelReady)return void u.postMessage({action:e.ConnectionAck});if(d===t||d===s||d===n){const e=_?cloneInto(p,document.defaultView):p;return void window.dispatchEvent(new CustomEvent('__WebGPUInspector',{detail:e}))}if(d===r)return sessionStorage.setItem(c,`${p.frames}%${p.filename}%${p.download}`),void setTimeout(()=>{window.location.reload()},50);let g='true';if(d===i){const e=JSON.stringify(p);if(p.frame>=0)d=o,g=e;else{sessionStorage.setItem('WEBGPU_INSPECTOR_CAPTURE_FRAME',e);const t={__webgpuInspector:!0,__webgpuInspectorPanel:!0,action:i,data:e},s=_?cloneInto(t,document.defaultView):t;window.dispatchEvent(new CustomEvent('__WebGPUInspector',{detail:s}))}}d===o&&(sessionStorage.setItem(a,g),setTimeout(()=>{window.location.reload()},50))});function d(e,t,s){const n=document.createElement('script');if(n.id=e,n.src=t,s)for(const e in s)n.setAttribute(e,s[e]);(document.head||document.documentElement).appendChild(n)}if(window.addEventListener('pageshow',e=>{e.persisted&&u.reset()}),window.addEventListener('__WebGPUInspector',t=>{const s=t.detail;if('object'!=typeof s||null===s)return;const n=s.action;if(e.values.has(n))try{u.postMessage(s)}catch(e){console.error('[WebGPU Inspector] Error sending message from page:',e)}}),window.addEventListener('__WebGPURecorder',t=>{const s=t.detail;if('object'!=typeof s||null===s)return;const n=s.action;if(e.values.has(n))try{u.postMessage(s)}catch(e){console.error('[WebGPU Inspector] Error sending message from page:',e)}}),-1===navigator.userAgent.indexOf('Chrom')&&(-1!==navigator.userAgent.indexOf('Safari')||-1!==navigator.userAgent.indexOf('Firefox'))){sessionStorage.getItem(a)&&d('__webgpu_inspector',chrome.runtime.getURL('webgpu_inspector_loader.js'));const e=sessionStorage.getItem(c);if(e){const t=e.split('%');d('__webgpu_recorder',chrome.runtime.getURL('webgpu_recorder_loader.js'),{filename:t[1],frames:t[0],download:t[2],removeUnusedResources:1,messageRecording:1})}}u.postMessage({action:e.PageReady})}();
2 | //# sourceMappingURL=content_script.js.map
3 |
--------------------------------------------------------------------------------
/test/timestamp_query.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
155 |
156 |
157 |
--------------------------------------------------------------------------------