merge from dev branch - dev
diff --git a/Cargo.lock b/Cargo.lock
index eaddf11..1af69e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20,15 +20,6 @@
 ]
 
 [[package]]
-name = "aho-corasick"
-version = "0.7.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
-dependencies = [
- "memchr",
-]
-
-[[package]]
 name = "android_system_properties"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -507,19 +498,6 @@
 ]
 
 [[package]]
-name = "env_logger"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
-dependencies = [
- "atty",
- "humantime",
- "log",
- "regex",
- "termcolor",
-]
-
-[[package]]
 name = "expat-sys"
 version = "2.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -757,12 +735,6 @@
 checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
 
 [[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
-[[package]]
 name = "ident_case"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1236,7 +1208,7 @@
  "raw-window-handle 0.3.4",
  "raw-window-handle 0.5.0",
  "roxmltree",
- "winit",
+ "winit 0.27.5",
 ]
 
 [[package]]
@@ -1303,15 +1275,11 @@
 version = "0.1.0"
 dependencies = [
  "bytemuck",
- "env_logger",
  "futures-intrusive",
  "parking_lot",
  "piet-scene",
- "png",
- "pollster",
- "roxmltree",
+ "raw-window-handle 0.5.0",
  "wgpu",
- "winit",
 ]
 
 [[package]]
@@ -1521,23 +1489,6 @@
 ]
 
 [[package]]
-name = "regex"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.6.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
-
-[[package]]
 name = "remove_dir_all"
 version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2253,6 +2204,17 @@
 
 [[package]]
 name = "winit"
+version = "0.1.0"
+dependencies = [
+ "piet-scene",
+ "piet-wgsl",
+ "pollster",
+ "roxmltree",
+ "winit 0.27.5",
+]
+
+[[package]]
+name = "winit"
 version = "0.27.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c"
diff --git a/Cargo.toml b/Cargo.toml
index dc40744..8d022fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@
     "piet-gpu-types",
     "piet-scene",
     "piet-wgsl",
+    "piet-wgsl/examples/winit",
     "tests",
 ]
 
