Starting some very simple, speculative WebXR+WebGPU samples
diff --git a/media/logo/webgpu-logo.svg b/media/logo/webgpu-logo.svg
new file mode 100644
index 0000000..69956b1
--- /dev/null
+++ b/media/logo/webgpu-logo.svg
@@ -0,0 +1,45 @@
+<svg id="Logo" xmlns="http://www.w3.org/2000/svg" width="932" height="315" viewBox="0 0 932 315">
+  <defs>
+    <style>
+      .cls-1, .cls-2, .cls-3, .cls-4, .cls-5 {
+        fill-rule: evenodd;
+        stroke-linejoin: round;
+      }
+
+      .cls-6 {
+        fill-rule: evenodd;
+      }
+
+      .cls-1 {
+        fill: #005a9c;
+        stroke: #005a9c;
+      }
+
+      .cls-2 {
+        fill: #0066b0;
+        stroke: #0066b0;
+      }
+
+      .cls-3 {
+        fill: #0076cc;
+        stroke: #0076cc;
+      }
+
+      .cls-4 {
+        fill: #0086e8;
+        stroke: #0086e8;
+      }
+
+      .cls-5 {
+        fill: #0093ff;
+        stroke: #0093ff;
+      }
+    </style>
+  </defs>
+  <path id="Triangle_4" data-name="Triangle 4" class="cls-4" d="m298.4 134.84-24.6-42.609h49.269z"/>
+  <path id="Triangle_5" data-name="Triangle 5" class="cls-5" d="m298.4 49.619-24.6 42.609h49.269z"/>
+  <path id="Triangle_3" data-name="Triangle 3" class="cls-3" d="m249.2 220.06-49.2-85.218 98.4-8.1e-4z"/>
+  <path id="Triangle_2" data-name="Triangle 2" class="cls-2" d="m249.2 49.619-49.2 85.218 98.4-8.1e-4z"/>
+  <path id="Triangle_1" data-name="Triangle 1" class="cls-1" d="m150.8 220.06-98.4-170.44h196.8z"/>
+  <path id="WebGPU" class="cls-6" d="M397.927,170a46.383,46.383,0,0,0-5.459-22.594A39.579,39.579,0,0,0,376.929,131.7a46.217,46.217,0,0,0-23.182-5.711,47.758,47.758,0,0,0-23.77,5.8,40.1,40.1,0,0,0-16.042,16.547q-5.714,10.752-5.712,25.029t5.88,25.114a41.6,41.6,0,0,0,16.21,16.714,46.512,46.512,0,0,0,23.434,5.88q16.461,0,27.55-8.315a39.574,39.574,0,0,0,14.782-21.586H379.617a24.465,24.465,0,0,1-9.323,12.347q-6.639,4.621-16.547,4.619a29.643,29.643,0,0,1-20.578-7.643q-8.484-7.642-9.323-21.25h73.577a89.923,89.923,0,0,0,.5-9.239h0Zm-15.791-3.192H324.014q1.173-13.1,9.323-20.494a28.334,28.334,0,0,1,19.738-7.391,32.751,32.751,0,0,1,14.7,3.275,25.147,25.147,0,0,1,10.5,9.575,28.21,28.21,0,0,1,3.863,15.035h0Zm51.235-22.174V95.246H418.084V219.554h15.287V202.588a35.4,35.4,0,0,0,13.523,13.27,40.941,40.941,0,0,0,20.914,5.208,41.875,41.875,0,0,0,37.964-23.014,52.851,52.851,0,0,0,5.712-24.862q0-14.111-5.712-24.777a41.653,41.653,0,0,0-15.622-16.547,42.965,42.965,0,0,0-22.342-5.879,40.763,40.763,0,0,0-20.662,5.207,36.146,36.146,0,0,0-13.775,13.439h0Zm62.49,28.557a38.312,38.312,0,0,1-4.2,18.4,29.535,29.535,0,0,1-11.338,11.927,30.887,30.887,0,0,1-15.707,4.115,30.558,30.558,0,0,1-15.539-4.115,30.072,30.072,0,0,1-11.422-11.927,40.557,40.557,0,0,1,0-36.285,30.092,30.092,0,0,1,11.422-11.927,30.578,30.578,0,0,1,15.539-4.115,31.4,31.4,0,0,1,15.707,4.031,29.1,29.1,0,0,1,11.338,11.759,37.449,37.449,0,0,1,4.2,18.142h0ZM638.647,136.4a52.884,52.884,0,0,0-20.914-26.038q-14.362-9.236-33.009-9.239a59.092,59.092,0,0,0-29.733,7.643,55.959,55.959,0,0,0-21.25,21.334,61.09,61.09,0,0,0-7.812,30.826,60.623,60.623,0,0,0,7.812,30.741,56.1,56.1,0,0,0,21.25,21.25,59.062,59.062,0,0,0,29.733,7.643,56.956,56.956,0,0,0,28.053-6.971A56.042,56.042,0,0,0,633.1,194.608a58.17,58.17,0,0,0,9.071-26.457V156.392H579.181v12.431h46.7q-1.849,17.472-12.935,27.717t-28.221,10.247a43.634,43.634,0,0,1-22.09-5.627,39.861,39.861,0,0,1-15.454-16.043q-5.631-10.413-5.628-24.189t5.628-24.274a39.769,39.769,0,0,1,15.454-16.127,43.654,43.654,0,0,1,22.09-5.627,40.52,40.52,0,0,1,21.5,5.627A36.445,36.445,0,0,1,620.337,136.4h18.31Zm101.967,0.336q0-15.118-10.332-24.694t-30.153-9.575h-37.8V219.554h15.286V170.671h22.51q20.493,0,30.489-9.659t10-24.274h0Zm-40.485,21.334h-22.51v-43h22.51q24.859,0,24.862,21.67,0,10.248-6.047,15.79t-18.815,5.544h0Zm59.13-55.6v73.913q0,14.614,5.795,24.61a36.391,36.391,0,0,0,15.791,14.866,54.05,54.05,0,0,0,44.852,0,36.911,36.911,0,0,0,15.874-14.866q5.878-9.993,5.88-24.61V102.469H832.164V176.55q0,15.623-7.643,23.182t-21.082,7.559q-13.606,0-21.25-7.559t-7.643-23.182V102.469H759.259Z"/>
+</svg>
diff --git a/webgpu/index.html b/webgpu/index.html
new file mode 100644
index 0000000..f0d97ce
--- /dev/null
+++ b/webgpu/index.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset='utf-8'>
+    <meta http-equiv='X-UA-Compatible' content='chrome=1'>
+    <meta name='viewport' content='width=device-width, initial-scale=1'>
+    <meta name='mobile-web-app-capable' content='yes'>
+    <meta name='apple-mobile-web-app-capable' content='yes'>
+
+    <meta name='twitter:card' content='summary'>
+    <meta name='twitter:title' content='WebXR Samples'>
+    <meta name='twitter:description' content='Sample WebXR pages for testing and reference'>
+
+    <link rel='icon' type='image/png' sizes='32x32' href='../favicon-32x32.png'>
+    <link rel='icon' type='image/png' sizes='96x96' href='../favicon-96x96.png'>
+
+    <link rel='stylesheet' href='../css/stylesheet.css'>
+    <link rel='stylesheet' href='../css/pygment_trac.css'>
+
+    <style>
+      article {
+        position: relative;
+        padding: 0.5em;
+        background-color: rgba(255, 255, 255, 0.90);
+        margin-bottom: 1em;
+        border-radius: 3px;
+      }
+
+      article h3 {
+        font-size: 1.0em;
+        margin-top: 0px;
+        margin-bottom: 0px;
+      }
+
+      article h3::before {
+        display: inline-block;
+        content: attr(data-index) ' - ';
+        font-weight: bold;
+        white-space: nowrap;
+        margin-right: 0.2em;
+      }
+
+      article h4 {
+        position: absolute;
+        right: 0.5em;
+        top: 0.5em;
+        margin-top: 0px;
+        margin-bottom: 0px;
+      }
+
+      article p {
+        margin: 0.5em;
+      }
+
+      article .links {
+        margin-left: 0.5em;
+        margin-right: 0.5em;
+      }
+
+      article a {
+        display: inline-block;
+      }
+
+      article a:not(:first-child)::before {
+        display: inline-block;
+        content: '•';
+        font-weight: bold;
+        white-space: nowrap;
+        margin-left: 0.5em;
+        margin-right: 0.5em;
+      }
+
+      .github-link {
+        font-size: 0.8em;
+      }
+
+      .wordmark img {
+        width: 70%;
+      }
+    </style>
+
+    <!--[if lt IE 9]>
+    <script src='https://html5shiv.googlecode.com/svn/trunk/html5.js'></script>
+    <![endif]-->
+    <title>WebXR - WebGPU</title>
+  </head>
+  <body>
+
+    <div class='container' id='container'>
+      <header class='header'>
+        <div id='nav'>
+            <a href='../'>Samples</a>
+            <a href='../layers-samples/'>Layers Samples</a>
+            <a href='./' class='selected'>WebGPU Samples</a>
+            <a href='../proposals/'>Proposals</a>
+            <a href='../tests/'>Test Pages</a>
+            <a href='../report/'>Report</a>
+        </div>
+
+        <h1>
+          <a href='' class='wordmark'>
+            <img src='../media/logo/webxr-logo.svg' alt='WebXR Logo'/><br/>
+            ❤️<br/>
+            <img src='../media/logo/webgpu-logo.svg' alt='WebGPU Logo'/>
+          </a>
+        </h1>
+        <h2 class='tagline'>WebGPU</h2>
+      </header>
+
+      <main class='main' id='main'>
+        <p>These pages test WebGPU integration with WebXR. They intentionally do not copy the WebGL-based samples 1:1
+          because a significant portion of the API functions identically regardless of which API is being used for
+          rendering.<br/>
+
+        <script>
+          let pages = [
+            { title: 'WebGPU VR barebones', category: 'WebGPU',
+              path: 'vr-barebones.html',
+              description: 'Extremely simple use of "immersive-vr" sessions that presents a WebGPU layer with no library dependencies. Doesn\'t render anything exciting.',
+              noPolyfill: true },
+          ];
+
+          let mainElement = document.getElementById("main");
+
+          // Append an element for every item in the pages list.
+          for (var i = 0; i < pages.length; ++i) {
+            var page = pages[i];
+
+            let article = document.createElement('article');
+
+            let title = document.createElement('h3');
+            title.setAttribute('data-index', i + 1);
+
+            let titleLink = document.createElement('a');
+            titleLink.href = page.path;
+            titleLink.textContent = page.title;
+            title.appendChild(titleLink);
+            article.appendChild(title);
+
+            let category = document.createElement('h4');
+            category.textContent = page.category;
+            article.appendChild(category);
+
+            let description = document.createElement('p');
+            description.textContent = page.description;
+            article.appendChild(description);
+
+            let links = document.createElement('div');
+            links.classList.add('links');
+
+            let sourceLink = document.createElement('a');
+            sourceLink.href = 'https://github.com/immersive-web/webxr-samples/blob/master/webgpu/' + page.path;
+            sourceLink.textContent = 'Source';
+            links.appendChild(sourceLink);
+
+            article.appendChild(links);
+
+            mainElement.appendChild(article);
+          }
+        </script>
+      </main>
+
+      <br/>
+
+      <h3><a class='github-link' href='https://github.com/immersive-web/webxr-samples'>View source on GitHub</a></h3>
+
+      <footer class='footer'>
+      </footer>
+    </div>
+  </body>
+</html>
diff --git a/webgpu/vr-barebones.html b/webgpu/vr-barebones.html
new file mode 100644
index 0000000..06c5b2f
--- /dev/null
+++ b/webgpu/vr-barebones.html
@@ -0,0 +1,228 @@
+<!doctype html>
+<!--
+Copyright 2023 The Immersive Web Community Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<html>
+  <head>
+    <meta charset='utf-8'>
+    <meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
+    <meta name='mobile-web-app-capable' content='yes'>
+    <meta name='apple-mobile-web-app-capable' content='yes'>
+    <link rel='icon' type='image/png' sizes='32x32' href='../favicon-32x32.png'>
+    <link rel='icon' type='image/png' sizes='96x96' href='../favicon-96x96.png'>
+    <link rel='stylesheet' href='../css/common.css'>
+
+    <title>Barebones VR</title>
+  </head>
+  <body>
+    <header>
+      <details open>
+        <summary>WebGPU Barebones VR</summary>
+        <p>
+          This sample demonstrates extremely simple use of an "immersive-vr"
+          session which presents a WebGPU layer with no library dependencies.
+          It doesn't render anything exciting, just clears your headset's
+          display to a slowly changing color to prove it's working.
+          <a class="back" href="./">Back</a>
+        </p>
+        <button id="xr-button" class="barebones-button" disabled>XR not found</button>
+      </details>
+    </header>
+    <main style='text-align: center;'>
+      <p>Click 'Enter VR' to see content</p> 
+    </main>
+    <script>
+      (function () {
+      'use strict';
+
+      // XR globals.
+      let xrButton = document.getElementById('xr-button');
+      let xrSession = null;
+      let xrRefSpace = null;
+
+      // WebGGPU scene globals.
+      let device = null;
+      let xrGpuBinding = null;
+      let xrGpuProjLayer = null;
+
+      // Checks to see if WebXR is available and, if so, requests an XRDevice
+      // that is connected to the system and tests it to ensure it supports the
+      // desired session options.
+      function initXR() {
+        // Is WebXR available on this UA?
+        if (navigator.xr) {
+          // Is WebGPU avaialble on this UA?
+          if (!navigator.gpu) {
+            xrButton.textContent = 'WebGPU not found';
+            return;
+          }
+
+          // If the device allows creation of exclusive sessions set it as the
+          // target of the 'Enter XR' button.
+          navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
+            if (supported) {
+              // Updates the button to start an XR session when clicked.
+              xrButton.addEventListener('click', onButtonClicked);
+              xrButton.textContent = 'Enter VR';
+              xrButton.disabled = false;
+            }
+          });
+        }
+      }
+
+      // Called when the user clicks the button to enter XR. If we don't have a
+      // session we'll request one, and if we do have a session we'll end it.
+      function onButtonClicked() {
+        if (!xrSession) {
+          navigator.xr.requestSession('immersive-vr', {
+            requiredFeatures: ['layers'],
+          }).then(onSessionStarted);
+        } else {
+          xrSession.end();
+        }
+      }
+
+      // Called when we've successfully acquired a XRSession. In response we
+      // will set up the necessary session state and kick off the frame loop.
+      async function onSessionStarted(session) {
+        xrSession = session;
+        xrButton.textContent = 'Exit VR';
+
+        // Listen for the sessions 'end' event so we can respond if the user
+        // or UA ends the session for any reason.
+        session.addEventListener('end', onSessionEnded);
+
+        // Request a WebGPU adapter to get a device with, initialized to be
+        // compatible with the XRDisplay we're presenting to.
+        const adapter = await navigator.gpu.requestAdapter({
+          xrCompatible: true,
+        });
+
+        if (!adapter) {
+          console.log('Could not request a compatible WebGPU adapter.');
+          xrSession.end();
+        }
+
+        // Create a WebGPU device to render with, initialized to be compatible
+        // with the XRDisplay we're presenting to.
+        device = await adapter.requestDevice();
+
+        xrGpuBinding = new XRWebGPUBinding(session, device);
+
+        // Get a reference space, which is required for querying poses. In this
+        // case an 'local' reference space means that all poses will be relative
+        // to the location where the XRDevice was first detected.
+        session.requestReferenceSpace('local').then((refSpace) => {
+          xrRefSpace = refSpace;
+
+          xrGpuProjLayer = xrGpuBinding.createProjectionLayer({ colorFormat: xrGpuBinding.getPreferredColorFormat() });
+          session.updateRenderState({ layers: [xrGpuProjLayer] });
+
+          // Inform the session that we're ready to begin drawing.
+          session.requestAnimationFrame(onXRFrame);
+        });
+      }
+
+      // Called either when the user has explicitly ended the session by calling
+      // session.end() or when the UA has ended the session for any reason.
+      // At this point the session object is no longer usable and should be
+      // discarded.
+      function onSessionEnded(event) {
+        xrSession = null;
+        xrButton.textContent = 'Enter VR';
+
+        // In this simple case discard the WebGPU device too, since we're not
+        // rendering anything else to the screen with it.
+        device = null;
+        xrGpuBinding = null;
+        xrGpuProjLayer = null;
+      }
+
+      // Called every time the XRSession requests that a new frame be drawn.
+      function onXRFrame(time, frame) {
+        let session = frame.session;
+
+        // Inform the session that we're ready for the next frame.
+        session.requestAnimationFrame(onXRFrame);
+
+        // Get the XRDevice pose relative to the reference space we created
+        // earlier.
+        let pose = frame.getViewerPose(xrRefSpace);
+
+        // Getting the pose may fail if, for example, tracking is lost. So we
+        // have to check to make sure that we got a valid pose before attempting
+        // to render with it. If not in this case we'll just leave the
+        // framebuffer cleared, so tracking loss means the scene will simply
+        // disappear.
+        if (pose) {
+          const commandEncoder = device.createCommandEncoder({});
+
+          // If we do have a valid pose, loop through the views and get the
+          // appropriate sub image for each.
+          for (const view in xrViewerPose.views) {
+            const subImage = xrGpuBinding.getViewSubImage(xrGpuProjLayer, view);
+
+            // The sub image will contain a color texture which is where any
+            // content to be displayed on the XRDevice must be rendered. To
+            // render to the subImage's color texture, use is as a color
+            // attachement in a render pass.
+            const passEncoder = commandEncoder.beginRenderPass({
+              colorAttachments: [{
+                attachment: subImage.colorTexture.createView(subImage.viewDescriptor),
+                loadOp: 'clear',
+
+                // Update the clear color so that we can observe the color in the
+                // headset changing over time.
+                clearValue: [
+                  Math.cos(time / 2000),
+                  Math.cos(time / 4000),
+                  Math.cos(time / 6000),
+                  1
+                ],
+              }]
+            });
+
+            // Normally you'd draw content for the view here into the given
+            // viewport, but we're keeping this sample slim so we're not
+            // bothering to draw any geometry.
+
+            /*
+            let vp = subImage.viewport;
+            passEncoder.setViewport(vp.x, vp.y, vp.width, vp.height, 0.0, 1.0);
+            
+            // Draw a scene using view.projectionMatrix as the projection matrix
+            // and view.transform to position the virtual camera. If you need a
+            // view matrix, use view.transform.inverse.matrix.
+            */
+
+            passEncoder.end();
+          }
+
+          device.defaultQueue.submit([commandEncoder.finish()]);
+        }
+      }
+
+      // Start the XR application.
+      initXR();
+
+    })();
+    </script>
+  </body>
+</html>