From 301975cba52f8ca6bb154a70aab1529ee490bad2 Mon Sep 17 00:00:00 2001 From: Billy Messenger <60663878+BillyDM@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:10:03 -0600 Subject: [PATCH 1/3] add "bring your own gui" example plugins --- Cargo.lock | 891 ++++++++++++++++-- Cargo.toml | 3 + plugins/examples/byo_gui_gl/Cargo.toml | 22 + plugins/examples/byo_gui_gl/src/lib.rs | 553 +++++++++++ plugins/examples/byo_gui_gl/src/main.rs | 7 + .../examples/byo_gui_softbuffer/Cargo.toml | 23 + .../examples/byo_gui_softbuffer/src/lib.rs | 585 ++++++++++++ .../examples/byo_gui_softbuffer/src/main.rs | 7 + plugins/examples/byo_gui_wgpu/Cargo.toml | 24 + plugins/examples/byo_gui_wgpu/src/lib.rs | 673 +++++++++++++ plugins/examples/byo_gui_wgpu/src/main.rs | 7 + 11 files changed, 2704 insertions(+), 91 deletions(-) create mode 100644 plugins/examples/byo_gui_gl/Cargo.toml create mode 100644 plugins/examples/byo_gui_gl/src/lib.rs create mode 100644 plugins/examples/byo_gui_gl/src/main.rs create mode 100644 plugins/examples/byo_gui_softbuffer/Cargo.toml create mode 100644 plugins/examples/byo_gui_softbuffer/src/lib.rs create mode 100644 plugins/examples/byo_gui_softbuffer/src/main.rs create mode 100644 plugins/examples/byo_gui_wgpu/Cargo.toml create mode 100644 plugins/examples/byo_gui_wgpu/src/lib.rs create mode 100644 plugins/examples/byo_gui_wgpu/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 553996ae..bac7ac1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" dependencies = [ "accesskit", "accesskit_consumer", - "objc2", + "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", ] @@ -129,6 +129,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "alsa" version = "0.7.1" @@ -298,6 +304,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.5", +] + [[package]] name = "assert_no_alloc" version = "1.1.2" @@ -475,7 +490,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -537,7 +552,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -623,7 +638,7 @@ version = "0.1.0" source = "git+https://github.com/RustAudio/baseview.git?rev=1d9806d5bd92275d0d8142d9c9c90198757b9b25#1d9806d5bd92275d0d8142d9c9c90198757b9b25" dependencies = [ "cocoa", - "core-foundation", + "core-foundation 0.9.4", "keyboard-types", "nix 0.22.3", "objc", @@ -641,7 +656,7 @@ version = "0.1.0" source = "git+https://github.com/RustAudio/baseview.git?rev=2c1b1a7b0fef1a29a5150a6a8f6fef6a0cbab8c4#2c1b1a7b0fef1a29a5150a6a8f6fef6a0cbab8c4" dependencies = [ "cocoa", - "core-foundation", + "core-foundation 0.9.4", "keyboard-types", "nix 0.22.3", "objc", @@ -659,7 +674,7 @@ version = "0.1.0" source = "git+https://github.com/RustAudio/baseview.git?rev=45465c5f46abed6c6ce370fffde5edc8e4cd5aa3#45465c5f46abed6c6ce370fffde5edc8e4cd5aa3" dependencies = [ "cocoa", - "core-foundation", + "core-foundation 0.9.4", "keyboard-types", "nix 0.22.3", "objc", @@ -676,7 +691,24 @@ version = "0.1.0" source = "git+https://github.com/RustAudio/baseview.git?rev=579130ecb4f9f315ae52190af42f0ea46aeaa4a2#579130ecb4f9f315ae52190af42f0ea46aeaa4a2" dependencies = [ "cocoa", - "core-foundation", + "core-foundation 0.9.4", + "keyboard-types", + "nix 0.22.3", + "objc", + "raw-window-handle 0.5.2", + "uuid", + "winapi", + "x11", + "x11rb 0.13.1", +] + +[[package]] +name = "baseview" +version = "0.1.0" +source = "git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0#9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" +dependencies = [ + "cocoa", + "core-foundation 0.9.4", "keyboard-types", "nix 0.22.3", "objc", @@ -704,9 +736,24 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.75", + "syn 2.0.90", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -740,7 +787,7 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", ] [[package]] @@ -750,7 +797,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" dependencies = [ "block-sys", - "objc2-encode", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", ] [[package]] @@ -779,11 +835,53 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byo_gui_gl" +version = "0.1.0" +dependencies = [ + "atomic_float", + "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0)", + "crossbeam", + "glow 0.16.0", + "nih_plug", + "raw-window-handle 0.5.2", + "serde", +] + +[[package]] +name = "byo_gui_softbuffer" +version = "0.1.0" +dependencies = [ + "atomic_float", + "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0)", + "crossbeam", + "nih_plug", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "serde", + "softbuffer", +] + +[[package]] +name = "byo_gui_wgpu" +version = "0.1.0" +dependencies = [ + "atomic_float", + "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0)", + "crossbeam", + "nih_plug", + "pollster", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "serde", + "wgpu", +] + [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -796,7 +894,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -894,6 +992,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cgl" version = "0.3.2" @@ -971,7 +1075,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -1008,9 +1112,9 @@ dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", + "core-foundation 0.9.4", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "libc", "objc", ] @@ -1023,12 +1127,22 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "libc", "objc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1126,6 +1240,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1139,9 +1263,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", "libc", ] @@ -1152,7 +1289,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", "libc", ] @@ -1162,9 +1310,9 @@ version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types", + "core-foundation 0.9.4", + "core-graphics 0.22.3", + "foreign-types 0.3.2", "libc", ] @@ -1194,7 +1342,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5" dependencies = [ - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "coremidi-sys", ] @@ -1374,9 +1522,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.75", + "syn 2.0.90", ] +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + [[package]] name = "cty" version = "0.2.2" @@ -1419,7 +1573,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -1480,7 +1634,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -1492,12 +1646,60 @@ dependencies = [ "libloading 0.8.5", ] +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "drm" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +dependencies = [ + "bitflags 2.6.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "rustix 0.38.34", +] + +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys", + "rustix 0.38.34", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -1607,7 +1809,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -1817,8 +2019,8 @@ checksum = "46c9a156ec38864999bc9c4156e5f3b50224d4a5578028a64e5a3875caa9ee28" dependencies = [ "bitflags 1.3.2", "byteorder", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.22.3", "core-text", "dirs-next", "dwrote", @@ -1862,7 +2064,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -1871,6 +2094,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "freetype" version = "0.7.0" @@ -1977,7 +2206,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -2195,6 +2424,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glow_glyph" version = "0.5.1" @@ -2214,15 +2467,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc93b03242719b8ad39fb26ed2b01737144ce7bd4bfc7adadcef806596760fe" dependencies = [ "bitflags 1.3.2", - "cfg_aliases", + "cfg_aliases 0.1.1", "cgl", - "core-foundation", + "core-foundation 0.9.4", "dispatch", "glutin_egl_sys", "glutin_glx_sys", - "glutin_wgl_sys", + "glutin_wgl_sys 0.4.0", "libloading 0.7.4", - "objc2", + "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", "raw-window-handle 0.5.2", "windows-sys 0.45.0", @@ -2235,7 +2488,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "glutin", "raw-window-handle 0.5.2", "winit", @@ -2270,6 +2523,15 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c" +dependencies = [ + "gl_generator", +] + [[package]] name = "glyph_brush" version = "0.7.9" @@ -2319,11 +2581,66 @@ dependencies = [ "scroll", ] +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.6.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +dependencies = [ + "bitflags 2.6.0", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "heck" @@ -2358,6 +2675,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -2625,10 +2948,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2641,6 +2965,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.5", + "pkg-config", +] + [[package]] name = "khronos_api" version = "3.1.0" @@ -2745,6 +3080,18 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -2902,6 +3249,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + [[package]] name = "midi-consts" version = "0.1.0" @@ -2968,6 +3330,27 @@ dependencies = [ "smallvec", ] +[[package]] +name = "naga" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" +dependencies = [ + "arrayvec 0.7.6", + "bit-set", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + [[package]] name = "ndk" version = "0.7.0" @@ -3050,7 +3433,7 @@ dependencies = [ "cfg-if", "clap", "clap-sys", - "core-foundation", + "core-foundation 0.9.4", "cpal", "crossbeam", "jack", @@ -3214,7 +3597,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -3293,7 +3676,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -3305,7 +3688,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -3343,15 +3726,31 @@ version = "0.2.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + [[package]] name = "objc2" version = "0.3.0-beta.3.patch-leaks.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" dependencies = [ - "block2", - "objc-sys", - "objc2-encode", + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys 0.3.5", + "objc2-encode 4.0.3", ] [[package]] @@ -3360,7 +3759,50 @@ version = "2.0.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", ] [[package]] @@ -3406,9 +3848,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "open" @@ -3733,6 +4175,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "poly_mod_synth" version = "0.1.0" @@ -3763,6 +4211,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "primal-check" version = "0.3.4" @@ -3799,13 +4253,19 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" + [[package]] name = "puberty_simulator" version = "0.1.0" @@ -3931,6 +4391,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + [[package]] name = "rangemap" version = "1.5.1" @@ -3952,6 +4418,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rayon" version = "1.10.0" @@ -4076,6 +4548,12 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "rgb" version = "0.8.48" @@ -4231,7 +4709,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -4291,7 +4769,7 @@ checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -4314,7 +4792,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -4498,6 +4976,34 @@ dependencies = [ "nih_plug", ] +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases 0.2.1", + "core-graphics 0.24.0", + "drm", + "fastrand 2.1.0", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle 0.6.2", + "redox_syscall 0.5.3", + "rustix 0.38.34", + "tiny-xlib", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", + "x11rb 0.13.1", +] + [[package]] name = "spectral_compressor" version = "0.4.3" @@ -4512,6 +5018,15 @@ dependencies = [ "triple_buffer", ] +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -4562,9 +5077,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -4621,22 +5136,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -4672,6 +5187,18 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "pkg-config", + "tracing", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4770,7 +5297,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] @@ -4895,7 +5422,7 @@ checksum = "1ed7f4237ba393424195053097c1516bd4590dc82b84f2f97c5c69e12704555b" dependencies = [ "proc-macro-hack", "quote", - "syn 2.0.75", + "syn 2.0.90", "unic-langid-impl", ] @@ -4947,6 +5474,18 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -5177,9 +5716,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -5188,36 +5727,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5225,22 +5764,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-timer" @@ -5332,14 +5871,120 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "wgpu" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +dependencies = [ + "arrayvec 0.7.6", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +dependencies = [ + "arrayvec 0.7.6", + "bit-vec", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash 1.1.0", + "smallvec", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +dependencies = [ + "android_system_properties", + "arrayvec 0.7.6", + "ash", + "bit-set", + "bitflags 2.6.0", + "block", + "bytemuck", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "glow 0.14.2", + "glutin_wgl_sys 0.6.0", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.5", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "range-alloc", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +dependencies = [ + "bitflags 2.6.0", + "js-sys", + "web-sys", +] + [[package]] name = "widestring" version = "1.1.0" @@ -5416,8 +6061,8 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.48.0", + "windows-interface 0.48.0", "windows-targets 0.48.5", ] @@ -5431,6 +6076,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -5446,7 +6101,20 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ - "windows-result", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings", "windows-targets 0.52.6", ] @@ -5461,6 +6129,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "windows-interface" version = "0.48.0" @@ -5472,6 +6151,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -5481,6 +6171,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -5718,16 +6427,16 @@ checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" dependencies = [ "android-activity", "bitflags 1.3.2", - "cfg_aliases", - "core-foundation", - "core-graphics", + "cfg_aliases 0.1.1", + "core-foundation 0.9.4", + "core-graphics 0.22.3", "dispatch", "instant", "libc", "log", "mio", "ndk 0.7.0", - "objc2", + "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", "orbclient", "percent-encoding", @@ -6011,7 +6720,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 76929e8e..6c5199b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,9 @@ members = [ "cargo_nih_plug", "xtask", + "plugins/examples/byo_gui_gl", + "plugins/examples/byo_gui_softbuffer", + "plugins/examples/byo_gui_wgpu", "plugins/examples/gain", "plugins/examples/gain_gui_egui", "plugins/examples/gain_gui_iced", diff --git a/plugins/examples/byo_gui_gl/Cargo.toml b/plugins/examples/byo_gui_gl/Cargo.toml new file mode 100644 index 00000000..7d302465 --- /dev/null +++ b/plugins/examples/byo_gui_gl/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "byo_gui_gl" +version = "0.1.0" +edition = "2021" +authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"] +license = "ISC" + +description = "A simple example plugin with a raw OpenGL context for rendering" + +[lib] +# The `lib` artifact is needed for the standalone target +crate-type = ["cdylib", "lib"] + +[dependencies] +nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] } +baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0", features = ["opengl"] } +raw-window-handle = "0.5" +glow = "0.16" +crossbeam = "0.8" +atomic_float = "0.1" +# To make the state persistable +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/plugins/examples/byo_gui_gl/src/lib.rs b/plugins/examples/byo_gui_gl/src/lib.rs new file mode 100644 index 00000000..5f1eb932 --- /dev/null +++ b/plugins/examples/byo_gui_gl/src/lib.rs @@ -0,0 +1,553 @@ +//! This plugin demonstrates how to "bring your own GUI toolkit" using a raw OpenGL context. + +use baseview::{gl::GlConfig, WindowHandle, WindowOpenOptions, WindowScalePolicy}; +use crossbeam::atomic::AtomicCell; +use nih_plug::params::persist::PersistentField; +use nih_plug::prelude::*; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use serde::{Deserialize, Serialize}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence. +const PEAK_METER_DECAY_MS: f64 = 150.0; + +pub struct CustomGlWindow { + gui_context: Arc, + gl: Arc, + + vertex_array: glow::NativeVertexArray, + program: glow::NativeProgram, + + #[allow(unused)] + params: Arc, + #[allow(unused)] + peak_meter: Arc, +} + +impl Drop for CustomGlWindow { + fn drop(&mut self) { + use glow::HasContext as _; + + unsafe { + self.gl.delete_program(self.program); + self.gl.delete_vertex_array(self.vertex_array); + } + } +} + +impl CustomGlWindow { + fn new( + window: &mut baseview::Window<'_>, + gui_context: Arc, + params: Arc, + peak_meter: Arc, + _scaling_factor: f32, + ) -> Self { + use glow::HasContext as _; + + // TODO: Return an error instead of panicking once baseview gets thats + // ability. + let gl_context = window + .gl_context() + .expect("failed to get baseview gl context"); + + let (gl, vertex_array, program) = unsafe { + gl_context.make_current(); + + #[allow(clippy::arc_with_non_send_sync)] + let gl = Arc::new(glow::Context::from_loader_function(|s| { + gl_context.get_proc_address(s) + })); + + let vertex_array = gl + .create_vertex_array() + .expect("Cannot create vertex array"); + gl.bind_vertex_array(Some(vertex_array)); + + let program = gl.create_program().expect("Cannot create program"); + + let (vertex_shader_source, fragment_shader_source) = ( + r#"const vec2 verts[3] = vec2[3]( + vec2(0.5f, 1.0f), + vec2(0.0f, 0.0f), + vec2(1.0f, 0.0f) + ); + out vec2 vert; + void main() { + vert = verts[gl_VertexID]; + gl_Position = vec4(vert - 0.5, 0.0, 1.0); + }"#, + r#"precision mediump float; + in vec2 vert; + out vec4 color; + void main() { + color = vec4(vert, 0.5, 1.0); + }"#, + ); + + let shader_sources = [ + (glow::VERTEX_SHADER, vertex_shader_source), + (glow::FRAGMENT_SHADER, fragment_shader_source), + ]; + + let mut shaders = Vec::with_capacity(shader_sources.len()); + + for (shader_type, shader_source) in shader_sources.iter() { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + gl.shader_source(shader, &format!("{}\n{}", "#version 130", shader_source)); + gl.compile_shader(shader); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + gl.attach_shader(program, shader); + shaders.push(shader); + } + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + gl.use_program(Some(program)); + + gl_context.make_not_current(); + + (gl, vertex_array, program) + }; + + Self { + gui_context, + gl, + vertex_array, + program, + params, + peak_meter, + } + } +} + +impl baseview::WindowHandler for CustomGlWindow { + fn on_frame(&mut self, window: &mut baseview::Window) { + use glow::HasContext as _; + // Do rendering here. + + let (_width, _height) = self.params.editor_state.size(); + + let gl_context = window + .gl_context() + .expect("failed to get baseview gl context"); + + unsafe { + gl_context.make_current(); + + self.gl.clear_color(0.05, 0.05, 0.05, 1.0); + self.gl.clear(glow::COLOR_BUFFER_BIT); + + self.gl.draw_arrays(glow::TRIANGLES, 0, 3); + + gl_context.swap_buffers(); + gl_context.make_not_current(); + } + } + + fn on_event( + &mut self, + _window: &mut baseview::Window, + event: baseview::Event, + ) -> baseview::EventStatus { + // Use this to set parameter values. + let _param_setter = ParamSetter::new(self.gui_context.as_ref()); + + match &event { + // Do event processing here. + baseview::Event::Window(event) => match event { + baseview::WindowEvent::Resized(window_info) => { + self.params.editor_state.size.store(( + window_info.logical_size().width.round() as u32, + window_info.logical_size().height.round() as u32, + )); + } + _ => {} + }, + _ => {} + } + + baseview::EventStatus::Captured + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CustomGlEditorState { + /// The window's size in logical pixels before applying `scale_factor`. + #[serde(with = "nih_plug::params::persist::serialize_atomic_cell")] + size: AtomicCell<(u32, u32)>, + /// Whether the editor's window is currently open. + #[serde(skip)] + open: AtomicBool, +} + +impl CustomGlEditorState { + pub fn from_size(size: (u32, u32)) -> Arc { + Arc::new(Self { + size: AtomicCell::new(size), + open: AtomicBool::new(false), + }) + } + + /// Returns a `(width, height)` pair for the current size of the GUI in logical pixels. + pub fn size(&self) -> (u32, u32) { + self.size.load() + } + + /// Whether the GUI is currently visible. + // Called `is_open()` instead of `open()` to avoid the ambiguity. + pub fn is_open(&self) -> bool { + self.open.load(Ordering::Acquire) + } +} + +impl<'a> PersistentField<'a, CustomGlEditorState> for Arc { + fn set(&self, new_value: CustomGlEditorState) { + self.size.store(new_value.size.load()); + } + + fn map(&self, f: F) -> R + where + F: Fn(&CustomGlEditorState) -> R, + { + f(self) + } +} + +pub struct CustomGlEditor { + params: Arc, + peak_meter: Arc, + + /// The scaling factor reported by the host, if any. On macOS this will never be set and we + /// should use the system scaling factor instead. + scaling_factor: AtomicCell>, +} + +impl Editor for CustomGlEditor { + fn spawn( + &self, + parent: ParentWindowHandle, + context: Arc, + ) -> Box { + let (unscaled_width, unscaled_height) = self.params.editor_state.size(); + let scaling_factor = self.scaling_factor.load(); + + let gui_context = Arc::clone(&context); + + let params = Arc::clone(&self.params); + let peak_meter = Arc::clone(&self.peak_meter); + + let window = baseview::Window::open_parented( + &ParentWindowHandleAdapter(parent), + WindowOpenOptions { + title: String::from("OpenGL Window"), + // Baseview should be doing the DPI scaling for us + size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64), + // NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but + // not the mouse events. + scale: scaling_factor + .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) + .unwrap_or(WindowScalePolicy::SystemScaleFactor), + + gl_config: Some(GlConfig { + version: (3, 2), + red_bits: 8, + blue_bits: 8, + green_bits: 8, + alpha_bits: 8, + depth_bits: 24, + stencil_bits: 8, + samples: None, + srgb: true, + double_buffer: true, + vsync: false, + ..Default::default() + }), + }, + move |window: &mut baseview::Window<'_>| -> CustomGlWindow { + CustomGlWindow::new( + window, + gui_context, + params, + peak_meter, + scaling_factor.unwrap_or(1.0), + ) + }, + ); + + self.params.editor_state.open.store(true, Ordering::Release); + Box::new(CustomGlEditorHandle { + state: self.params.editor_state.clone(), + window, + }) + } + + fn size(&self) -> (u32, u32) { + self.params.editor_state.size() + } + + fn set_scale_factor(&self, factor: f32) -> bool { + // If the editor is currently open then the host must not change the current HiDPI scale as + // we don't have a way to handle that. Ableton Live does this. + if self.params.editor_state.is_open() { + return false; + } + + self.scaling_factor.store(Some(factor)); + true + } + + fn param_value_changed(&self, _id: &str, _normalized_value: f32) { + // As mentioned above, for now we'll always force a redraw to allow meter widgets to work + // correctly. In the future we can use an `Arc` and only force a redraw when + // that boolean is set. + } + + fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {} + + fn param_values_changed(&self) { + // Same + } +} + +/// The window handle used for [`EguiEditor`]. +struct CustomGlEditorHandle { + state: Arc, + window: WindowHandle, +} + +/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around +/// having this requirement? +unsafe impl Send for CustomGlEditorHandle {} + +impl Drop for CustomGlEditorHandle { + fn drop(&mut self) { + self.state.open.store(false, Ordering::Release); + // XXX: This should automatically happen when the handle gets dropped, but apparently not + self.window.close(); + } +} + +/// This version of `baseview` uses a different version of `raw_window_handle than NIH-plug, so we +/// need to adapt it ourselves. +struct ParentWindowHandleAdapter(nih_plug::editor::ParentWindowHandle); + +unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter { + fn raw_window_handle(&self) -> RawWindowHandle { + match self.0 { + ParentWindowHandle::X11Window(window) => { + let mut handle = raw_window_handle::XcbWindowHandle::empty(); + handle.window = window; + RawWindowHandle::Xcb(handle) + } + ParentWindowHandle::AppKitNsView(ns_view) => { + let mut handle = raw_window_handle::AppKitWindowHandle::empty(); + handle.ns_view = ns_view; + RawWindowHandle::AppKit(handle) + } + ParentWindowHandle::Win32Hwnd(hwnd) => { + let mut handle = raw_window_handle::Win32WindowHandle::empty(); + handle.hwnd = hwnd; + RawWindowHandle::Win32(handle) + } + } + } +} + +/// This is mostly identical to the gain example, minus some fluff, and with a GUI. +pub struct MyPlugin { + params: Arc, + + /// Needed to normalize the peak meter's response based on the sample rate. + peak_meter_decay_weight: f32, + /// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between + /// the GUI and the audio processing parts. If you have more state to share, then it's a good + /// idea to put all of that in a struct behind a single `Arc`. + /// + /// This is stored as voltage gain. + peak_meter: Arc, +} + +#[derive(Params)] +pub struct MyPluginParams { + /// The editor state, saved together with the parameter state so the custom scaling can be + /// restored. + #[persist = "editor-state"] + editor_state: Arc, + + #[id = "gain"] + pub gain: FloatParam, + + #[id = "foobar"] + pub some_int: IntParam, +} + +impl Default for MyPlugin { + fn default() -> Self { + Self { + params: Arc::new(MyPluginParams::default()), + + peak_meter_decay_weight: 1.0, + peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), + } + } +} + +impl Default for MyPluginParams { + fn default() -> Self { + Self { + editor_state: CustomGlEditorState::from_size((400, 300)), + + // See the main gain example for more details + gain: FloatParam::new( + "Gain", + util::db_to_gain(0.0), + FloatRange::Skewed { + min: util::db_to_gain(-30.0), + max: util::db_to_gain(30.0), + factor: FloatRange::gain_skew_factor(-30.0, 30.0), + }, + ) + .with_smoother(SmoothingStyle::Logarithmic(50.0)) + .with_unit(" dB") + .with_value_to_string(formatters::v2s_f32_gain_to_db(2)) + .with_string_to_value(formatters::s2v_f32_gain_to_db()), + some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }), + } + } +} + +impl Plugin for MyPlugin { + const NAME: &'static str = "BYO GUI Example (OpenGL)"; + const VENDOR: &'static str = "Moist Plugins GmbH"; + const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; + const EMAIL: &'static str = "info@example.com"; + + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + ..AudioIOLayout::const_default() + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + type SysExMessage = (); + type BackgroundTask = (); + + fn params(&self) -> Arc { + self.params.clone() + } + + fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { + Some(Box::new(CustomGlEditor { + params: Arc::clone(&self.params), + peak_meter: Arc::clone(&self.peak_meter), + + // TODO: We can't get the size of the window when baseview does its own scaling, so if the + // host does not set a scale factor on Windows or Linux we should just use a factor of + // 1. That may make the GUI tiny but it also prevents it from getting cut off. + #[cfg(target_os = "macos")] + scaling_factor: AtomicCell::new(None), + #[cfg(not(target_os = "macos"))] + scaling_factor: AtomicCell::new(Some(1.0)), + })) + } + + fn initialize( + &mut self, + _audio_io_layout: &AudioIOLayout, + buffer_config: &BufferConfig, + _context: &mut impl InitContext, + ) -> bool { + // After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should + // have dropped by 12 dB + self.peak_meter_decay_weight = 0.25f64 + .powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip()) + as f32; + + true + } + + fn process( + &mut self, + buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + _context: &mut impl ProcessContext, + ) -> ProcessStatus { + for channel_samples in buffer.iter_samples() { + let mut amplitude = 0.0; + let num_samples = channel_samples.len(); + + let gain = self.params.gain.smoothed.next(); + for sample in channel_samples { + *sample *= gain; + amplitude += *sample; + } + + // To save resources, a plugin can (and probably should!) only perform expensive + // calculations that are only displayed on the GUI while the GUI is open + if self.params.editor_state.is_open() { + amplitude = (amplitude / num_samples as f32).abs(); + let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed); + let new_peak_meter = if amplitude > current_peak_meter { + amplitude + } else { + current_peak_meter * self.peak_meter_decay_weight + + amplitude * (1.0 - self.peak_meter_decay_weight) + }; + + self.peak_meter + .store(new_peak_meter, std::sync::atomic::Ordering::Relaxed) + } + } + + ProcessStatus::Normal + } +} + +impl ClapPlugin for MyPlugin { + const CLAP_ID: &'static str = "com.moist-plugins-gmbh.byo-gui-gl"; + const CLAP_DESCRIPTION: Option<&'static str> = + Some("A simple example plugin with a raw OpenGL context for rendering"); + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Stereo, + ClapFeature::Mono, + ClapFeature::Utility, + ]; +} + +impl Vst3Plugin for MyPlugin { + const VST3_CLASS_ID: [u8; 16] = *b"ByoGuiOpenGLWooo"; + const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = + &[Vst3SubCategory::Fx, Vst3SubCategory::Tools]; +} + +nih_export_clap!(MyPlugin); +nih_export_vst3!(MyPlugin); diff --git a/plugins/examples/byo_gui_gl/src/main.rs b/plugins/examples/byo_gui_gl/src/main.rs new file mode 100644 index 00000000..0468397b --- /dev/null +++ b/plugins/examples/byo_gui_gl/src/main.rs @@ -0,0 +1,7 @@ +use nih_plug::prelude::*; + +use byo_gui_gl::MyPlugin; + +fn main() { + nih_export_standalone::(); +} diff --git a/plugins/examples/byo_gui_softbuffer/Cargo.toml b/plugins/examples/byo_gui_softbuffer/Cargo.toml new file mode 100644 index 00000000..4fa27ea1 --- /dev/null +++ b/plugins/examples/byo_gui_softbuffer/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "byo_gui_softbuffer" +version = "0.1.0" +edition = "2021" +authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"] +license = "ISC" + +description = "A simple example plugin with a raw Softbuffer context for rendering" + +[lib] +# The `lib` artifact is needed for the standalone target +crate-type = ["cdylib", "lib"] + +[dependencies] +nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] } +baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" } +softbuffer = { version = "0.4.6", default-features = false, features = ["kms", "x11"]} +raw-window-handle = "0.5" +raw-window-handle-06 = { package = "raw-window-handle", version = "0.6" } +crossbeam = "0.8" +atomic_float = "0.1" +# To make the state persistable +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/plugins/examples/byo_gui_softbuffer/src/lib.rs b/plugins/examples/byo_gui_softbuffer/src/lib.rs new file mode 100644 index 00000000..2d65108d --- /dev/null +++ b/plugins/examples/byo_gui_softbuffer/src/lib.rs @@ -0,0 +1,585 @@ +//! This plugin demonstrates how to "bring your own GUI toolkit" using a raw Softbuffer rendering context. + +use baseview::{WindowHandle, WindowOpenOptions, WindowScalePolicy}; +use crossbeam::atomic::AtomicCell; +use nih_plug::params::persist::PersistentField; +use nih_plug::prelude::*; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use serde::{Deserialize, Serialize}; +use std::{ + num::NonZeroIsize, + ptr::NonNull, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence. +const PEAK_METER_DECAY_MS: f64 = 150.0; + +pub struct CustomSoftbufferWindow { + gui_context: Arc, + + _sb_context: softbuffer::Context, + sb_surface: softbuffer::Surface, + + physical_width: u32, + physical_height: u32, + + #[allow(unused)] + params: Arc, + #[allow(unused)] + peak_meter: Arc, +} + +impl CustomSoftbufferWindow { + fn new( + window: &mut baseview::Window<'_>, + gui_context: Arc, + params: Arc, + peak_meter: Arc, + scaling_factor: f32, + ) -> Self { + let (unscaled_width, unscaled_height) = params.editor_state.size(); + let physical_width = (unscaled_width as f64 * scaling_factor as f64).round() as u32; + let physical_height = (unscaled_height as f64 * scaling_factor as f64).round() as u32; + + let target = baseview_window_to_surface_target(window); + + let sb_context = + softbuffer::Context::new(target.clone()).expect("could not get softbuffer context"); + let mut sb_surface = softbuffer::Surface::new(&sb_context, target) + .expect("could not create softbuffer surface"); + + sb_surface + .resize( + NonZeroU32::new(physical_width).unwrap(), + NonZeroU32::new(physical_height).unwrap(), + ) + .unwrap(); + + Self { + gui_context, + _sb_context: sb_context, + sb_surface, + physical_width, + physical_height, + params, + peak_meter, + } + } +} + +impl baseview::WindowHandler for CustomSoftbufferWindow { + fn on_frame(&mut self, _window: &mut baseview::Window) { + // Do rendering here. + + let mut buffer = self.sb_surface.buffer_mut().unwrap(); + for y in 0..self.physical_height { + for x in 0..self.physical_width { + let red = x % 255; + let green = y % 255; + let blue = (x * y) % 255; + let index = y as usize * self.physical_width as usize + x as usize; + buffer[index] = blue | (green << 8) | (red << 16); + } + } + + buffer.present().unwrap(); + } + + fn on_event( + &mut self, + _window: &mut baseview::Window, + event: baseview::Event, + ) -> baseview::EventStatus { + // Use this to set parameter values. + let _param_setter = ParamSetter::new(self.gui_context.as_ref()); + + match &event { + // Do event processing here. + baseview::Event::Window(event) => match event { + baseview::WindowEvent::Resized(window_info) => { + self.params.editor_state.size.store(( + window_info.logical_size().width.round() as u32, + window_info.logical_size().height.round() as u32, + )); + + self.physical_width = window_info.physical_size().width; + self.physical_height = window_info.physical_size().height; + + self.sb_surface + .resize( + NonZeroU32::new(self.physical_width).unwrap(), + NonZeroU32::new(self.physical_height).unwrap(), + ) + .unwrap(); + } + _ => {} + }, + _ => {} + } + + baseview::EventStatus::Captured + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CustomSoftbufferEditorState { + /// The window's size in logical pixels before applying `scale_factor`. + #[serde(with = "nih_plug::params::persist::serialize_atomic_cell")] + size: AtomicCell<(u32, u32)>, + /// Whether the editor's window is currently open. + #[serde(skip)] + open: AtomicBool, +} + +impl CustomSoftbufferEditorState { + pub fn from_size(size: (u32, u32)) -> Arc { + Arc::new(Self { + size: AtomicCell::new(size), + open: AtomicBool::new(false), + }) + } + + /// Returns a `(width, height)` pair for the current size of the GUI in logical pixels. + pub fn size(&self) -> (u32, u32) { + self.size.load() + } + + /// Whether the GUI is currently visible. + // Called `is_open()` instead of `open()` to avoid the ambiguity. + pub fn is_open(&self) -> bool { + self.open.load(Ordering::Acquire) + } +} + +impl<'a> PersistentField<'a, CustomSoftbufferEditorState> for Arc { + fn set(&self, new_value: CustomSoftbufferEditorState) { + self.size.store(new_value.size.load()); + } + + fn map(&self, f: F) -> R + where + F: Fn(&CustomSoftbufferEditorState) -> R, + { + f(self) + } +} + +pub struct CustomSoftbufferEditor { + params: Arc, + peak_meter: Arc, + + /// The scaling factor reported by the host, if any. On macOS this will never be set and we + /// should use the system scaling factor instead. + scaling_factor: AtomicCell>, +} + +impl Editor for CustomSoftbufferEditor { + fn spawn( + &self, + parent: ParentWindowHandle, + context: Arc, + ) -> Box { + let (unscaled_width, unscaled_height) = self.params.editor_state.size(); + let scaling_factor = self.scaling_factor.load(); + + let gui_context = Arc::clone(&context); + + let params = Arc::clone(&self.params); + let peak_meter = Arc::clone(&self.peak_meter); + + let window = baseview::Window::open_parented( + &ParentWindowHandleAdapter(parent), + WindowOpenOptions { + title: String::from("Softbuffer Window"), + // Baseview should be doing the DPI scaling for us + size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64), + // NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but + // not the mouse events. + scale: scaling_factor + .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) + .unwrap_or(WindowScalePolicy::SystemScaleFactor), + }, + move |window: &mut baseview::Window<'_>| -> CustomSoftbufferWindow { + CustomSoftbufferWindow::new( + window, + gui_context, + params, + peak_meter, + scaling_factor.unwrap_or(1.0), + ) + }, + ); + + self.params.editor_state.open.store(true, Ordering::Release); + Box::new(CustomSoftbufferEditorHandle { + state: self.params.editor_state.clone(), + window, + }) + } + + fn size(&self) -> (u32, u32) { + self.params.editor_state.size() + } + + fn set_scale_factor(&self, factor: f32) -> bool { + // If the editor is currently open then the host must not change the current HiDPI scale as + // we don't have a way to handle that. Ableton Live does this. + if self.params.editor_state.is_open() { + return false; + } + + self.scaling_factor.store(Some(factor)); + true + } + + fn param_value_changed(&self, _id: &str, _normalized_value: f32) { + // As mentioned above, for now we'll always force a redraw to allow meter widgets to work + // correctly. In the future we can use an `Arc` and only force a redraw when + // that boolean is set. + } + + fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {} + + fn param_values_changed(&self) { + // Same + } +} + +/// The window handle used for [`EguiEditor`]. +struct CustomSoftbufferEditorHandle { + state: Arc, + window: WindowHandle, +} + +/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around +/// having this requirement? +unsafe impl Send for CustomSoftbufferEditorHandle {} + +impl Drop for CustomSoftbufferEditorHandle { + fn drop(&mut self) { + self.state.open.store(false, Ordering::Release); + // XXX: This should automatically happen when the handle gets dropped, but apparently not + self.window.close(); + } +} + +/// This version of `baseview` uses a different version of `raw_window_handle than NIH-plug, so we +/// need to adapt it ourselves. +struct ParentWindowHandleAdapter(nih_plug::editor::ParentWindowHandle); + +unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter { + fn raw_window_handle(&self) -> RawWindowHandle { + match self.0 { + ParentWindowHandle::X11Window(window) => { + let mut handle = raw_window_handle::XcbWindowHandle::empty(); + handle.window = window; + RawWindowHandle::Xcb(handle) + } + ParentWindowHandle::AppKitNsView(ns_view) => { + let mut handle = raw_window_handle::AppKitWindowHandle::empty(); + handle.ns_view = ns_view; + RawWindowHandle::AppKit(handle) + } + ParentWindowHandle::Win32Hwnd(hwnd) => { + let mut handle = raw_window_handle::Win32WindowHandle::empty(); + handle.hwnd = hwnd; + RawWindowHandle::Win32(handle) + } + } + } +} + +/// Softbuffer uses raw_window_handle v6, but baseview uses raw_window_handle v5, so we need to +/// adapt it ourselves. +#[derive(Clone)] +struct SoftbufferWindowHandleAdapter { + raw_display_handle: raw_window_handle_06::RawDisplayHandle, + raw_window_handle: raw_window_handle_06::RawWindowHandle, +} + +impl raw_window_handle_06::HasDisplayHandle for SoftbufferWindowHandleAdapter { + fn display_handle( + &self, + ) -> Result, raw_window_handle_06::HandleError> { + unsafe { + Ok(raw_window_handle_06::DisplayHandle::borrow_raw( + self.raw_display_handle, + )) + } + } +} + +impl raw_window_handle_06::HasWindowHandle for SoftbufferWindowHandleAdapter { + fn window_handle( + &self, + ) -> Result, raw_window_handle_06::HandleError> { + unsafe { + Ok(raw_window_handle_06::WindowHandle::borrow_raw( + self.raw_window_handle, + )) + } + } +} + +fn baseview_window_to_surface_target( + window: &baseview::Window<'_>, +) -> SoftbufferWindowHandleAdapter { + use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + + let raw_display_handle = window.raw_display_handle(); + let raw_window_handle = window.raw_window_handle(); + + SoftbufferWindowHandleAdapter { + raw_display_handle: match raw_display_handle { + raw_window_handle::RawDisplayHandle::AppKit(_) => { + raw_window_handle_06::RawDisplayHandle::AppKit( + raw_window_handle_06::AppKitDisplayHandle::new(), + ) + } + raw_window_handle::RawDisplayHandle::Xlib(handle) => { + raw_window_handle_06::RawDisplayHandle::Xlib( + raw_window_handle_06::XlibDisplayHandle::new( + NonNull::new(handle.display), + handle.screen, + ), + ) + } + raw_window_handle::RawDisplayHandle::Xcb(handle) => { + raw_window_handle_06::RawDisplayHandle::Xcb( + raw_window_handle_06::XcbDisplayHandle::new( + NonNull::new(handle.connection), + handle.screen, + ), + ) + } + raw_window_handle::RawDisplayHandle::Windows(_) => { + raw_window_handle_06::RawDisplayHandle::Windows( + raw_window_handle_06::WindowsDisplayHandle::new(), + ) + } + _ => todo!(), + }, + raw_window_handle: match raw_window_handle { + raw_window_handle::RawWindowHandle::AppKit(handle) => { + raw_window_handle_06::RawWindowHandle::AppKit( + raw_window_handle_06::AppKitWindowHandle::new( + NonNull::new(handle.ns_view).unwrap(), + ), + ) + } + raw_window_handle::RawWindowHandle::Xlib(handle) => { + raw_window_handle_06::RawWindowHandle::Xlib( + raw_window_handle_06::XlibWindowHandle::new(handle.window), + ) + } + raw_window_handle::RawWindowHandle::Xcb(handle) => { + raw_window_handle_06::RawWindowHandle::Xcb( + raw_window_handle_06::XcbWindowHandle::new( + NonZeroU32::new(handle.window).unwrap(), + ), + ) + } + raw_window_handle::RawWindowHandle::Win32(handle) => { + // will this work? i have no idea! + let mut raw_handle = raw_window_handle_06::Win32WindowHandle::new( + NonZeroIsize::new(handle.hwnd as isize).unwrap(), + ); + + raw_handle.hinstance = handle + .hinstance + .is_null() + .then(|| NonZeroIsize::new(handle.hinstance as isize).unwrap()); + + raw_window_handle_06::RawWindowHandle::Win32(raw_handle) + } + _ => todo!(), + }, + } +} + +/// This is mostly identical to the gain example, minus some fluff, and with a GUI. +pub struct MyPlugin { + params: Arc, + + /// Needed to normalize the peak meter's response based on the sample rate. + peak_meter_decay_weight: f32, + /// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between + /// the GUI and the audio processing parts. If you have more state to share, then it's a good + /// idea to put all of that in a struct behind a single `Arc`. + /// + /// This is stored as voltage gain. + peak_meter: Arc, +} + +#[derive(Params)] +pub struct MyPluginParams { + /// The editor state, saved together with the parameter state so the custom scaling can be + /// restored. + #[persist = "editor-state"] + editor_state: Arc, + + #[id = "gain"] + pub gain: FloatParam, + + #[id = "foobar"] + pub some_int: IntParam, +} + +impl Default for MyPlugin { + fn default() -> Self { + Self { + params: Arc::new(MyPluginParams::default()), + + peak_meter_decay_weight: 1.0, + peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), + } + } +} + +impl Default for MyPluginParams { + fn default() -> Self { + Self { + editor_state: CustomSoftbufferEditorState::from_size((200, 150)), + + // See the main gain example for more details + gain: FloatParam::new( + "Gain", + util::db_to_gain(0.0), + FloatRange::Skewed { + min: util::db_to_gain(-30.0), + max: util::db_to_gain(30.0), + factor: FloatRange::gain_skew_factor(-30.0, 30.0), + }, + ) + .with_smoother(SmoothingStyle::Logarithmic(50.0)) + .with_unit(" dB") + .with_value_to_string(formatters::v2s_f32_gain_to_db(2)) + .with_string_to_value(formatters::s2v_f32_gain_to_db()), + some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }), + } + } +} + +impl Plugin for MyPlugin { + const NAME: &'static str = "BYO GUI Example (Softbuffer)"; + const VENDOR: &'static str = "Moist Plugins GmbH"; + const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; + const EMAIL: &'static str = "info@example.com"; + + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + ..AudioIOLayout::const_default() + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + type SysExMessage = (); + type BackgroundTask = (); + + fn params(&self) -> Arc { + self.params.clone() + } + + fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { + Some(Box::new(CustomSoftbufferEditor { + params: Arc::clone(&self.params), + peak_meter: Arc::clone(&self.peak_meter), + + // TODO: We can't get the size of the window when baseview does its own scaling, so if the + // host does not set a scale factor on Windows or Linux we should just use a factor of + // 1. That may make the GUI tiny but it also prevents it from getting cut off. + #[cfg(target_os = "macos")] + scaling_factor: AtomicCell::new(None), + #[cfg(not(target_os = "macos"))] + scaling_factor: AtomicCell::new(Some(1.0)), + })) + } + + fn initialize( + &mut self, + _audio_io_layout: &AudioIOLayout, + buffer_config: &BufferConfig, + _context: &mut impl InitContext, + ) -> bool { + // After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should + // have dropped by 12 dB + self.peak_meter_decay_weight = 0.25f64 + .powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip()) + as f32; + + true + } + + fn process( + &mut self, + buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + _context: &mut impl ProcessContext, + ) -> ProcessStatus { + for channel_samples in buffer.iter_samples() { + let mut amplitude = 0.0; + let num_samples = channel_samples.len(); + + let gain = self.params.gain.smoothed.next(); + for sample in channel_samples { + *sample *= gain; + amplitude += *sample; + } + + // To save resources, a plugin can (and probably should!) only perform expensive + // calculations that are only displayed on the GUI while the GUI is open + if self.params.editor_state.is_open() { + amplitude = (amplitude / num_samples as f32).abs(); + let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed); + let new_peak_meter = if amplitude > current_peak_meter { + amplitude + } else { + current_peak_meter * self.peak_meter_decay_weight + + amplitude * (1.0 - self.peak_meter_decay_weight) + }; + + self.peak_meter + .store(new_peak_meter, std::sync::atomic::Ordering::Relaxed) + } + } + + ProcessStatus::Normal + } +} + +impl ClapPlugin for MyPlugin { + const CLAP_ID: &'static str = "com.moist-plugins-gmbh.byo-gui-softbuffer"; + const CLAP_DESCRIPTION: Option<&'static str> = + Some("A simple example plugin with a raw Softbuffer context for rendering"); + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Stereo, + ClapFeature::Mono, + ClapFeature::Utility, + ]; +} + +impl Vst3Plugin for MyPlugin { + const VST3_CLASS_ID: [u8; 16] = *b"ByoGuiSoftbuffer"; + const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = + &[Vst3SubCategory::Fx, Vst3SubCategory::Tools]; +} + +nih_export_clap!(MyPlugin); +nih_export_vst3!(MyPlugin); diff --git a/plugins/examples/byo_gui_softbuffer/src/main.rs b/plugins/examples/byo_gui_softbuffer/src/main.rs new file mode 100644 index 00000000..625da05e --- /dev/null +++ b/plugins/examples/byo_gui_softbuffer/src/main.rs @@ -0,0 +1,7 @@ +use nih_plug::prelude::*; + +use byo_gui_softbuffer::MyPlugin; + +fn main() { + nih_export_standalone::(); +} diff --git a/plugins/examples/byo_gui_wgpu/Cargo.toml b/plugins/examples/byo_gui_wgpu/Cargo.toml new file mode 100644 index 00000000..1cd58bf9 --- /dev/null +++ b/plugins/examples/byo_gui_wgpu/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "byo_gui_wgpu" +version = "0.1.0" +edition = "2021" +authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"] +license = "ISC" + +description = "A simple example plugin with a raw WGPU context for rendering" + +[lib] +# The `lib` artifact is needed for the standalone target +crate-type = ["cdylib", "lib"] + +[dependencies] +nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] } +baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" } +wgpu = "23" +raw-window-handle = "0.5" +raw-window-handle-06 = { package = "raw-window-handle", version = "0.6" } +pollster = "0.4.0" +crossbeam = "0.8" +atomic_float = "0.1" +# To make the state persistable +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/plugins/examples/byo_gui_wgpu/src/lib.rs b/plugins/examples/byo_gui_wgpu/src/lib.rs new file mode 100644 index 00000000..0c533323 --- /dev/null +++ b/plugins/examples/byo_gui_wgpu/src/lib.rs @@ -0,0 +1,673 @@ +//! This plugin demonstrates how to "bring your own GUI toolkit" using a raw WGPU context. + +use baseview::{WindowHandle, WindowOpenOptions, WindowScalePolicy}; +use crossbeam::atomic::AtomicCell; +use nih_plug::params::persist::PersistentField; +use nih_plug::prelude::*; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + num::NonZeroIsize, + ptr::NonNull, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use wgpu::SurfaceTargetUnsafe; + +/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence. +const PEAK_METER_DECAY_MS: f64 = 150.0; + +pub struct CustomWgpuWindow { + gui_context: Arc, + + device: wgpu::Device, + queue: wgpu::Queue, + pipeline: wgpu::RenderPipeline, + surface: wgpu::Surface<'static>, + surface_config: wgpu::SurfaceConfiguration, + + #[allow(unused)] + params: Arc, + #[allow(unused)] + peak_meter: Arc, +} + +impl CustomWgpuWindow { + fn new( + window: &mut baseview::Window<'_>, + gui_context: Arc, + params: Arc, + peak_meter: Arc, + scaling_factor: f32, + ) -> Self { + let target = baseview_window_to_surface_target(window); + + pollster::block_on(Self::create( + target, + gui_context, + params, + peak_meter, + scaling_factor, + )) + } + + async fn create( + target: SurfaceTargetUnsafe, + gui_context: Arc, + params: Arc, + peak_meter: Arc, + scaling_factor: f32, + ) -> Self { + let (unscaled_width, unscaled_height) = params.editor_state.size(); + let width = (unscaled_width as f64 * scaling_factor as f64).round() as u32; + let height = (unscaled_height as f64 * scaling_factor as f64).round() as u32; + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); + + let surface = unsafe { instance.create_surface_unsafe(target) }.unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::LowPower, + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropriate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + memory_hints: wgpu::MemoryHints::MemoryUsage, + }, + None, + ) + .await + .expect("Failed to create device"); + + const SHADER: &str = " + const VERTS = array( + vec2(0.5, 1.0), + vec2(0.0, 0.0), + vec2(1.0, 0.0) + ); + + struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) position: vec2, + }; + + @vertex + fn vs_main( + @builtin(vertex_index) in_vertex_index: u32, + ) -> VertexOutput { + var out: VertexOutput; + out.position = VERTS[in_vertex_index]; + out.clip_position = vec4(out.position - 0.5, 0.0, 1.0); + return out; + } + + @fragment + fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(in.position, 0.5, 1.0); + } + "; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(SHADER)), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(swapchain_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let surface_config = surface.get_default_config(&adapter, width, height).unwrap(); + surface.configure(&device, &surface_config); + + Self { + gui_context, + device, + queue, + pipeline, + surface, + surface_config, + params, + peak_meter, + } + } +} + +impl baseview::WindowHandler for CustomWgpuWindow { + fn on_frame(&mut self, _window: &mut baseview::Window) { + // Do rendering here. + + let frame = self + .surface + .get_current_texture() + .expect("Failed to acquire next swap chain texture"); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.draw(0..3, 0..1); + } + + self.queue.submit(Some(encoder.finish())); + frame.present(); + } + + fn on_event( + &mut self, + _window: &mut baseview::Window, + event: baseview::Event, + ) -> baseview::EventStatus { + // Use this to set parameter values. + let _param_setter = ParamSetter::new(self.gui_context.as_ref()); + + match &event { + // Do event processing here. + baseview::Event::Window(event) => match event { + baseview::WindowEvent::Resized(window_info) => { + self.params.editor_state.size.store(( + window_info.logical_size().width.round() as u32, + window_info.logical_size().height.round() as u32, + )); + + self.surface_config.width = window_info.physical_size().width; + self.surface_config.height = window_info.physical_size().height; + + self.surface.configure(&self.device, &self.surface_config); + } + _ => {} + }, + _ => {} + } + + baseview::EventStatus::Captured + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CustomWgpuEditorState { + /// The window's size in logical pixels before applying `scale_factor`. + #[serde(with = "nih_plug::params::persist::serialize_atomic_cell")] + size: AtomicCell<(u32, u32)>, + /// Whether the editor's window is currently open. + #[serde(skip)] + open: AtomicBool, +} + +impl CustomWgpuEditorState { + pub fn from_size(size: (u32, u32)) -> Arc { + Arc::new(Self { + size: AtomicCell::new(size), + open: AtomicBool::new(false), + }) + } + + /// Returns a `(width, height)` pair for the current size of the GUI in logical pixels. + pub fn size(&self) -> (u32, u32) { + self.size.load() + } + + /// Whether the GUI is currently visible. + // Called `is_open()` instead of `open()` to avoid the ambiguity. + pub fn is_open(&self) -> bool { + self.open.load(Ordering::Acquire) + } +} + +impl<'a> PersistentField<'a, CustomWgpuEditorState> for Arc { + fn set(&self, new_value: CustomWgpuEditorState) { + self.size.store(new_value.size.load()); + } + + fn map(&self, f: F) -> R + where + F: Fn(&CustomWgpuEditorState) -> R, + { + f(self) + } +} + +pub struct CustomWgpuEditor { + params: Arc, + peak_meter: Arc, + + /// The scaling factor reported by the host, if any. On macOS this will never be set and we + /// should use the system scaling factor instead. + scaling_factor: AtomicCell>, +} + +impl Editor for CustomWgpuEditor { + fn spawn( + &self, + parent: ParentWindowHandle, + context: Arc, + ) -> Box { + let (unscaled_width, unscaled_height) = self.params.editor_state.size(); + let scaling_factor = self.scaling_factor.load(); + + let gui_context = Arc::clone(&context); + + let params = Arc::clone(&self.params); + let peak_meter = Arc::clone(&self.peak_meter); + + let window = baseview::Window::open_parented( + &ParentWindowHandleAdapter(parent), + WindowOpenOptions { + title: String::from("WGPU Window"), + // Baseview should be doing the DPI scaling for us + size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64), + // NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but + // not the mouse events. + scale: scaling_factor + .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) + .unwrap_or(WindowScalePolicy::SystemScaleFactor), + }, + move |window: &mut baseview::Window<'_>| -> CustomWgpuWindow { + CustomWgpuWindow::new( + window, + gui_context, + params, + peak_meter, + scaling_factor.unwrap_or(1.0), + ) + }, + ); + + self.params.editor_state.open.store(true, Ordering::Release); + Box::new(CustomWgpuEditorHandle { + state: self.params.editor_state.clone(), + window, + }) + } + + fn size(&self) -> (u32, u32) { + self.params.editor_state.size() + } + + fn set_scale_factor(&self, factor: f32) -> bool { + // If the editor is currently open then the host must not change the current HiDPI scale as + // we don't have a way to handle that. Ableton Live does this. + if self.params.editor_state.is_open() { + return false; + } + + self.scaling_factor.store(Some(factor)); + true + } + + fn param_value_changed(&self, _id: &str, _normalized_value: f32) { + // As mentioned above, for now we'll always force a redraw to allow meter widgets to work + // correctly. In the future we can use an `Arc` and only force a redraw when + // that boolean is set. + } + + fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {} + + fn param_values_changed(&self) { + // Same + } +} + +/// The window handle used for [`EguiEditor`]. +struct CustomWgpuEditorHandle { + state: Arc, + window: WindowHandle, +} + +/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around +/// having this requirement? +unsafe impl Send for CustomWgpuEditorHandle {} + +impl Drop for CustomWgpuEditorHandle { + fn drop(&mut self) { + self.state.open.store(false, Ordering::Release); + // XXX: This should automatically happen when the handle gets dropped, but apparently not + self.window.close(); + } +} + +/// This version of `baseview` uses a different version of `raw_window_handle than NIH-plug, so we +/// need to adapt it ourselves. +struct ParentWindowHandleAdapter(nih_plug::editor::ParentWindowHandle); + +unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter { + fn raw_window_handle(&self) -> RawWindowHandle { + match self.0 { + ParentWindowHandle::X11Window(window) => { + let mut handle = raw_window_handle::XcbWindowHandle::empty(); + handle.window = window; + RawWindowHandle::Xcb(handle) + } + ParentWindowHandle::AppKitNsView(ns_view) => { + let mut handle = raw_window_handle::AppKitWindowHandle::empty(); + handle.ns_view = ns_view; + RawWindowHandle::AppKit(handle) + } + ParentWindowHandle::Win32Hwnd(hwnd) => { + let mut handle = raw_window_handle::Win32WindowHandle::empty(); + handle.hwnd = hwnd; + RawWindowHandle::Win32(handle) + } + } + } +} + +/// WGPU uses raw_window_handle v6, but baseview uses raw_window_handle v5, so manually convert it. +fn baseview_window_to_surface_target(window: &baseview::Window<'_>) -> wgpu::SurfaceTargetUnsafe { + use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + + let raw_display_handle = window.raw_display_handle(); + let raw_window_handle = window.raw_window_handle(); + + wgpu::SurfaceTargetUnsafe::RawHandle { + raw_display_handle: match raw_display_handle { + raw_window_handle::RawDisplayHandle::AppKit(_) => { + raw_window_handle_06::RawDisplayHandle::AppKit( + raw_window_handle_06::AppKitDisplayHandle::new(), + ) + } + raw_window_handle::RawDisplayHandle::Xlib(handle) => { + raw_window_handle_06::RawDisplayHandle::Xlib( + raw_window_handle_06::XlibDisplayHandle::new( + NonNull::new(handle.display), + handle.screen, + ), + ) + } + raw_window_handle::RawDisplayHandle::Xcb(handle) => { + raw_window_handle_06::RawDisplayHandle::Xcb( + raw_window_handle_06::XcbDisplayHandle::new( + NonNull::new(handle.connection), + handle.screen, + ), + ) + } + raw_window_handle::RawDisplayHandle::Windows(_) => { + raw_window_handle_06::RawDisplayHandle::Windows( + raw_window_handle_06::WindowsDisplayHandle::new(), + ) + } + _ => todo!(), + }, + raw_window_handle: match raw_window_handle { + raw_window_handle::RawWindowHandle::AppKit(handle) => { + raw_window_handle_06::RawWindowHandle::AppKit( + raw_window_handle_06::AppKitWindowHandle::new( + NonNull::new(handle.ns_view).unwrap(), + ), + ) + } + raw_window_handle::RawWindowHandle::Xlib(handle) => { + raw_window_handle_06::RawWindowHandle::Xlib( + raw_window_handle_06::XlibWindowHandle::new(handle.window), + ) + } + raw_window_handle::RawWindowHandle::Xcb(handle) => { + raw_window_handle_06::RawWindowHandle::Xcb( + raw_window_handle_06::XcbWindowHandle::new( + NonZeroU32::new(handle.window).unwrap(), + ), + ) + } + raw_window_handle::RawWindowHandle::Win32(handle) => { + // will this work? i have no idea! + let mut raw_handle = raw_window_handle_06::Win32WindowHandle::new( + NonZeroIsize::new(handle.hwnd as isize).unwrap(), + ); + + raw_handle.hinstance = handle + .hinstance + .is_null() + .then(|| NonZeroIsize::new(handle.hinstance as isize).unwrap()); + + raw_window_handle_06::RawWindowHandle::Win32(raw_handle) + } + _ => todo!(), + }, + } +} + +/// This is mostly identical to the gain example, minus some fluff, and with a GUI. +pub struct MyPlugin { + params: Arc, + + /// Needed to normalize the peak meter's response based on the sample rate. + peak_meter_decay_weight: f32, + /// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between + /// the GUI and the audio processing parts. If you have more state to share, then it's a good + /// idea to put all of that in a struct behind a single `Arc`. + /// + /// This is stored as voltage gain. + peak_meter: Arc, +} + +#[derive(Params)] +pub struct MyPluginParams { + /// The editor state, saved together with the parameter state so the custom scaling can be + /// restored. + #[persist = "editor-state"] + editor_state: Arc, + + #[id = "gain"] + pub gain: FloatParam, + + #[id = "foobar"] + pub some_int: IntParam, +} + +impl Default for MyPlugin { + fn default() -> Self { + Self { + params: Arc::new(MyPluginParams::default()), + + peak_meter_decay_weight: 1.0, + peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)), + } + } +} + +impl Default for MyPluginParams { + fn default() -> Self { + Self { + editor_state: CustomWgpuEditorState::from_size((400, 300)), + + // See the main gain example for more details + gain: FloatParam::new( + "Gain", + util::db_to_gain(0.0), + FloatRange::Skewed { + min: util::db_to_gain(-30.0), + max: util::db_to_gain(30.0), + factor: FloatRange::gain_skew_factor(-30.0, 30.0), + }, + ) + .with_smoother(SmoothingStyle::Logarithmic(50.0)) + .with_unit(" dB") + .with_value_to_string(formatters::v2s_f32_gain_to_db(2)) + .with_string_to_value(formatters::s2v_f32_gain_to_db()), + some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }), + } + } +} + +impl Plugin for MyPlugin { + const NAME: &'static str = "BYO GUI Example (WGPU)"; + const VENDOR: &'static str = "Moist Plugins GmbH"; + const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ"; + const EMAIL: &'static str = "info@example.com"; + + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + ..AudioIOLayout::const_default() + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + + type SysExMessage = (); + type BackgroundTask = (); + + fn params(&self) -> Arc { + self.params.clone() + } + + fn editor(&mut self, _async_executor: AsyncExecutor) -> Option> { + Some(Box::new(CustomWgpuEditor { + params: Arc::clone(&self.params), + peak_meter: Arc::clone(&self.peak_meter), + + // TODO: We can't get the size of the window when baseview does its own scaling, so if the + // host does not set a scale factor on Windows or Linux we should just use a factor of + // 1. That may make the GUI tiny but it also prevents it from getting cut off. + #[cfg(target_os = "macos")] + scaling_factor: AtomicCell::new(None), + #[cfg(not(target_os = "macos"))] + scaling_factor: AtomicCell::new(Some(1.0)), + })) + } + + fn initialize( + &mut self, + _audio_io_layout: &AudioIOLayout, + buffer_config: &BufferConfig, + _context: &mut impl InitContext, + ) -> bool { + // TODO: Figure out a way to disable log spam from wgpu. + + // After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should + // have dropped by 12 dB + self.peak_meter_decay_weight = 0.25f64 + .powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip()) + as f32; + + true + } + + fn process( + &mut self, + buffer: &mut Buffer, + _aux: &mut AuxiliaryBuffers, + _context: &mut impl ProcessContext, + ) -> ProcessStatus { + for channel_samples in buffer.iter_samples() { + let mut amplitude = 0.0; + let num_samples = channel_samples.len(); + + let gain = self.params.gain.smoothed.next(); + for sample in channel_samples { + *sample *= gain; + amplitude += *sample; + } + + // To save resources, a plugin can (and probably should!) only perform expensive + // calculations that are only displayed on the GUI while the GUI is open + if self.params.editor_state.is_open() { + amplitude = (amplitude / num_samples as f32).abs(); + let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed); + let new_peak_meter = if amplitude > current_peak_meter { + amplitude + } else { + current_peak_meter * self.peak_meter_decay_weight + + amplitude * (1.0 - self.peak_meter_decay_weight) + }; + + self.peak_meter + .store(new_peak_meter, std::sync::atomic::Ordering::Relaxed) + } + } + + ProcessStatus::Normal + } +} + +impl ClapPlugin for MyPlugin { + const CLAP_ID: &'static str = "com.moist-plugins-gmbh.byo-gui-wgpu"; + const CLAP_DESCRIPTION: Option<&'static str> = + Some("A simple example plugin with a raw WGPU context for rendering"); + const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL); + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Stereo, + ClapFeature::Mono, + ClapFeature::Utility, + ]; +} + +impl Vst3Plugin for MyPlugin { + const VST3_CLASS_ID: [u8; 16] = *b"ByoGuiWGPUWooooo"; + const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = + &[Vst3SubCategory::Fx, Vst3SubCategory::Tools]; +} + +nih_export_clap!(MyPlugin); +nih_export_vst3!(MyPlugin); diff --git a/plugins/examples/byo_gui_wgpu/src/main.rs b/plugins/examples/byo_gui_wgpu/src/main.rs new file mode 100644 index 00000000..9d551f5e --- /dev/null +++ b/plugins/examples/byo_gui_wgpu/src/main.rs @@ -0,0 +1,7 @@ +use nih_plug::prelude::*; + +use byo_gui_wgpu::MyPlugin; + +fn main() { + nih_export_standalone::(); +} From 7f0639855a368242f99d893a8c28a300efd29b86 Mon Sep 17 00:00:00 2001 From: Billy Messenger <60663878+BillyDM@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:26:57 -0600 Subject: [PATCH 2/3] fix typo in docs --- plugins/examples/byo_gui_gl/src/lib.rs | 2 +- plugins/examples/byo_gui_softbuffer/src/lib.rs | 2 +- plugins/examples/byo_gui_wgpu/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/examples/byo_gui_gl/src/lib.rs b/plugins/examples/byo_gui_gl/src/lib.rs index 5f1eb932..73596bfb 100644 --- a/plugins/examples/byo_gui_gl/src/lib.rs +++ b/plugins/examples/byo_gui_gl/src/lib.rs @@ -325,7 +325,7 @@ impl Editor for CustomGlEditor { } } -/// The window handle used for [`EguiEditor`]. +/// The window handle used for [`CustomGlEditor`]. struct CustomGlEditorHandle { state: Arc, window: WindowHandle, diff --git a/plugins/examples/byo_gui_softbuffer/src/lib.rs b/plugins/examples/byo_gui_softbuffer/src/lib.rs index 2d65108d..3054168b 100644 --- a/plugins/examples/byo_gui_softbuffer/src/lib.rs +++ b/plugins/examples/byo_gui_softbuffer/src/lib.rs @@ -249,7 +249,7 @@ impl Editor for CustomSoftbufferEditor { } } -/// The window handle used for [`EguiEditor`]. +/// The window handle used for [`CustomSoftbufferEditor`]. struct CustomSoftbufferEditorHandle { state: Arc, window: WindowHandle, diff --git a/plugins/examples/byo_gui_wgpu/src/lib.rs b/plugins/examples/byo_gui_wgpu/src/lib.rs index 0c533323..c324c36b 100644 --- a/plugins/examples/byo_gui_wgpu/src/lib.rs +++ b/plugins/examples/byo_gui_wgpu/src/lib.rs @@ -368,7 +368,7 @@ impl Editor for CustomWgpuEditor { } } -/// The window handle used for [`EguiEditor`]. +/// The window handle used for [`CustomWgpuEditor`]. struct CustomWgpuEditorHandle { state: Arc, window: WindowHandle, From 78ca9bb41da14b488235e12b9832467b82404e2a Mon Sep 17 00:00:00 2001 From: Billy Messenger <60663878+BillyDM@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:42:28 -0600 Subject: [PATCH 3/3] work around rust-analyzer derp --- plugins/examples/byo_gui_softbuffer/Cargo.toml | 4 +++- plugins/examples/byo_gui_softbuffer/src/lib.rs | 4 ++++ plugins/examples/byo_gui_wgpu/Cargo.toml | 4 +++- plugins/examples/byo_gui_wgpu/src/lib.rs | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/examples/byo_gui_softbuffer/Cargo.toml b/plugins/examples/byo_gui_softbuffer/Cargo.toml index 4fa27ea1..6e0bbbc2 100644 --- a/plugins/examples/byo_gui_softbuffer/Cargo.toml +++ b/plugins/examples/byo_gui_softbuffer/Cargo.toml @@ -13,7 +13,9 @@ crate-type = ["cdylib", "lib"] [dependencies] nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] } -baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" } +# NOTE: OpenGL support is not needed here, but rust-analyzer gets confused when +# some crates do use it and others don't +baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0", features = ["opengl"]} softbuffer = { version = "0.4.6", default-features = false, features = ["kms", "x11"]} raw-window-handle = "0.5" raw-window-handle-06 = { package = "raw-window-handle", version = "0.6" } diff --git a/plugins/examples/byo_gui_softbuffer/src/lib.rs b/plugins/examples/byo_gui_softbuffer/src/lib.rs index 3054168b..b02f0976 100644 --- a/plugins/examples/byo_gui_softbuffer/src/lib.rs +++ b/plugins/examples/byo_gui_softbuffer/src/lib.rs @@ -202,6 +202,10 @@ impl Editor for CustomSoftbufferEditor { scale: scaling_factor .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) .unwrap_or(WindowScalePolicy::SystemScaleFactor), + + // NOTE: The OpenGL feature in baseview is not needed here, but rust-analyzer gets + // confused when some crates do use it and others don't. + gl_config: None, }, move |window: &mut baseview::Window<'_>| -> CustomSoftbufferWindow { CustomSoftbufferWindow::new( diff --git a/plugins/examples/byo_gui_wgpu/Cargo.toml b/plugins/examples/byo_gui_wgpu/Cargo.toml index 1cd58bf9..4520114a 100644 --- a/plugins/examples/byo_gui_wgpu/Cargo.toml +++ b/plugins/examples/byo_gui_wgpu/Cargo.toml @@ -13,7 +13,9 @@ crate-type = ["cdylib", "lib"] [dependencies] nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] } -baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" } +# NOTE: OpenGL support is not needed here, but rust-analyzer gets confused when +# some crates do use it and others don't +baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0", features = ["opengl"]} wgpu = "23" raw-window-handle = "0.5" raw-window-handle-06 = { package = "raw-window-handle", version = "0.6" } diff --git a/plugins/examples/byo_gui_wgpu/src/lib.rs b/plugins/examples/byo_gui_wgpu/src/lib.rs index c324c36b..2bf83cea 100644 --- a/plugins/examples/byo_gui_wgpu/src/lib.rs +++ b/plugins/examples/byo_gui_wgpu/src/lib.rs @@ -321,6 +321,10 @@ impl Editor for CustomWgpuEditor { scale: scaling_factor .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) .unwrap_or(WindowScalePolicy::SystemScaleFactor), + + // NOTE: The OpenGL feature in baseview is not needed here, but rust-analyzer gets + // confused when some crates do use it and others don't. + gl_config: None, }, move |window: &mut baseview::Window<'_>| -> CustomWgpuWindow { CustomWgpuWindow::new(