diff --git a/piet-gpu/src/samples.rs b/piet-gpu/src/samples.rs
index fd6ea6b..e4f7bc8 100644
--- a/piet-gpu/src/samples.rs
+++ b/piet-gpu/src/samples.rs
@@ -82,7 +82,10 @@
 #[allow(unused)]
 pub fn render_tiger(sb: &mut SceneBuilder, print_stats: bool) {
     use super::pico_svg::*;
-    let xml_str = std::str::from_utf8(include_bytes!("../Ghostscript_Tiger.svg")).unwrap();
+    let xml_str = std::str::from_utf8(include_bytes!(
+        "../../piet-wgsl/examples/assets/Ghostscript_Tiger.svg"
+    ))
+    .unwrap();
     let start = std::time::Instant::now();
     let svg = PicoSvg::load(xml_str, 8.0).unwrap();
     if print_stats {
diff --git a/piet-gpu/src/simple_text.rs b/piet-gpu/src/simple_text.rs
index 00eefaa..674099e 100644
--- a/piet-gpu/src/simple_text.rs
+++ b/piet-gpu/src/simple_text.rs
@@ -22,7 +22,8 @@
 
 // This is very much a hack to get things working.
 // On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
-const FONT_DATA: &[u8] = include_bytes!("../third-party/Roboto-Regular.ttf");
+const FONT_DATA: &[u8] =
+    include_bytes!("../../piet-wgsl/examples/assets/third-party/Roboto-Regular.ttf");
 
 pub struct SimpleText {
     gcx: GlyphContext,
diff --git a/piet-wgsl/Cargo.toml b/piet-wgsl/Cargo.toml
index d10aa41..d74eb15 100644
--- a/piet-wgsl/Cargo.toml
+++ b/piet-wgsl/Cargo.toml
@@ -7,17 +7,8 @@
 
 [dependencies]
 wgpu = "0.14"
-env_logger = "0.9.1"
-pollster = "0.2.5"
+raw-window-handle = "0.5"
 futures-intrusive = "0.5.0"
 parking_lot = "0.12"
 bytemuck = { version = "1.12.1", features = ["derive"] }
-png = "0.17.6"
-
 piet-scene = { path = "../piet-scene" }
-
-# for picosvg, should be split out
-roxmltree = "0.13"
-
-# move this to an example
-winit = "0.27.5"
diff --git a/piet-gpu/Ghostscript_Tiger.svg b/piet-wgsl/examples/assets/Ghostscript_Tiger.svg
similarity index 100%
rename from piet-gpu/Ghostscript_Tiger.svg
rename to piet-wgsl/examples/assets/Ghostscript_Tiger.svg
diff --git a/piet-gpu/third-party/LICENSE.txt b/piet-wgsl/examples/assets/third-party/LICENSE.txt
similarity index 99%
rename from piet-gpu/third-party/LICENSE.txt
rename to piet-wgsl/examples/assets/third-party/LICENSE.txt
index 75b5248..d645695 100644
--- a/piet-gpu/third-party/LICENSE.txt
+++ b/piet-wgsl/examples/assets/third-party/LICENSE.txt
@@ -1,202 +1,202 @@
-

-                                 Apache License

-                           Version 2.0, January 2004

-                        http://www.apache.org/licenses/

-

-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

-

-   1. Definitions.

-

-      "License" shall mean the terms and conditions for use, reproduction,

-      and distribution as defined by Sections 1 through 9 of this document.

-

-      "Licensor" shall mean the copyright owner or entity authorized by

-      the copyright owner that is granting the License.

-

-      "Legal Entity" shall mean the union of the acting entity and all

-      other entities that control, are controlled by, or are under common

-      control with that entity. For the purposes of this definition,

-      "control" means (i) the power, direct or indirect, to cause the

-      direction or management of such entity, whether by contract or

-      otherwise, or (ii) ownership of fifty percent (50%) or more of the

-      outstanding shares, or (iii) beneficial ownership of such entity.

-

-      "You" (or "Your") shall mean an individual or Legal Entity

-      exercising permissions granted by this License.

-

-      "Source" form shall mean the preferred form for making modifications,

-      including but not limited to software source code, documentation

-      source, and configuration files.

-

-      "Object" form shall mean any form resulting from mechanical

-      transformation or translation of a Source form, including but

-      not limited to compiled object code, generated documentation,

-      and conversions to other media types.

-

-      "Work" shall mean the work of authorship, whether in Source or

-      Object form, made available under the License, as indicated by a

-      copyright notice that is included in or attached to the work

-      (an example is provided in the Appendix below).

-

-      "Derivative Works" shall mean any work, whether in Source or Object

-      form, that is based on (or derived from) the Work and for which the

-      editorial revisions, annotations, elaborations, or other modifications

-      represent, as a whole, an original work of authorship. For the purposes

-      of this License, Derivative Works shall not include works that remain

-      separable from, or merely link (or bind by name) to the interfaces of,

-      the Work and Derivative Works thereof.

-

-      "Contribution" shall mean any work of authorship, including

-      the original version of the Work and any modifications or additions

-      to that Work or Derivative Works thereof, that is intentionally

-      submitted to Licensor for inclusion in the Work by the copyright owner

-      or by an individual or Legal Entity authorized to submit on behalf of

-      the copyright owner. For the purposes of this definition, "submitted"

-      means any form of electronic, verbal, or written communication sent

-      to the Licensor or its representatives, including but not limited to

-      communication on electronic mailing lists, source code control systems,

-      and issue tracking systems that are managed by, or on behalf of, the

-      Licensor for the purpose of discussing and improving the Work, but

-      excluding communication that is conspicuously marked or otherwise

-      designated in writing by the copyright owner as "Not a Contribution."

-

-      "Contributor" shall mean Licensor and any individual or Legal Entity

-      on behalf of whom a Contribution has been received by Licensor and

-      subsequently incorporated within the Work.

-

-   2. Grant of Copyright License. Subject to the terms and conditions of

-      this License, each Contributor hereby grants to You a perpetual,

-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

-      copyright license to reproduce, prepare Derivative Works of,

-      publicly display, publicly perform, sublicense, and distribute the

-      Work and such Derivative Works in Source or Object form.

-

-   3. Grant of Patent License. Subject to the terms and conditions of

-      this License, each Contributor hereby grants to You a perpetual,

-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable

-      (except as stated in this section) patent license to make, have made,

-      use, offer to sell, sell, import, and otherwise transfer the Work,

-      where such license applies only to those patent claims licensable

-      by such Contributor that are necessarily infringed by their

-      Contribution(s) alone or by combination of their Contribution(s)

-      with the Work to which such Contribution(s) was submitted. If You

-      institute patent litigation against any entity (including a

-      cross-claim or counterclaim in a lawsuit) alleging that the Work

-      or a Contribution incorporated within the Work constitutes direct

-      or contributory patent infringement, then any patent licenses

-      granted to You under this License for that Work shall terminate

-      as of the date such litigation is filed.

-

-   4. Redistribution. You may reproduce and distribute copies of the

-      Work or Derivative Works thereof in any medium, with or without

-      modifications, and in Source or Object form, provided that You

-      meet the following conditions:

-

-      (a) You must give any other recipients of the Work or

-          Derivative Works a copy of this License; and

-

-      (b) You must cause any modified files to carry prominent notices

-          stating that You changed the files; and

-

-      (c) You must retain, in the Source form of any Derivative Works

-          that You distribute, all copyright, patent, trademark, and

-          attribution notices from the Source form of the Work,

-          excluding those notices that do not pertain to any part of

-          the Derivative Works; and

-

-      (d) If the Work includes a "NOTICE" text file as part of its

-          distribution, then any Derivative Works that You distribute must

-          include a readable copy of the attribution notices contained

-          within such NOTICE file, excluding those notices that do not

-          pertain to any part of the Derivative Works, in at least one

-          of the following places: within a NOTICE text file distributed

-          as part of the Derivative Works; within the Source form or

-          documentation, if provided along with the Derivative Works; or,

-          within a display generated by the Derivative Works, if and

-          wherever such third-party notices normally appear. The contents

-          of the NOTICE file are for informational purposes only and

-          do not modify the License. You may add Your own attribution

-          notices within Derivative Works that You distribute, alongside

-          or as an addendum to the NOTICE text from the Work, provided

-          that such additional attribution notices cannot be construed

-          as modifying the License.

-

-      You may add Your own copyright statement to Your modifications and

-      may provide additional or different license terms and conditions

-      for use, reproduction, or distribution of Your modifications, or

-      for any such Derivative Works as a whole, provided Your use,

-      reproduction, and distribution of the Work otherwise complies with

-      the conditions stated in this License.

-

-   5. Submission of Contributions. Unless You explicitly state otherwise,

-      any Contribution intentionally submitted for inclusion in the Work

-      by You to the Licensor shall be under the terms and conditions of

-      this License, without any additional terms or conditions.

-      Notwithstanding the above, nothing herein shall supersede or modify

-      the terms of any separate license agreement you may have executed

-      with Licensor regarding such Contributions.

-

-   6. Trademarks. This License does not grant permission to use the trade

-      names, trademarks, service marks, or product names of the Licensor,

-      except as required for reasonable and customary use in describing the

-      origin of the Work and reproducing the content of the NOTICE file.

-

-   7. Disclaimer of Warranty. Unless required by applicable law or

-      agreed to in writing, Licensor provides the Work (and each

-      Contributor provides its Contributions) on an "AS IS" BASIS,

-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

-      implied, including, without limitation, any warranties or conditions

-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A

-      PARTICULAR PURPOSE. You are solely responsible for determining the

-      appropriateness of using or redistributing the Work and assume any

-      risks associated with Your exercise of permissions under this License.

-

-   8. Limitation of Liability. In no event and under no legal theory,

-      whether in tort (including negligence), contract, or otherwise,

-      unless required by applicable law (such as deliberate and grossly

-      negligent acts) or agreed to in writing, shall any Contributor be

-      liable to You for damages, including any direct, indirect, special,

-      incidental, or consequential damages of any character arising as a

-      result of this License or out of the use or inability to use the

-      Work (including but not limited to damages for loss of goodwill,

-      work stoppage, computer failure or malfunction, or any and all

-      other commercial damages or losses), even if such Contributor

-      has been advised of the possibility of such damages.

-

-   9. Accepting Warranty or Additional Liability. While redistributing

-      the Work or Derivative Works thereof, You may choose to offer,

-      and charge a fee for, acceptance of support, warranty, indemnity,

-      or other liability obligations and/or rights consistent with this

-      License. However, in accepting such obligations, You may act only

-      on Your own behalf and on Your sole responsibility, not on behalf

-      of any other Contributor, and only if You agree to indemnify,

-      defend, and hold each Contributor harmless for any liability

-      incurred by, or claims asserted against, such Contributor by reason

-      of your accepting any such warranty or additional liability.

-

-   END OF TERMS AND CONDITIONS

-

-   APPENDIX: How to apply the Apache License to your work.

-

-      To apply the Apache License to your work, attach the following

-      boilerplate notice, with the fields enclosed by brackets "[]"

-      replaced with your own identifying information. (Don't include

-      the brackets!)  The text should be enclosed in the appropriate

-      comment syntax for the file format. We also recommend that a

-      file or class name and description of purpose be included on the

-      same "printed page" as the copyright notice for easier

-      identification within third-party archives.

-

-   Copyright [yyyy] [name of copyright owner]

-

-   Licensed under the Apache License, Version 2.0 (the "License");

-   you may not use this file except in compliance with the License.

-   You may obtain a copy of the License at

-

-       http://www.apache.org/licenses/LICENSE-2.0

-

-   Unless required by applicable law or agreed to in writing, software

-   distributed under the License is distributed on an "AS IS" BASIS,

-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

-   See the License for the specific language governing permissions and

-   limitations under the License.

+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/piet-gpu/third-party/Roboto-Regular.ttf b/piet-wgsl/examples/assets/third-party/Roboto-Regular.ttf
similarity index 100%
rename from piet-gpu/third-party/Roboto-Regular.ttf
rename to piet-wgsl/examples/assets/third-party/Roboto-Regular.ttf
Binary files differ
diff --git a/piet-wgsl/examples/winit/Cargo.toml b/piet-wgsl/examples/winit/Cargo.toml
new file mode 100644
index 0000000..e112839
--- /dev/null
+++ b/piet-wgsl/examples/winit/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "winit"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+piet-wgsl = { path = "../../../piet-wgsl" }
+piet-scene = { path = "../../../piet-scene" }
+winit = "0.27.5"
+pollster = "0.2.5"
+# for picosvg
+roxmltree = "0.13"
diff --git a/piet-wgsl/examples/winit/src/main.rs b/piet-wgsl/examples/winit/src/main.rs
new file mode 100644
index 0000000..ba6226c
--- /dev/null
+++ b/piet-wgsl/examples/winit/src/main.rs
@@ -0,0 +1,106 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Also licensed under MIT license, at your choice.
+
+mod pico_svg;
+mod simple_text;
+mod test_scene;
+
+use piet_scene::{Scene, SceneBuilder};
+use piet_wgsl::{util::RenderContext, Renderer, Result};
+
+async fn run() -> Result<()> {
+    use winit::{
+        dpi::LogicalSize,
+        event::*,
+        event_loop::{ControlFlow, EventLoop},
+        window::WindowBuilder,
+    };
+    let event_loop = EventLoop::new();
+    let window = WindowBuilder::new()
+        .with_inner_size(LogicalSize::new(1044, 800))
+        .with_resizable(true)
+        .build(&event_loop)
+        .unwrap();
+    let render_cx = RenderContext::new().await?;
+    let size = window.inner_size();
+    let mut surface = render_cx.create_surface(&window, size.width, size.height);
+    let mut renderer = Renderer::new(&render_cx.device)?;
+    let mut simple_text = simple_text::SimpleText::new();
+    let mut current_frame = 0usize;
+    let mut scene_ix = 0usize;
+    let mut scene = Scene::default();
+    event_loop.run(move |event, _, control_flow| match event {
+        Event::WindowEvent {
+            ref event,
+            window_id,
+        } if window_id == window.id() => match event {
+            WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
+            WindowEvent::KeyboardInput { input, .. } => {
+                if input.state == ElementState::Pressed {
+                    match input.virtual_keycode {
+                        Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1),
+                        Some(VirtualKeyCode::Right) => scene_ix = scene_ix.saturating_add(1),
+                        _ => {}
+                    }
+                }
+            }
+            WindowEvent::Resized(size) => {
+                render_cx.resize_surface(&mut surface, size.width, size.height);
+                window.request_redraw();
+            }
+            _ => {}
+        },
+        Event::MainEventsCleared => {
+            window.request_redraw();
+        }
+        Event::RedrawRequested(_) => {
+            current_frame += 1;
+            let width = surface.config.width;
+            let height = surface.config.height;
+            let mut builder = SceneBuilder::for_scene(&mut scene);
+            const N_SCENES: usize = 6;
+            match scene_ix % N_SCENES {
+                0 => test_scene::render_anim_frame(&mut builder, &mut simple_text, current_frame),
+                1 => test_scene::render_blend_grid(&mut builder),
+                2 => test_scene::render_tiger(&mut builder, false),
+                3 => test_scene::render_brush_transform(&mut builder, current_frame),
+                4 => test_scene::render_funky_paths(&mut builder),
+                _ => test_scene::render_scene(&mut builder),
+            }
+            builder.finish();
+            let surface_texture = surface
+                .surface
+                .get_current_texture()
+                .expect("failed to get surface texture");
+            renderer
+                .render_to_surface(
+                    &render_cx.device,
+                    &render_cx.queue,
+                    &scene,
+                    &surface_texture,
+                    width,
+                    height,
+                )
+                .expect("failed to render to surface");
+            surface_texture.present();
+        }
+        _ => {}
+    });
+}
+
+fn main() {
+    pollster::block_on(run()).unwrap();
+}
diff --git a/piet-wgsl/src/pico_svg.rs b/piet-wgsl/examples/winit/src/pico_svg.rs
similarity index 100%
rename from piet-wgsl/src/pico_svg.rs
rename to piet-wgsl/examples/winit/src/pico_svg.rs
diff --git a/piet-wgsl/src/simple_text.rs b/piet-wgsl/examples/winit/src/simple_text.rs
similarity index 96%
rename from piet-wgsl/src/simple_text.rs
rename to piet-wgsl/examples/winit/src/simple_text.rs
index 9b2d4c8..f35ef56 100644
--- a/piet-wgsl/src/simple_text.rs
+++ b/piet-wgsl/examples/winit/src/simple_text.rs
@@ -22,7 +22,7 @@
 
 // This is very much a hack to get things working.
 // On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
-const FONT_DATA: &[u8] = include_bytes!("../../piet-gpu/third-party/Roboto-Regular.ttf");
+const FONT_DATA: &[u8] = include_bytes!("../../assets/third-party/Roboto-Regular.ttf");
 
 pub struct SimpleText {
     gcx: GlyphContext,
diff --git a/piet-wgsl/src/test_scene.rs b/piet-wgsl/examples/winit/src/test_scene.rs
similarity index 98%
rename from piet-wgsl/src/test_scene.rs
rename to piet-wgsl/examples/winit/src/test_scene.rs
index d5a744d..4c90829 100644
--- a/piet-wgsl/src/test_scene.rs
+++ b/piet-wgsl/examples/winit/src/test_scene.rs
@@ -1,9 +1,8 @@
-use super::PicoSvg;
+use crate::pico_svg::PicoSvg;
+use crate::simple_text::SimpleText;
 use piet_scene::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect};
 use piet_scene::*;
 
-use crate::SimpleText;
-
 pub fn render_funky_paths(sb: &mut SceneBuilder) {
     use PathEl::*;
     let missing_movetos = [
@@ -83,7 +82,7 @@
 pub fn render_tiger(sb: &mut SceneBuilder, print_stats: bool) {
     use super::pico_svg::*;
     let xml_str =
-        std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")).unwrap();
+        std::str::from_utf8(include_bytes!("../../assets/Ghostscript_Tiger.svg")).unwrap();
     let start = std::time::Instant::now();
     let svg = PicoSvg::load(xml_str, 6.0).unwrap();
     if print_stats {
diff --git a/piet-wgsl/src/debug.rs b/piet-wgsl/src/debug.rs
deleted file mode 100644
index 964c8ee..0000000
--- a/piet-wgsl/src/debug.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-#![allow(dead_code)]
-
-pub mod clip;
-pub mod draw;
-pub mod fine;
diff --git a/piet-wgsl/src/debug/clip.rs b/piet-wgsl/src/debug/clip.rs
deleted file mode 100644
index 8b2a0e3..0000000
--- a/piet-wgsl/src/debug/clip.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use bytemuck::{Pod, Zeroable};
-
-#[derive(Copy, Clone, Debug, Zeroable, Pod)]
-#[repr(C)]
-pub struct ClipEl {
-    pub parent_ix: u32,
-    pub pad: [u32; 3],
-    pub bbox: [f32; 4],
-}
-
-pub fn parse_clip_els(data: &[u8]) -> Vec<ClipEl> {
-    Vec::from(bytemuck::cast_slice(data))
-}
diff --git a/piet-wgsl/src/debug/draw.rs b/piet-wgsl/src/debug/draw.rs
deleted file mode 100644
index ab56ed5..0000000
--- a/piet-wgsl/src/debug/draw.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use bytemuck::{Pod, Zeroable};
-
-#[derive(Copy, Clone, Debug, Zeroable, Pod)]
-#[repr(C)]
-pub struct DrawMonoid {
-    pub path_ix: u32,
-    pub clip_ix: u32,
-    pub scene_offset: u32,
-    pub info_offset: u32,
-}
-
-pub fn parse_draw_monoids(data: &[u8]) -> Vec<DrawMonoid> {
-    Vec::from(bytemuck::cast_slice(data))
-}
diff --git a/piet-wgsl/src/debug/fine.rs b/piet-wgsl/src/debug/fine.rs
deleted file mode 100644
index d9f05f0..0000000
--- a/piet-wgsl/src/debug/fine.rs
+++ /dev/null
@@ -1,153 +0,0 @@
-#[derive(Copy, Clone, Debug)]
-#[repr(C)]
-pub struct Fill {
-    pub tile: u32,
-    pub backdrop: i32,
-}
-
-#[derive(Copy, Clone, Debug)]
-#[repr(C)]
-pub struct Stroke {
-    pub tile: u32,
-    pub half_width: f32,
-}
-
-#[derive(Copy, Clone, Debug)]
-#[repr(C)]
-pub struct Color {
-    abgr: [u8; 4],
-}
-
-#[derive(Copy, Clone, Debug)]
-#[repr(C)]
-pub struct LinGrad {
-    pub index: u32,
-    pub line_x: f32,
-    pub line_y: f32,
-    pub line_c: f32,
-}
-
-#[derive(Copy, Clone, Debug)]
-#[repr(C)]
-pub struct RadGrad {
-    pub index: u32,
-    pub matrix: [f32; 4],
-    pub xlat: [f32; 2],
-    pub c1: [f32; 2],
-    pub ra: f32,
-    pub roff: f32,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum Command {
-    Fill(Fill),
-    Stroke(Stroke),
-    Solid,
-    Color(Color),
-    LinGrad(LinGrad),
-    RadGrad(RadGrad),
-    BeginClip,
-    EndClip(u32),
-    End,
-}
-
-const PTCL_INITIAL_ALLOC: usize = 64;
-
-#[derive(Debug)]
-pub struct CommandList {
-    pub tiles: Vec<(u32, u32, Vec<Command>)>,
-}
-
-impl CommandList {
-    pub fn parse(width: usize, height: usize, ptcl: &[u8]) -> Self {
-        let mut tiles = vec![];
-        let width_tiles = width / 16;
-        let height_tiles = height / 16;
-        for y in 0..height_tiles {
-            for x in 0..width_tiles {
-                let tile_ix = y * width_tiles + x;
-                let ix = tile_ix * PTCL_INITIAL_ALLOC;
-                let commands = parse_commands(ptcl, ix);
-                if !commands.is_empty() {
-                    tiles.push((x as u32, y as u32, commands));
-                }
-            }
-        }
-        Self { tiles }
-    }
-}
-
-fn parse_commands(ptcl: &[u8], mut ix: usize) -> Vec<Command> {
-    let mut commands = vec![];
-    let words: &[u32] = bytemuck::cast_slice(ptcl);
-    while ix < words.len() {
-        let tag = words[ix];
-        ix += 1;
-        match tag {
-            0 => break,
-            1 => {
-                commands.push(Command::Fill(Fill {
-                    tile: words[ix],
-                    backdrop: words[ix + 1] as i32,
-                }));
-                ix += 2;
-            }
-            2 => {
-                commands.push(Command::Stroke(Stroke {
-                    tile: words[ix],
-                    half_width: bytemuck::cast(words[ix + 1]),
-                }));
-                ix += 2;
-            }
-            3 => {
-                commands.push(Command::Solid);
-            }
-            5 => {
-                commands.push(Command::Color(Color {
-                    abgr: bytemuck::cast(words[ix]),
-                }));
-                ix += 1;
-            }
-            6 => {
-                commands.push(Command::LinGrad(LinGrad {
-                    index: words[ix],
-                    line_x: bytemuck::cast(words[ix + 1]),
-                    line_y: bytemuck::cast(words[ix + 2]),
-                    line_c: bytemuck::cast(words[ix + 3]),
-                }));
-                ix += 4;
-            }
-            7 => {
-                let matrix = [
-                    bytemuck::cast(words[ix + 1]),
-                    bytemuck::cast(words[ix + 2]),
-                    bytemuck::cast(words[ix + 3]),
-                    bytemuck::cast(words[ix + 4]),
-                ];
-                let xlat = [bytemuck::cast(words[ix + 5]), bytemuck::cast(words[ix + 6])];
-                let c1 = [bytemuck::cast(words[ix + 7]), bytemuck::cast(words[ix + 8])];
-                commands.push(Command::RadGrad(RadGrad {
-                    index: words[ix],
-                    matrix,
-                    xlat,
-                    c1,
-                    ra: bytemuck::cast(words[ix + 9]),
-                    roff: bytemuck::cast(words[ix + 10]),
-                }));
-                ix += 11;
-            }
-            9 => {
-                commands.push(Command::BeginClip);
-            }
-            10 => {
-                commands.push(Command::EndClip(words[ix]));
-                ix += 1;
-            }
-            11 => {
-                ix = words[ix] as usize;
-            }
-            _ => {}
-        }
-    }
-    commands
-}
diff --git a/piet-wgsl/src/lib.rs b/piet-wgsl/src/lib.rs
new file mode 100644
index 0000000..59641eb
--- /dev/null
+++ b/piet-wgsl/src/lib.rs
@@ -0,0 +1,274 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Also licensed under MIT license, at your choice.
+
+mod engine;
+mod ramp;
+mod render;
+mod shaders;
+
+pub mod util;
+
+use engine::{Engine, ExternalResource};
+use shaders::FullShaders;
+
+use piet_scene::Scene;
+use wgpu::{Device, Queue, SurfaceTexture, Texture, TextureFormat, TextureView};
+
+/// Catch-all error type.
+pub type Error = Box<dyn std::error::Error>;
+
+/// Specialization of `Result` for our catch-all error type.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Renders a scene into a texture or surface.
+pub struct Renderer {
+    engine: Engine,
+    shaders: FullShaders,
+    blit: BlitPipeline,
+    target: Option<TargetTexture>,
+}
+
+impl Renderer {
+    /// Creates a new renderer for the specified device.
+    pub fn new(device: &Device) -> Result<Self> {
+        let mut engine = Engine::new();
+        let shaders = shaders::full_shaders(device, &mut engine)?;
+        let blit = BlitPipeline::new(device, TextureFormat::Bgra8Unorm);
+        Ok(Self {
+            engine,
+            shaders,
+            blit,
+            target: None,
+        })
+    }
+
+    /// Renders a scene to the target texture.
+    ///
+    /// The texture is assumed to be of the specified dimensions and have been created with
+    /// the [wgpu::TextureFormat::Rgba8Unorm] format and the [wgpu::TextureUsages::STORAGE_BINDING]
+    /// flag set.
+    pub fn render_to_texture(
+        &mut self,
+        device: &Device,
+        queue: &Queue,
+        scene: &Scene,
+        texture: &TextureView,
+        width: u32,
+        height: u32,
+    ) -> Result<()> {
+        let (recording, target) = render::render_full(&scene, &self.shaders, width, height);
+        let external_resources = [ExternalResource::Image(
+            *target.as_image().unwrap(),
+            texture,
+        )];
+        let _ = self
+            .engine
+            .run_recording(device, queue, &recording, &external_resources)?;
+        Ok(())
+    }
+
+    /// Renders a scene to the target surface.
+    ///
+    /// This renders to an intermediate texture and then runs a render pass to blit to the
+    /// specified surface texture.
+    ///
+    /// The surface is assumed to be of the specified dimensions and have been created with the
+    /// [wgpu::TextureFormat::Bgra8Unorm] format.
+    pub fn render_to_surface(
+        &mut self,
+        device: &Device,
+        queue: &Queue,
+        scene: &Scene,
+        surface: &SurfaceTexture,
+        width: u32,
+        height: u32,
+    ) -> Result<()> {
+        let mut target = self
+            .target
+            .take()
+            .unwrap_or_else(|| TargetTexture::new(device, width, height));
+        // TODO: implement clever resizing semantics here to avoid thrashing the memory allocator
+        // during resize, specifically on metal.
+        if target.width != width || target.height != height {
+            target = TargetTexture::new(device, width, height);
+        }
+        self.render_to_texture(device, queue, scene, &target.view, width, height)?;
+        let mut encoder =
+            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
+        {
+            let surface_view = surface
+                .texture
+                .create_view(&wgpu::TextureViewDescriptor::default());
+            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+                label: None,
+                layout: &self.blit.bind_layout,
+                entries: &[wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: wgpu::BindingResource::TextureView(&target.view),
+                }],
+            });
+            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: None,
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: &surface_view,
+                    resolve_target: None,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Clear(wgpu::Color::default()),
+                        store: true,
+                    },
+                })],
+                depth_stencil_attachment: None,
+            });
+            render_pass.set_pipeline(&self.blit.pipeline);
+            render_pass.set_bind_group(0, &bind_group, &[]);
+            render_pass.draw(0..6, 0..1);
+        }
+        queue.submit(Some(encoder.finish()));
+        self.target = Some(target);
+        Ok(())
+    }
+}
+
+struct TargetTexture {
+    texture: Texture,
+    view: TextureView,
+    width: u32,
+    height: u32,
+}
+
+impl TargetTexture {
+    pub fn new(device: &Device, width: u32, height: u32) -> Self {
+        let texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: None,
+            size: wgpu::Extent3d {
+                width,
+                height,
+                depth_or_array_layers: 1,
+            },
+            mip_level_count: 1,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
+            format: wgpu::TextureFormat::Rgba8Unorm,
+        });
+        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+        Self {
+            texture,
+            view,
+            width,
+            height,
+        }
+    }
+}
+
+struct BlitPipeline {
+    bind_layout: wgpu::BindGroupLayout,
+    pipeline: wgpu::RenderPipeline,
+}
+
+impl BlitPipeline {
+    fn new(device: &Device, format: TextureFormat) -> Self {
+        const SHADERS: &str = r#"
+            @vertex
+            fn vs_main(@builtin(vertex_index) ix: u32) -> @builtin(position) vec4<f32> {
+                // Generate a full screen quad in NDCs
+                var vertex = vec2<f32>(-1.0, 1.0);
+                switch ix {
+                    case 1u: {
+                        vertex = vec2<f32>(-1.0, -1.0);
+                    }
+                    case 2u, 4u: {
+                        vertex = vec2<f32>(1.0, -1.0);
+                    }
+                    case 5u: {
+                        vertex = vec2<f32>(1.0, 1.0);
+                    }
+                    default: {}
+                }
+                return vec4<f32>(vertex, 0.0, 1.0);
+            }
+            
+            @group(0) @binding(0)
+            var fine_output: texture_2d<f32>;
+            
+            @fragment
+            fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
+                return textureLoad(fine_output, vec2<i32>(pos.xy), 0);
+            }
+        "#;
+
+        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+            label: Some("blit shaders"),
+            source: wgpu::ShaderSource::Wgsl(SHADERS.into()),
+        });
+        let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+            label: None,
+            entries: &[wgpu::BindGroupLayoutEntry {
+                visibility: wgpu::ShaderStages::FRAGMENT,
+                binding: 0,
+                ty: wgpu::BindingType::Texture {
+                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                    view_dimension: wgpu::TextureViewDimension::D2,
+                    multisampled: false,
+                },
+                count: None,
+            }],
+        });
+        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+            label: None,
+            bind_group_layouts: &[&bind_layout],
+            push_constant_ranges: &[],
+        });
+        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+            label: None,
+            layout: Some(&pipeline_layout),
+            vertex: wgpu::VertexState {
+                module: &shader,
+                entry_point: "vs_main",
+                buffers: &[],
+            },
+            fragment: Some(wgpu::FragmentState {
+                module: &shader,
+                entry_point: "fs_main",
+                targets: &[Some(wgpu::ColorTargetState {
+                    format,
+                    blend: None,
+                    write_mask: wgpu::ColorWrites::ALL,
+                })],
+            }),
+            primitive: wgpu::PrimitiveState {
+                topology: wgpu::PrimitiveTopology::TriangleList,
+                strip_index_format: None,
+                front_face: wgpu::FrontFace::Ccw,
+                cull_mode: Some(wgpu::Face::Back),
+                polygon_mode: wgpu::PolygonMode::Fill,
+                unclipped_depth: false,
+                conservative: false,
+            },
+            depth_stencil: None,
+            multisample: wgpu::MultisampleState {
+                count: 1,
+                mask: !0,
+                alpha_to_coverage_enabled: false,
+            },
+            multiview: None,
+        });
+        Self {
+            bind_layout,
+            pipeline,
+        }
+    }
+}
diff --git a/piet-wgsl/src/main.rs b/piet-wgsl/src/main.rs
deleted file mode 100644
index 8d735d4..0000000
--- a/piet-wgsl/src/main.rs
+++ /dev/null
@@ -1,348 +0,0 @@
-// Copyright 2022 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// Also licensed under MIT license, at your choice.
-
-//! A simple application to run a compute shader.
-
-use engine::{Engine, Error, ExternalResource};
-
-use piet_scene::{Scene, SceneBuilder};
-use wgpu::{Device, Instance, Limits, Queue, Surface, SurfaceConfiguration};
-use winit::window::Window;
-
-mod debug;
-mod engine;
-mod pico_svg;
-mod ramp;
-mod render;
-mod shaders;
-mod simple_text;
-mod test_scene;
-
-use pico_svg::PicoSvg;
-use simple_text::SimpleText;
-
-pub struct Dimensions {
-    width: u32,
-    height: u32,
-}
-
-pub struct WgpuState {
-    pub instance: Instance,
-    pub device: Device,
-    pub queue: Queue,
-    pub surface: Option<Surface>,
-    pub surface_config: SurfaceConfiguration,
-}
-
-impl WgpuState {
-    pub async fn new(window: Option<&Window>) -> Result<Self, Box<dyn std::error::Error>> {
-        let instance = Instance::new(wgpu::Backends::PRIMARY);
-        let adapter = instance.request_adapter(&Default::default()).await.unwrap();
-        let features = adapter.features();
-        let mut limits = Limits::default();
-        limits.max_storage_buffers_per_shader_stage = 16;
-        let (device, queue) = adapter
-            .request_device(
-                &wgpu::DeviceDescriptor {
-                    label: None,
-                    features: features
-                        & (wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::CLEAR_TEXTURE),
-                    limits,
-                },
-                None,
-            )
-            .await?;
-        let (surface, surface_config) = if let Some(window) = window {
-            let surface = unsafe { instance.create_surface(window) };
-            let size = window.inner_size();
-            // let format = surface.get_supported_formats(&adapter)[0];
-            let format = wgpu::TextureFormat::Bgra8Unorm;
-            println!("surface: {:?} {:?}", size, format);
-            let surface_config = wgpu::SurfaceConfiguration {
-                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
-                format,
-                width: size.width,
-                height: size.height,
-                present_mode: wgpu::PresentMode::Fifo,
-                alpha_mode: wgpu::CompositeAlphaMode::Auto,
-            };
-            surface.configure(&device, &surface_config);
-            (Some(surface), surface_config)
-        } else {
-            let surface_config = wgpu::SurfaceConfiguration {
-                usage: wgpu::TextureUsages::empty(),
-                format: wgpu::TextureFormat::Bgra8Unorm,
-                width: 0,
-                height: 0,
-                present_mode: wgpu::PresentMode::Fifo,
-                alpha_mode: wgpu::CompositeAlphaMode::Auto,
-            };
-            (None, surface_config)
-        };
-        Ok(Self {
-            instance,
-            device,
-            queue,
-            surface,
-            surface_config,
-        })
-    }
-
-    pub fn create_target_texture(&self) -> (wgpu::Texture, wgpu::TextureView) {
-        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
-            label: None,
-            size: wgpu::Extent3d {
-                width: self.surface_config.width,
-                height: self.surface_config.height,
-                depth_or_array_layers: 1,
-            },
-            mip_level_count: 1,
-            sample_count: 1,
-            dimension: wgpu::TextureDimension::D2,
-            usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
-            format: wgpu::TextureFormat::Rgba8Unorm,
-        });
-        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
-        (texture, view)
-    }
-}
-
-async fn run_interactive() -> Result<(), Error> {
-    use winit::{
-        dpi::LogicalSize,
-        event::*,
-        event_loop::{ControlFlow, EventLoop},
-        window::WindowBuilder,
-    };
-    let event_loop = EventLoop::new();
-    let window = WindowBuilder::new()
-        .with_inner_size(LogicalSize::new(1044, 800))
-        .with_resizable(true)
-        .build(&event_loop)
-        .unwrap();
-    let mut state = WgpuState::new(Some(&window)).await?;
-    let mut engine = Engine::new();
-    let full_shaders = shaders::full_shaders(&state.device, &mut engine)?;
-    let (blit_layout, blit_pipeline) = create_blit_pipeline(&state);
-    let mut simple_text = SimpleText::new();
-    let mut current_frame = 0usize;
-    let mut scene_ix = 0usize;
-    let (mut _target_texture, mut target_view) = state.create_target_texture();
-    event_loop.run(move |event, _, control_flow| match event {
-        Event::WindowEvent {
-            ref event,
-            window_id,
-        } if window_id == window.id() => match event {
-            WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-            WindowEvent::KeyboardInput { input, .. } => {
-                if input.state == ElementState::Pressed {
-                    match input.virtual_keycode {
-                        Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1),
-                        Some(VirtualKeyCode::Right) => scene_ix = scene_ix.saturating_add(1),
-                        _ => {}
-                    }
-                }
-            }
-            WindowEvent::Resized(size) => {
-                state.surface_config.width = size.width;
-                state.surface_config.height = size.height;
-                state
-                    .surface
-                    .as_ref()
-                    .unwrap()
-                    .configure(&state.device, &state.surface_config);
-                let (t, v) = state.create_target_texture();
-                _target_texture = t;
-                target_view = v;
-                window.request_redraw();
-            }
-            _ => {}
-        },
-        Event::MainEventsCleared => {
-            window.request_redraw();
-        }
-        Event::RedrawRequested(_) => {
-            current_frame += 1;
-            let surface_texture = state
-                .surface
-                .as_ref()
-                .unwrap()
-                .get_current_texture()
-                .unwrap();
-            let dimensions = Dimensions {
-                width: state.surface_config.width,
-                height: state.surface_config.height,
-            };
-            let mut scene = Scene::default();
-            let mut builder = SceneBuilder::for_scene(&mut scene);
-            const N_SCENES: usize = 6;
-            match scene_ix % N_SCENES {
-                0 => test_scene::render_anim_frame(&mut builder, &mut simple_text, current_frame),
-                1 => test_scene::render_blend_grid(&mut builder),
-                2 => test_scene::render_tiger(&mut builder, false),
-                3 => test_scene::render_brush_transform(&mut builder, current_frame),
-                4 => test_scene::render_funky_paths(&mut builder),
-                _ => test_scene::render_scene(&mut builder),
-            }
-            builder.finish();
-            let (recording, target) = render::render_full(&scene, &full_shaders, &dimensions);
-            let external_resources = [ExternalResource::Image(
-                *target.as_image().unwrap(),
-                &target_view,
-            )];
-            let _ = engine
-                .run_recording(&state.device, &state.queue, &recording, &external_resources)
-                .unwrap();
-            let mut encoder = state
-                .device
-                .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
-            {
-                let surface_view = surface_texture
-                    .texture
-                    .create_view(&wgpu::TextureViewDescriptor::default());
-                let bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
-                    label: None,
-                    layout: &blit_layout,
-                    entries: &[wgpu::BindGroupEntry {
-                        binding: 0,
-                        resource: wgpu::BindingResource::TextureView(&target_view),
-                    }],
-                });
-                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
-                    label: None,
-                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                        view: &surface_view,
-                        resolve_target: None,
-                        ops: wgpu::Operations {
-                            load: wgpu::LoadOp::Clear(wgpu::Color::default()),
-                            store: true,
-                        },
-                    })],
-                    depth_stencil_attachment: None,
-                });
-                render_pass.set_pipeline(&blit_pipeline);
-                render_pass.set_bind_group(0, &bind_group, &[]);
-                render_pass.draw(0..6, 0..1);
-            }
-            state.queue.submit(Some(encoder.finish()));
-            surface_texture.present();
-        }
-        _ => {}
-    });
-}
-
-fn main() {
-    pollster::block_on(run_interactive()).unwrap();
-}
-
-// Fit this into the recording code somehow?
-fn create_blit_pipeline(state: &WgpuState) -> (wgpu::BindGroupLayout, wgpu::RenderPipeline) {
-    const SHADERS: &str = r#"
-        @vertex
-        fn vs_main(@builtin(vertex_index) ix: u32) -> @builtin(position) vec4<f32> {
-            // Generate a full screen quad in NDCs
-            var vertex = vec2<f32>(-1.0, 1.0);
-            switch ix {
-                case 1u: {
-                    vertex = vec2<f32>(-1.0, -1.0);
-                }
-                case 2u, 4u: {
-                    vertex = vec2<f32>(1.0, -1.0);
-                }
-                case 5u: {
-                    vertex = vec2<f32>(1.0, 1.0);
-                }
-                default: {}
-            }
-            return vec4<f32>(vertex, 0.0, 1.0);
-        }
-        
-        @group(0) @binding(0)
-        var fine_output: texture_2d<f32>;
-        
-        @fragment
-        fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
-            return textureLoad(fine_output, vec2<i32>(pos.xy), 0);
-        }
-    "#;
-
-    let shader = state
-        .device
-        .create_shader_module(wgpu::ShaderModuleDescriptor {
-            label: Some("blit shaders"),
-            source: wgpu::ShaderSource::Wgsl(SHADERS.into()),
-        });
-    let bind_group_layout =
-        state
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                label: None,
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    visibility: wgpu::ShaderStages::FRAGMENT,
-                    binding: 0,
-                    ty: wgpu::BindingType::Texture {
-                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
-                        view_dimension: wgpu::TextureViewDimension::D2,
-                        multisampled: false,
-                    },
-                    count: None,
-                }],
-            });
-    let pipeline_layout = state
-        .device
-        .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-            label: None,
-            bind_group_layouts: &[&bind_group_layout],
-            push_constant_ranges: &[],
-        });
-    let pipeline = state
-        .device
-        .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-            label: None,
-            layout: Some(&pipeline_layout),
-            vertex: wgpu::VertexState {
-                module: &shader,
-                entry_point: "vs_main",
-                buffers: &[],
-            },
-            fragment: Some(wgpu::FragmentState {
-                module: &shader,
-                entry_point: "fs_main",
-                targets: &[Some(wgpu::ColorTargetState {
-                    format: state.surface_config.format,
-                    blend: None,
-                    write_mask: wgpu::ColorWrites::ALL,
-                })],
-            }),
-            primitive: wgpu::PrimitiveState {
-                topology: wgpu::PrimitiveTopology::TriangleList,
-                strip_index_format: None,
-                front_face: wgpu::FrontFace::Ccw,
-                cull_mode: Some(wgpu::Face::Back),
-                polygon_mode: wgpu::PolygonMode::Fill,
-                unclipped_depth: false,
-                conservative: false,
-            },
-            depth_stencil: None,
-            multisample: wgpu::MultisampleState {
-                count: 1,
-                mask: !0,
-                alpha_to_coverage_enabled: false,
-            },
-            multiview: None,
-        });
-    (bind_group_layout, pipeline)
-}
diff --git a/piet-wgsl/src/render.rs b/piet-wgsl/src/render.rs
index e747d41..aef46c1 100644
--- a/piet-wgsl/src/render.rs
+++ b/piet-wgsl/src/render.rs
@@ -6,7 +6,6 @@
 use crate::{
     engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy},
     shaders::{self, FullShaders, Shaders},
