From c1331b4f3bc0f34e20a89ed7a59dd29591755629 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:56:01 +0000 Subject: [PATCH 1/5] Initial plan From 6455bda5f753c9d01cde990a7dfbb8e8b22c6309 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:11:08 +0000 Subject: [PATCH 2/5] Cherry-pick bug fixes from upstream PR #66 Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com> --- js/regl/main.js | 2 +- js/regl/mirrorPass.js | 36 ++++++++++++++++++++++-------------- js/webgpu/bloomPass.js | 10 +++++++--- js/webgpu/main.js | 1 + js/webgpu/mirrorPass.js | 39 ++++++++++++++++++++------------------- 5 files changed, 51 insertions(+), 37 deletions(-) diff --git a/js/regl/main.js b/js/regl/main.js index d7b1a74..e4d00ae 100644 --- a/js/regl/main.js +++ b/js/regl/main.js @@ -73,7 +73,7 @@ export default async (canvas, config) => { }; const effects = createEffectsMapping("regl", passModules); const effectPass = getEffectPass(config.effect, effects, "palette"); - const context = { regl, config, lkg, cameraTex, cameraAspectRatio }; + const context = { regl, canvas, config, lkg, cameraTex, cameraAspectRatio }; const pipeline = makePipeline(context, [makeRain, makeBloomPass, effectPass, makeQuiltPass]); const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary }; const drawToScreen = regl({ uniforms: screenUniforms }); diff --git a/js/regl/mirrorPass.js b/js/regl/mirrorPass.js index 3d26fea..0d6d973 100644 --- a/js/regl/mirrorPass.js +++ b/js/regl/mirrorPass.js @@ -1,19 +1,22 @@ import { loadText, makePassFBO, makePass } from "./utils.js"; -let start; -const numClicks = 5; -const clicks = Array(numClicks).fill([0, 0, -Infinity]).flat(); -let aspectRatio = 1; +export default ({ regl, canvas, config, cameraTex, cameraAspectRatio }, inputs) => { + let start; + const numClicks = 5; + const clicks = Array(numClicks) + .fill() + .map((_) => [0, 0, -Infinity]); + let aspectRatio = 1; -let index = 0; -window.onclick = (e) => { - clicks[index * 3 + 0] = 0 + e.clientX / e.srcElement.clientWidth; - clicks[index * 3 + 1] = 1 - e.clientY / e.srcElement.clientHeight; - clicks[index * 3 + 2] = (Date.now() - start) / 1000; - index = (index + 1) % numClicks; -}; + let index = 0; + canvas.onmousedown = (e) => { + const rect = e.srcElement.getBoundingClientRect(); + clicks[index][0] = 0 + (e.clientX - rect.x) / rect.width; + clicks[index][1] = 1 - (e.clientY - rect.y) / rect.height; + clicks[index][2] = (performance.now() - start) / 1000; + index = (index + 1) % numClicks; + }; -export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => { const output = makePassFBO(regl, config.useHalfFloat); const mirrorPassFrag = loadText("shaders/glsl/mirrorPass.frag.glsl"); const render = regl({ @@ -23,14 +26,19 @@ export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => { tex: inputs.primary, bloomTex: inputs.bloom, cameraTex, - clicks: () => clicks, + // REGL bug can misinterpret array uniforms + ["clicks[0]"]: () => clicks[0], + ["clicks[1]"]: () => clicks[1], + ["clicks[2]"]: () => clicks[2], + ["clicks[3]"]: () => clicks[3], + ["clicks[4]"]: () => clicks[4], aspectRatio: () => aspectRatio, cameraAspectRatio, }, framebuffer: output, }); - start = Date.now(); + start = performance.now(); return makePass( { diff --git a/js/webgpu/bloomPass.js b/js/webgpu/bloomPass.js index 88e5c97..2bb2b3c 100644 --- a/js/webgpu/bloomPass.js +++ b/js/webgpu/bloomPass.js @@ -20,7 +20,7 @@ const makePyramid = (device, size, pyramidHeight) => .map((_, index) => makeComputeTarget( device, - size.map((x) => Math.floor(x * 2 ** -index)), + size.map((x) => Math.max(1, Math.floor(x * 2 ** -index))), ), ); @@ -102,7 +102,7 @@ export default ({ config, device }) => { const build = (screenSize, inputs) => { // Since the bloom is blurry, we downscale everything - scaledScreenSize = screenSize.map((x) => Math.floor(x * bloomSize)); + scaledScreenSize = screenSize.map((x) => Math.max(1, Math.floor(x * bloomSize))); destroyPyramid(hBlurPyramid); hBlurPyramid = makePyramid(device, scaledScreenSize, pyramidHeight); @@ -144,7 +144,11 @@ export default ({ config, device }) => { computePass.setPipeline(blurPipeline); for (let i = 0; i < pyramidHeight; i++) { - const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32), Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)), 1]; + const dispatchSize = [ + Math.max(1, Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32)), + Math.max(1, Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i))), + 1, + ]; computePass.setBindGroup(0, hBlurBindGroups[i]); computePass.dispatchWorkgroups(...dispatchSize); computePass.setBindGroup(0, vBlurBindGroups[i]); diff --git a/js/webgpu/main.js b/js/webgpu/main.js index 5a7fe7b..9d30d8f 100644 --- a/js/webgpu/main.js +++ b/js/webgpu/main.js @@ -59,6 +59,7 @@ export default async (canvas, config) => { config, adapter, device, + canvas, canvasContext, timeBuffer, canvasFormat, diff --git a/js/webgpu/mirrorPass.js b/js/webgpu/mirrorPass.js index 9de12af..2da85a5 100644 --- a/js/webgpu/mirrorPass.js +++ b/js/webgpu/mirrorPass.js @@ -1,24 +1,7 @@ import { structs } from "../../lib/gpu-buffer.js"; import { makeComputeTarget, makeUniformBuffer, loadShader, makeBindGroup, makePass } from "./utils.js"; -let start; -const numTouches = 5; -const touches = Array(numTouches) - .fill() - .map((_) => [0, 0, -Infinity, 0]); -let aspectRatio = 1; - -let index = 0; -let touchesChanged = true; -window.onclick = (e) => { - touches[index][0] = 0 + e.clientX / e.srcElement.clientWidth; - touches[index][1] = 1 - e.clientY / e.srcElement.clientHeight; - touches[index][2] = (Date.now() - start) / 1000; - index = (index + 1) % numTouches; - touchesChanged = true; -}; - -export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => { +export default ({ config, device, canvas, cameraTex, cameraAspectRatio, timeBuffer }) => { const assets = [loadShader(device, "shaders/wgsl/mirrorPass.wgsl")]; const linearSampler = device.createSampler({ @@ -26,6 +9,24 @@ export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => minFilter: "linear", }); + let start; + const numTouches = 5; + const touches = Array(numTouches) + .fill() + .map((_) => [0, 0, -Infinity, 0]); + let aspectRatio = 1; + + let index = 0; + let touchesChanged = true; + canvas.onmousedown = (e) => { + const rect = e.srcElement.getBoundingClientRect(); + touches[index][0] = 0 + (e.clientX - rect.x) / rect.width; + touches[index][1] = 1 - (e.clientY - rect.y) / rect.height; + touches[index][2] = (performance.now() - start) / 1000; + index = (index + 1) % numTouches; + touchesChanged = true; + }; + let computePipeline; let configBuffer; let sceneUniforms; @@ -99,7 +100,7 @@ export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => computePass.end(); }; - start = Date.now(); + start = performance.now(); return makePass("Mirror", loaded, build, run); }; From 7a41e2dc8bbc1595094f1952982bd83b9770ca14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:08:18 +0000 Subject: [PATCH 3/5] Add PR preview deployment workflow Co-authored-by: ap0ught <41078+ap0ught@users.noreply.github.com> --- .github/PR_PREVIEW.md | 82 +++++++++++ .github/workflows/pr-preview.yml | 246 +++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 .github/PR_PREVIEW.md create mode 100644 .github/workflows/pr-preview.yml diff --git a/.github/PR_PREVIEW.md b/.github/PR_PREVIEW.md new file mode 100644 index 0000000..1e991e1 --- /dev/null +++ b/.github/PR_PREVIEW.md @@ -0,0 +1,82 @@ +# PR Preview Deployment + +This repository has automated PR preview deployments set up via GitHub Actions. + +## How It Works + +When a Pull Request is created or updated: + +1. The PR Preview workflow automatically deploys the PR branch to GitHub Pages +2. Each PR gets its own subdirectory: `https://ap0ught.github.io/matrix/pr-{number}/` +3. A comment is posted on the PR with links to test the preview +4. The preview is automatically updated when new commits are pushed + +## Manual Deployment + +You can also manually trigger a preview deployment: + +1. Go to the Actions tab +2. Select "PR Preview Deployment" +3. Click "Run workflow" +4. Select the branch you want to deploy + +## Preview URLs + +After deployment, you can access your PR preview at: + +- **Main preview:** `https://ap0ught.github.io/matrix/pr-{number}/` +- **With options:** `https://ap0ught.github.io/matrix/pr-{number}/?suppressWarnings=true` + +### Test Links + +The automated PR comment includes convenient test links for different Matrix versions: + +- Default Matrix effect +- Mirror mode with mouse interaction +- 3D volumetric mode +- Resurrections version + +## Cleanup + +PR previews remain on GitHub Pages until manually removed. To clean up old previews: + +1. Check out the `gh-pages` branch +2. Remove the `pr-{number}` directory +3. Commit and push + +## Requirements + +- GitHub Pages must be enabled for the repository +- The workflow requires these permissions: + - `contents: write` - to push to gh-pages branch + - `pages: write` - to deploy to GitHub Pages + - `pull-requests: write` - to comment on PRs + +## Technical Details + +- **Workflow file:** `.github/workflows/pr-preview.yml` +- **Deployment branch:** `gh-pages` +- **Directory structure:** Each PR gets its own subdirectory +- **Files deployed:** `index.html`, `js/`, `lib/`, `assets/`, `shaders/` + +## Limitations + +- This is a static site deployment - no server-side processing +- Previews don't require any build step (keeping with the project's philosophy) +- Each preview is a complete copy of the web application + +## Local Testing Alternative + +If you prefer to test locally instead of using the preview: + +```bash +# Clone and checkout the PR branch +git fetch origin pull/{PR_NUMBER}/head:pr-{PR_NUMBER} +git checkout pr-{PR_NUMBER} + +# Start a local server +python3 -m http.server 8000 + +# Open in browser +open http://localhost:8000/?suppressWarnings=true +``` diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml new file mode 100644 index 0000000..b93cdb5 --- /dev/null +++ b/.github/workflows/pr-preview.yml @@ -0,0 +1,246 @@ +name: PR Preview Deployment + +# Deploy PR branches to GitHub Pages at /pr-{number}/ for testing +# This allows testing changes without merging to master + +on: + workflow_dispatch: # Allow manual trigger + pull_request: + types: [opened, synchronize, reopened] + branches: + - master + +permissions: + contents: write + pages: write + id-token: write + pull-requests: write + +jobs: + deploy-preview: + name: Deploy PR Preview + runs-on: ubuntu-latest + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref || github.ref }} + fetch-depth: 1 + + - name: Determine preview path + id: preview + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + PR_NUMBER="${{ github.event.pull_request.number }}" + PREVIEW_PATH="pr-${PR_NUMBER}" + else + # Manual trigger - use branch name + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + PREVIEW_PATH="pr-${BRANCH_NAME//\//-}" + fi + echo "path=${PREVIEW_PATH}" >> $GITHUB_OUTPUT + echo "url=https://ap0ught.github.io/matrix/${PREVIEW_PATH}/" >> $GITHUB_OUTPUT + echo "Preview will be deployed to: ${PREVIEW_PATH}" + + - name: Checkout gh-pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + path: gh-pages + fetch-depth: 1 + continue-on-error: true + + - name: Initialize gh-pages if needed + run: | + if [ ! -d "gh-pages" ]; then + echo "Creating new gh-pages branch" + mkdir -p gh-pages + cd gh-pages + git init + git checkout -b gh-pages + echo "# Matrix PR Previews" > README.md + echo "" >> README.md + echo "This branch contains PR preview deployments." >> README.md + echo "Main site: https://ap0ught.github.io/matrix/" >> README.md + cd .. + fi + + - name: Prepare preview directory + run: | + PREVIEW_PATH="${{ steps.preview.outputs.path }}" + + # Create preview directory in gh-pages + mkdir -p "gh-pages/${PREVIEW_PATH}" + + # Copy web application files to preview directory + cp index.html "gh-pages/${PREVIEW_PATH}/" + cp -r js "gh-pages/${PREVIEW_PATH}/" + cp -r lib "gh-pages/${PREVIEW_PATH}/" + cp -r assets "gh-pages/${PREVIEW_PATH}/" + cp -r shaders "gh-pages/${PREVIEW_PATH}/" + + # Optional files (don't fail if missing) + cp README.md "gh-pages/${PREVIEW_PATH}/" 2>/dev/null || true + cp screenshot.png "gh-pages/${PREVIEW_PATH}/" 2>/dev/null || true + + echo "✅ Preview files copied to gh-pages/${PREVIEW_PATH}" + ls -la "gh-pages/${PREVIEW_PATH}" + + - name: Update gh-pages index + run: | + cd gh-pages + + # Create or update index.html with list of previews + cat > index.html <<'HTMLEOF' + + +
+ + +