-    Dimensions,
 };
 
 const TAG_MONOID_SIZE: u64 = 12;
@@ -62,6 +61,7 @@
     }
 }
 
+#[allow(unused)]
 fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
     let mut recording = Recording::default();
     let data = scene.data();
@@ -140,7 +140,8 @@
 pub fn render_full(
     scene: &Scene,
     shaders: &FullShaders,
-    dimensions: &Dimensions,
+    width: u32,
+    height: u32,
 ) -> (Recording, ResourceProxy) {
     let mut recording = Recording::default();
     let mut ramps = crate::ramp::RampCache::default();
@@ -209,15 +210,15 @@
     let n_drawobj = n_path;
     let n_clip = data.n_clip;
 
-    let new_width = next_multiple_of(dimensions.width, 16);
-    let new_height = next_multiple_of(dimensions.height, 16);
+    let new_width = next_multiple_of(width, 16);
+    let new_height = next_multiple_of(height, 16);
 
     let config = Config {
         // TODO: Replace with div_ceil once stable
         width_in_tiles: new_width / 16,
         height_in_tiles: new_height / 16,
-        target_width: dimensions.width,
-        target_height: dimensions.height,
+        target_width: width,
+        target_height: height,
         n_drawobj,
         n_path,
         n_clip,
@@ -402,7 +403,7 @@
             ptcl_buf,
         ],
     );
-    let out_image = ImageProxy::new(dimensions.width, dimensions.height, ImageFormat::Rgba8);
+    let out_image = ImageProxy::new(width, height, ImageFormat::Rgba8);
     recording.dispatch(
         shaders.fine,
         (config.width_in_tiles, config.height_in_tiles, 1),
diff --git a/piet-wgsl/src/shaders.rs b/piet-wgsl/src/shaders.rs
index a011c58..8cb6129 100644
--- a/piet-wgsl/src/shaders.rs
+++ b/piet-wgsl/src/shaders.rs
@@ -18,7 +18,7 @@
 
 mod preprocess;
 
-use std::{collections::HashSet, fs, path::Path};
+use std::collections::HashSet;
 
 use wgpu::Device;
 
@@ -30,6 +30,12 @@
 pub const PATH_DRAWOBJ_WG: u32 = 256;
 pub const CLIP_REDUCE_WG: u32 = 256;
 
+macro_rules! shader {
+    ($name:expr) => {
+        include_str!(concat!(concat!("../shader/", $name), ".wgsl"))
+    };
+}
+
 pub struct Shaders {
     pub pathtag_reduce: ShaderId,
     pub pathtag_scan: ShaderId,
@@ -57,14 +63,14 @@
 }
 
 pub fn init_shaders(device: &Device, engine: &mut Engine) -> Result<Shaders, Error> {
-    let shader_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/shader"));
-    let imports = preprocess::get_imports(shader_dir);
-    let read_shader =
-        |path: &str| fs::read_to_string(shader_dir.join(path.to_string() + ".wgsl")).unwrap();
+    let imports = SHARED_SHADERS
+        .iter()
+        .copied()
+        .collect::<std::collections::HashMap<_, _>>();
     let empty = HashSet::new();
     let pathtag_reduce = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("pathtag_reduce"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("pathtag_reduce"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -73,7 +79,7 @@
     )?;
     let pathtag_scan = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("pathtag_scan"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("pathtag_scan"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -86,7 +92,7 @@
 
     let path_coarse = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("path_coarse"), &path_coarse_config, &imports).into(),
+        preprocess::preprocess(shader!("path_coarse"), &path_coarse_config, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -97,12 +103,12 @@
     )?;
     let backdrop = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("backdrop"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("backdrop"), &empty, &imports).into(),
         &[BindType::BufReadOnly, BindType::Buffer],
     )?;
     let fine = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("fine"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("fine"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -120,16 +126,16 @@
 }
 
 pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result<FullShaders, Error> {
-    let shader_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/shader"));
-    let imports = preprocess::get_imports(shader_dir);
-    let read_shader =
-        |path: &str| fs::read_to_string(shader_dir.join(path.to_string() + ".wgsl")).unwrap();
+    let imports = SHARED_SHADERS
+        .iter()
+        .copied()
+        .collect::<std::collections::HashMap<_, _>>();
     let empty = HashSet::new();
     let mut full_config = HashSet::new();
     full_config.insert("full".into());
     let pathtag_reduce = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("pathtag_reduce"), &full_config, &imports).into(),
+        preprocess::preprocess(shader!("pathtag_reduce"), &full_config, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -138,7 +144,7 @@
     )?;
     let pathtag_scan = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("pathtag_scan"), &full_config, &imports).into(),
+        preprocess::preprocess(shader!("pathtag_scan"), &full_config, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -148,12 +154,12 @@
     )?;
     let bbox_clear = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("bbox_clear"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("bbox_clear"), &empty, &imports).into(),
         &[BindType::BufReadOnly, BindType::Buffer],
     )?;
     let pathseg = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("pathseg"), &full_config, &imports).into(),
+        preprocess::preprocess(shader!("pathseg"), &full_config, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -164,7 +170,7 @@
     )?;
     let draw_reduce = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("draw_reduce"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("draw_reduce"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -173,7 +179,7 @@
     )?;
     let draw_leaf = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("draw_leaf"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("draw_leaf"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -186,7 +192,7 @@
     )?;
     let clip_reduce = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("clip_reduce"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("clip_reduce"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -197,7 +203,7 @@
     )?;
     let clip_leaf = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("clip_leaf"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("clip_leaf"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -210,7 +216,7 @@
     )?;
     let binning = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("binning"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("binning"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -224,7 +230,7 @@
     )?;
     let tile_alloc = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("tile_alloc"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("tile_alloc"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -237,7 +243,7 @@
 
     let path_coarse = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("path_coarse_full"), &full_config, &imports).into(),
+        preprocess::preprocess(shader!("path_coarse_full"), &full_config, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -251,7 +257,7 @@
     )?;
     let backdrop = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("backdrop_dyn"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("backdrop_dyn"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -260,7 +266,7 @@
     )?;
     let coarse = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("coarse"), &empty, &imports).into(),
+        preprocess::preprocess(shader!("coarse"), &empty, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -276,7 +282,7 @@
     )?;
     let fine = engine.add_shader(
         device,
-        preprocess::preprocess(&read_shader("fine"), &full_config, &imports).into(),
+        preprocess::preprocess(shader!("fine"), &full_config, &imports).into(),
         &[
             BindType::BufReadOnly,
             BindType::BufReadOnly,
@@ -303,3 +309,27 @@
         fine,
     })
 }
+
+macro_rules! shared_shader {
+    ($name:expr) => {
+        (
+            $name,
+            include_str!(concat!(concat!("../shader/shared/", $name), ".wgsl")),
+        )
+    };
+}
+
+const SHARED_SHADERS: &[(&str, &str)] = &[
+    shared_shader!("bbox"),
+    shared_shader!("blend"),
+    shared_shader!("bump"),
+    shared_shader!("clip"),
+    shared_shader!("config"),
+    shared_shader!("cubic"),
+    shared_shader!("bbox"),
+    shared_shader!("drawtag"),
+    shared_shader!("pathtag"),
+    shared_shader!("ptcl"),
+    shared_shader!("segment"),
+    shared_shader!("tile"),
+];
diff --git a/piet-wgsl/src/shaders/preprocess.rs b/piet-wgsl/src/shaders/preprocess.rs
index 8da2e28..ac731c2 100644
--- a/piet-wgsl/src/shaders/preprocess.rs
+++ b/piet-wgsl/src/shaders/preprocess.rs
@@ -34,11 +34,7 @@
     else_passed: bool,
 }
 
-pub fn preprocess(
-    input: &str,
-    defines: &HashSet<String>,
-    imports: &HashMap<String, String>,
-) -> String {
+pub fn preprocess(input: &str, defines: &HashSet<String>, imports: &HashMap<&str, &str>) -> String {
     let mut output = String::with_capacity(input.len());
     let mut stack = vec![];
     'all_lines: for (line_number, mut line) in input.lines().enumerate() {
diff --git a/piet-wgsl/src/util.rs b/piet-wgsl/src/util.rs
new file mode 100644
index 0000000..1422f7a
--- /dev/null
+++ b/piet-wgsl/src/util.rs
@@ -0,0 +1,87 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Also licensed under MIT license, at your choice.
+
+//! Simple helpers for managing wgpu state and surfaces.
+
+use super::Result;
+
+use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
+use wgpu::{Device, Instance, Limits, Queue, Surface, SurfaceConfiguration};
+
+/// Simple render context that maintains wgpu state for rendering the pipeline.
+pub struct RenderContext {
+    pub instance: Instance,
+    pub device: Device,
+    pub queue: Queue,
+}
+
+impl RenderContext {
+    pub async fn new() -> Result<Self> {
+        let instance = Instance::new(wgpu::Backends::PRIMARY);
+        let adapter = instance.request_adapter(&Default::default()).await.unwrap();
+        let features = adapter.features();
+        let mut limits = Limits::default();
+        limits.max_storage_buffers_per_shader_stage = 16;
+        let (device, queue) = adapter
+            .request_device(
+                &wgpu::DeviceDescriptor {
+                    label: None,
+                    features: features
+                        & (wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::CLEAR_TEXTURE),
+                    limits,
+                },
+                None,
+            )
+            .await?;
+        Ok(Self {
+            instance,
+            device,
+            queue,
+        })
+    }
+
+    /// Creates a new surface for the specified window and dimensions.
+    pub fn create_surface<W>(&self, window: &W, width: u32, height: u32) -> RenderSurface
+    where
+        W: HasRawWindowHandle + HasRawDisplayHandle,
+    {
+        let surface = unsafe { self.instance.create_surface(window) };
+        let format = wgpu::TextureFormat::Bgra8Unorm;
+        let config = wgpu::SurfaceConfiguration {
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+            format,
+            width,
+            height,
+            present_mode: wgpu::PresentMode::Fifo,
+            alpha_mode: wgpu::CompositeAlphaMode::Auto,
+        };
+        surface.configure(&self.device, &config);
+        RenderSurface { surface, config }
+    }
+
+    /// Resizes the surface to the new dimensions.
+    pub fn resize_surface(&self, surface: &mut RenderSurface, width: u32, height: u32) {
+        surface.config.width = width;
+        surface.config.height = height;
+        surface.surface.configure(&self.device, &surface.config);
+    }
+}
+
+/// Combination of surface and its configuration.
+pub struct RenderSurface {
+    pub surface: Surface,
+    pub config: SurfaceConfiguration,
+}