├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── npm-publish.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── eslint.config.js ├── package-lock.json ├── package.json ├── packages ├── logo │ ├── package.json │ ├── src │ │ ├── app.d.ts │ │ ├── app.html │ │ └── routes │ │ │ ├── +page.svelte │ │ │ ├── logoDark.png │ │ │ └── shader.frag │ ├── static │ │ └── favicon.png │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.js ├── svader │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── lib │ │ │ ├── BaseShader.svelte │ │ │ ├── WebGlShader.svelte │ │ │ ├── WebGpuShader.svelte │ │ │ ├── devicePixelResizeObserver.js │ │ │ ├── index.js │ │ │ ├── intersectionObserver.js │ │ │ └── utils.js │ ├── svelte.config.js │ └── tsconfig.json ├── tests-svelte4 │ ├── package.json │ ├── src │ │ ├── app.d.ts │ │ ├── app.html │ │ └── routes │ │ │ ├── +layout.server.js │ │ │ ├── +page.svelte │ │ │ ├── hello-world │ │ │ ├── +page.svelte │ │ │ ├── webgl │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.frag │ │ │ └── webgpu │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.wgsl │ │ │ ├── landing-page-bubbles │ │ │ ├── +page.svelte │ │ │ ├── webgl │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.frag │ │ │ └── webgpu │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.wgsl │ │ │ ├── landing-page-halo │ │ │ ├── +page.svelte │ │ │ ├── webgl │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.frag │ │ │ └── webgpu │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.wgsl │ │ │ ├── oversized-canvas │ │ │ ├── +page.svelte │ │ │ ├── webgl │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.frag │ │ │ └── webgpu │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.wgsl │ │ │ ├── remount │ │ │ ├── +page.svelte │ │ │ ├── webgl │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.frag │ │ │ └── webgpu │ │ │ │ ├── +page.svelte │ │ │ │ └── shader.wgsl │ │ │ └── slider │ │ │ ├── +page.svelte │ │ │ ├── webgl │ │ │ ├── +page.svelte │ │ │ └── shader.frag │ │ │ └── webgpu │ │ │ ├── +page.svelte │ │ │ └── shader.wgsl │ ├── static │ │ └── favicon.png │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.js └── tests-svelte5 │ ├── package.json │ ├── src │ ├── app.d.ts │ ├── app.html │ └── routes │ │ ├── +layout.server.js │ │ ├── +page.svelte │ │ ├── hello-world │ │ ├── +page.svelte │ │ ├── webgl │ │ │ ├── +page.svelte │ │ │ └── shader.frag │ │ └── webgpu │ │ │ ├── +page.svelte │ │ │ └── shader.wgsl │ │ ├── landing-page-bubbles │ │ ├── +page.svelte │ │ ├── webgl │ │ │ ├── +page.svelte │ │ │ └── shader.frag │ │ └── webgpu │ │ │ ├── +page.svelte │ │ │ └── shader.wgsl │ │ ├── landing-page-halo │ │ ├── +page.svelte │ │ ├── webgl │ │ │ ├── +page.svelte │ │ │ └── shader.frag │ │ └── webgpu │ │ │ ├── +page.svelte │ │ │ └── shader.wgsl │ │ ├── oversized-canvas │ │ ├── +page.svelte │ │ ├── webgl │ │ │ ├── +page.svelte │ │ │ └── shader.frag │ │ └── webgpu │ │ │ ├── +page.svelte │ │ │ └── shader.wgsl │ │ ├── remount │ │ ├── +page.svelte │ │ ├── webgl │ │ │ ├── +page.svelte │ │ │ └── shader.frag │ │ └── webgpu │ │ │ ├── +page.svelte │ │ │ └── shader.wgsl │ │ └── slider │ │ ├── +page.svelte │ │ ├── webgl │ │ ├── +page.svelte │ │ └── shader.frag │ │ └── webgpu │ │ ├── +page.svelte │ │ └── shader.wgsl │ ├── static │ └── favicon.png │ ├── svelte.config.js │ ├── tsconfig.json │ └── vite.config.js ├── playwright.config.ts ├── resources ├── banner.png ├── bubbles.png ├── collage.png ├── debugShaderWebGl.png ├── debugShaderWebGpu.png ├── halo.png ├── logoDark.png ├── logoLight.png └── slider.png ├── tests ├── .generated-screenshots │ └── test-sites.spec.ts │ │ ├── hello-world-webgl.png │ │ ├── hello-world-webgpu-unsupported.png │ │ ├── hello-world-webgpu.png │ │ ├── landing-page-bubbles-webgl.png │ │ ├── landing-page-bubbles-webgpu-unsupported.png │ │ ├── landing-page-bubbles-webgpu.png │ │ ├── landing-page-halo-webgl.png │ │ ├── landing-page-halo-webgpu-unsupported.png │ │ ├── landing-page-halo-webgpu.png │ │ ├── oversized-canvas-webgl-1.png │ │ ├── oversized-canvas-webgl-2.png │ │ ├── oversized-canvas-webgpu-1.png │ │ ├── oversized-canvas-webgpu-2.png │ │ ├── oversized-canvas-webgpu-unsupported-1.png │ │ ├── oversized-canvas-webgpu-unsupported-2.png │ │ ├── remount-webgl-1.png │ │ ├── remount-webgl-2.png │ │ ├── remount-webgpu-1.png │ │ ├── remount-webgpu-2.png │ │ ├── remount-webgpu-unsupported-1.png │ │ ├── remount-webgpu-unsupported-2.png │ │ ├── slider-webgl-1.png │ │ ├── slider-webgl-2.png │ │ ├── slider-webgl-3.png │ │ ├── slider-webgpu-1.png │ │ ├── slider-webgpu-2.png │ │ ├── slider-webgpu-3.png │ │ ├── slider-webgpu-unsupported-1.png │ │ ├── slider-webgpu-unsupported-2.png │ │ └── slider-webgpu-unsupported-3.png └── test-sites.spec.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [sockmaster27] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | ignore: 12 | - dependency-name: "svelte" 13 | update-types: ["version-update:semver-major"] 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - uses: actions/setup-node@v6 15 | with: 16 | node-version: 20 17 | - name: Install Dependencies 18 | run: npm ci 19 | - name: Check 20 | run: npm run check 21 | - name: Package 22 | run: npm run package 23 | - name: Build Example Sites 24 | run: npm run build 25 | 26 | test: 27 | # GitHub's macOS runners are currently the only ones that properly support WebGL and WebGPU out of the box 28 | runs-on: macos-latest 29 | timeout-minutes: 60 30 | steps: 31 | - uses: actions/checkout@v6 32 | - uses: actions/setup-node@v6 33 | with: 34 | node-version: 20 35 | - name: Install Dependencies 36 | run: npm ci 37 | - name: Install Playwright Browsers 38 | run: npx playwright install --with-deps && npx playwright install chrome 39 | - name: Run Tests 40 | run: npm test 41 | - uses: actions/upload-artifact@v5 42 | if: ${{ !cancelled() }} 43 | with: 44 | name: playwright-report 45 | path: playwright-report/ 46 | retention-days: 30 47 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Release to npm 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-npm: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v6 15 | - uses: actions/setup-node@v6 16 | with: 17 | node-version: 20 18 | registry-url: https://registry.npmjs.org/ 19 | - name: Install Dependencies 20 | run: npm ci 21 | - name: Package 22 | run: npm run package 23 | - name: Publish 24 | working-directory: ./packages/svader 25 | run: npm publish --provenance --access public 26 | env: 27 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | .svelte-kit 7 | build 8 | dist 9 | 10 | # OS 11 | .DS_Store 12 | Thumbs.db 13 | 14 | # Env 15 | .env 16 | .env.* 17 | !.env.example 18 | !.env.test 19 | 20 | # Vite 21 | vite.config.js.timestamp-* 22 | vite.config.ts.timestamp-* 23 | 24 | # Playwright 25 | /test-results/ 26 | /playwright-report/ 27 | /blob-report/ 28 | /playwright/.cache/ 29 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.frag 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-svelte"], 3 | "printWidth": 80, 4 | "tabWidth": 4, 5 | "semi": true, 6 | "singleQuote": false, 7 | "quoteProps": "consistent", 8 | "trailingComma": "all", 9 | "bracketSpacing": true, 10 | "arrowParens": "avoid", 11 | "endOfLine": "auto", 12 | "overrides": [ 13 | { 14 | "files": "*.yml", 15 | "options": { "tabWidth": 2 } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "ms-playwright.playwright" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Svader 2 | 3 | All contributions to Svader are welcome and greatly appreciated! 4 | 5 | This document outlines the process of developing and contributing to the project. 6 | 7 | ## Development 8 | 9 | Start by installing the necessary dependencies: 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | ### Project Structure 16 | 17 | The project is structured in the following way: 18 | 19 | - The top-level package, `"svader-monorepo"`, is a workspace containing the necessary configuration files, scripts, etc. to build and test the project. 20 | - `packages/svader/` contains the Svader library itself, which is the main package that is published to npm. 21 | - `packages/tests-svelte4/` and `packages/tests-svelte5/` contain (what should be) visually identical test sites implemented with Svelte 4 and Svelte 5, respectively. These sites contain a set of routes representing different test cases for the library. 22 | - `tests/` contains the automated tests of the project, plus the `.generated-screenshots/` directory. The contents of this directory are automatically generated by the tests and should, in general, not be modified manually. See the [Testing](#testing) section for more information. 23 | 24 | To run a test site on the development server, use the following commands, respectively: 25 | 26 | Svelte 4: 27 | 28 | ```bash 29 | npm run dev:v4 30 | ``` 31 | 32 | Svelte 5: 33 | 34 | ```bash 35 | npm run dev:v5 36 | ``` 37 | 38 | This will start the development server, allowing you to view the test site at `http://localhost:5173`. 39 | 40 | ### Testing 41 | 42 | To run the tests, run the following command in the root directory of the project: 43 | 44 | ```bash 45 | npm run test 46 | ``` 47 | 48 | > You may need to install Playwright browsers manually before running the tests. 49 | > 50 | > ```bash 51 | > npx playwright install 52 | > ``` 53 | 54 | This will run the automated tests using the [Playwright](https://playwright.dev/) testing framework. 55 | 56 | Playwright works by running the test sites in a set of different browsers on your computer. This means that it might be necessary to install some additional browsers at this point, which Playwright will guide you through if necessary. 57 | 58 | Svader's test suite consists of a set of visual tests that are run against each of the test sites. The tests will take screenshots at the declared points and compare them to a reference image from the `.generated-screenshots/` directory. When a newly added test is run for the first time, the reference images will be created automatically and can be inspected to ensure that the test is working as expected. If a reference image has become outdated, the image can be deleted, and the tests can be re-run to generate a new one. 59 | 60 | ## Style Guide 61 | 62 | To ensure consistent code style, the [Prettier](https://prettier.io/) formatter and [ESLint](https://eslint.org/) linter is used. 63 | When you open a pull request, an automated check will be run to ensure that the code follows the correct style in accordance with the project configuration. 64 | 65 | The same check can be run locally using the following command: 66 | 67 | ```bash 68 | npm run check 69 | ``` 70 | 71 | When using VS Code, the workspace is set up to automatically run this formatting on every save, as long as you have the recommended [Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) installed. 72 | The linting errors will also be displayed inline if the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) is installed. 73 | 74 | ## Licensing 75 | 76 | By contributing your code, you agree to license your contribution under the [MIT License](LICENSE.md). 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Holger Dal Mogensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./packages/svader/README.md 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import { includeIgnoreFile } from "@eslint/compat"; 3 | import svelte from "eslint-plugin-svelte"; 4 | import globals from "globals"; 5 | import { fileURLToPath } from "node:url"; 6 | import ts from "typescript-eslint"; 7 | const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url)); 8 | 9 | export default ts.config( 10 | includeIgnoreFile(gitignorePath), 11 | js.configs.recommended, 12 | ...ts.configs.strict, 13 | ...svelte.configs["flat/recommended"], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node, 19 | }, 20 | }, 21 | }, 22 | { 23 | files: ["**/*.svelte"], 24 | 25 | languageOptions: { 26 | parserOptions: { 27 | parser: ts.parser, 28 | }, 29 | }, 30 | }, 31 | { rules: { eqeqeq: ["error", "always"] } }, 32 | ); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svader-monorepo", 3 | "private": true, 4 | "type": "module", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "package": "cd packages/svader && npm run package", 10 | "dev:logo": "npm run package && cd packages/logo && npm run dev", 11 | "dev:v4": "npm run package && cd packages/tests-svelte4 && npm run dev", 12 | "dev:v5": "npm run package && cd packages/tests-svelte5 && npm run dev", 13 | "preview:logo": "cd packages/logo && npm run preview", 14 | "preview:v4": "cd packages/tests-svelte4 && npm run preview", 15 | "preview:v5": "cd packages/tests-svelte5 && npm run preview", 16 | "check": "npm run package && npm run check:formatting && npm run check:lint && npm run check:svader && npm run check:logo && npm run check:v4 && npm run check:v5", 17 | "check:formatting": "prettier . --check", 18 | "check:lint": "eslint .", 19 | "check:svader": "cd packages/svader && npm run check", 20 | "check:logo": "cd packages/logo && npm run check", 21 | "check:v4": "cd packages/tests-svelte4 && npm run check", 22 | "check:v5": "cd packages/tests-svelte5 && npm run check", 23 | "build": "npm run package && npm run build:logo && npm run build:v4 && npm run build:v5", 24 | "build:logo": "cd packages/logo && npm run build", 25 | "build:v4": "cd packages/tests-svelte4 && npm run build", 26 | "build:v5": "cd packages/tests-svelte5 && npm run build", 27 | "test": "npm run package && playwright test" 28 | }, 29 | "devDependencies": { 30 | "@eslint/compat": "^2.0.0", 31 | "@playwright/test": "^1.57.0", 32 | "@sveltejs/adapter-auto": "^7.0.0", 33 | "@sveltejs/kit": "^2.49.0", 34 | "@sveltejs/package": "^2.5.7", 35 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 36 | "@types/node": "^24.10.1", 37 | "eslint": "^9.39.1", 38 | "eslint-plugin-svelte": "^3.10.1", 39 | "globals": "^16.5.0", 40 | "prettier": "^3.7.1", 41 | "prettier-plugin-svelte": "^3.4.0", 42 | "publint": "^0.3.15", 43 | "svelte-check": "^4.3.4", 44 | "typescript": "^5.9.3", 45 | "typescript-eslint": "^8.48.0", 46 | "vite": "^6.4.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/logo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logo", 3 | "private": true, 4 | "scripts": { 5 | "postinstall": "svelte-kit sync", 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite build && vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 11 | }, 12 | "devDependencies": { 13 | "svelte": "^5.0.0" 14 | }, 15 | "svelte": "./dist/index.js", 16 | "types": "./dist/index.d.ts", 17 | "type": "module" 18 | } 19 | -------------------------------------------------------------------------------- /packages/logo/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /packages/logo/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/logo/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 |
20 |
21 | 38 |
39 | 40 |
41 | S 42 |

SVADER

43 |
44 |
45 | 46 | 90 | -------------------------------------------------------------------------------- /packages/logo/src/routes/logoDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/packages/logo/src/routes/logoDark.png -------------------------------------------------------------------------------- /packages/logo/src/routes/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | uniform float u_scale; 9 | 10 | 11 | const float pi = 3.14159265359; 12 | 13 | 14 | // Pick a pseudo-random number for the given `pos`. 15 | float random(vec2 pos) { 16 | vec2 st = pos / u_resolution; 17 | return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453); 18 | } 19 | 20 | 21 | // Create a 2-dimensional vector with a length of 1 and a pseudo-random angle. 22 | vec2 random_gradient(vec2 pos) { 23 | float angle = random(pos) * pi * 2.0; 24 | return vec2(cos(angle), sin(angle)); 25 | } 26 | 27 | 28 | // Interpolate the value of `x` between 0 and 1 by using a cubic function. 29 | // When drawn on a graph, this creates an S-shaped curve between 0 and 1. 30 | float cubic_s(float x) { 31 | return x * x * (3.0 - x * 2.0); 32 | } 33 | 34 | 35 | // Create Perlin noise: https://en.wikipedia.org/wiki/Perlin_noise 36 | float perlin_noise(vec2 pos) { 37 | float cell_size = 100.0 * u_scale; 38 | 39 | vec2 grid_st = fract(pos / cell_size); 40 | vec2 grid_square_coord = floor(pos / cell_size); 41 | 42 | vec2 corner_bot_left = vec2(0.0, 0.0); 43 | vec2 corner_bot_right = vec2(1.0, 0.0); 44 | vec2 corner_top_left = vec2(0.0, 1.0); 45 | vec2 corner_top_right = vec2(1.0, 1.0); 46 | 47 | vec2 offset_bot_left = grid_st - corner_bot_left; 48 | vec2 offset_bot_right = grid_st - corner_bot_right; 49 | vec2 offset_top_left = grid_st - corner_top_left; 50 | vec2 offset_top_right = grid_st - corner_top_right; 51 | 52 | vec2 gradient_bot_left = random_gradient((grid_square_coord + corner_bot_left) * cell_size); 53 | vec2 gradient_bot_right = random_gradient((grid_square_coord + corner_bot_right) * cell_size); 54 | vec2 gradient_top_left = random_gradient((grid_square_coord + corner_top_left) * cell_size); 55 | vec2 gradient_top_right = random_gradient((grid_square_coord + corner_top_right) * cell_size); 56 | 57 | float dot_bot_left = dot(offset_bot_left, gradient_bot_left); 58 | float dot_bot_right = dot(offset_bot_right, gradient_bot_right); 59 | float dot_top_left = dot(offset_top_left, gradient_top_left); 60 | float dot_top_right = dot(offset_top_right, gradient_top_right); 61 | 62 | float x = cubic_s(grid_st.x); 63 | float y = cubic_s(grid_st.y); 64 | float bot = mix(dot_bot_left, dot_bot_right, x); 65 | float top = mix(dot_top_left, dot_top_right, x); 66 | return mix(bot, top, y); 67 | } 68 | 69 | 70 | void main() { 71 | vec2 pos = gl_FragCoord.xy + u_offset; 72 | 73 | // Mirror the y-axis to create the exact same image as the WebGPU version. 74 | pos.y = u_resolution.y - pos.y; 75 | 76 | // Multiply the noise with the vignette to create a darker spot towards the middle. 77 | float noise = perlin_noise(pos); 78 | float vignette = cubic_s(distance(pos / u_resolution, vec2(0.5, 0.5))); 79 | float r = step(0.4 - random(pos) * 0.4, vignette * noise) * 0.8; 80 | 81 | fragColor = vec4(vec3(r), 1.0); 82 | } 83 | -------------------------------------------------------------------------------- /packages/logo/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/packages/logo/static/favicon.png -------------------------------------------------------------------------------- /packages/logo/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-auto"; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | adapter: adapter(), 7 | }, 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /packages/logo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/logo/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/svader/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Holger Dal Mogensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/svader/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | Svader Logo 5 | 6 |
7 | MIT License 8 | CI Status 9 |
10 | NPM Version 11 |

12 | 13 | # Svader 14 | 15 | Create GPU-rendered Svelte components with WebGL and WebGPU fragment shaders. 16 | 17 | Supports Svelte 4 and Svelte 5. 18 | 19 | ## What is a fragment shader? 20 | 21 | In short, a _fragment shader_ can be written as a program that takes the coordinates of a pixel on the screen and returns the color that this pixel should have. 22 | This program can be executed on the GPU, ensuring massive parallelism and speed. 23 | 24 | To learn more about how to write fragment shaders, check out [The Book of Shaders](https://thebookofshaders.com/). 25 | 26 | The following is a collection of examples all made using Svader. The live version of all of these can be previewed on [svader.vercel.app](https://svader.vercel.app/), 27 | and the source code can be found in the [`src/routes/`](https://github.com/sockmaster27/svader/tree/master/packages/tests-svelte5/src/routes) directory. 28 | 29 | ![Shader example collage](https://raw.githubusercontent.com/sockmaster27/svader/master/resources/collage.png) 30 | 31 | ## Installation 32 | 33 | ```bash 34 | # npm 35 | npm i -D svader 36 | 37 | # pnpm 38 | pnpm i -D svader 39 | 40 | # Bun 41 | bun i -D svader 42 | 43 | # Yarn 44 | yarn add -D svader 45 | ``` 46 | 47 | ## Usage 48 | 49 | To use a fragment shader component, you first need to decide whether to use WebGL or WebGPU. 50 | If you're unsure about what to use, see the [WebGL vs. WebGPU](#webgl-vs-webgpu) section. 51 | 52 | ### Sections 53 | 54 | - [WebGL](#webgl) 55 | - [WebGL parameters](#webgl-parameters) 56 | - [WebGL built-in values](#webgl-built-in-values) 57 | - [WebGPU](#webgpu) 58 | - [WebGPU parameters](#webgpu-parameters) 59 | - [WebGPU built-in values](#webgpu-built-in-values) 60 | 61 | ### WebGL 62 | 63 | The following is a minimal example of a WebGL fragment shader component. 64 | 65 | [**View in playground**](https://svelte.dev/playground/3e4a38bca5ca49fa94e1106a841063d5?version=5.16.2) 66 | 67 | ```svelte 68 | 87 | 88 | 97 |
WebGL not supported in this environment.
98 |
99 | ``` 100 | 101 | This produces the following output: 102 | 103 | ![Output of the WebGL shader](https://raw.githubusercontent.com/sockmaster27/svader/master/resources/debugShaderWebGl.png) 104 | 105 | Here, the `shaderCode` variable is a string containing the [GLES](https://en.wikipedia.org/wiki/OpenGL_ES) shader code. 106 | For simplicity, this is stored as a string, but it would typically be stored in a separate `myShader.frag` file. 107 | When loading the shader from a file, it might be useful to know that the `code` property accepts both a `string` and a `Promise`. 108 | 109 | What this code does is: 110 | 111 | 1. Add the given `u_offset` uniform to the 2D coordinates of the pixel given by `gl_FragCoord.xy`. 112 | 2. Divide the resulting coordinates entrywise by the `u_resolution` uniform to normalize the coordinates between 0 and 1. 113 | 3. Return the normalized coordinates as the color of the pixel, such that the `x` coordinate becomes the red channel and the `y` coordinate becomes the green channel. The blue channel is always set to 0, and the alpha (opacity) channel is always set to 1 (fully opaque). 114 | 115 | In GLES, _uniforms_ are inputs to the shader program, that are the same for every pixel on the screen. 116 | These need to be passed in via the `parameters` property of the `` component. 117 | In this case, we need to pass in two uniforms: `u_resolution` and `u_offset`. 118 | Since these specific parameters are very commonly used, they are specially implemented in Svader 119 | such that the `value` property of each parameter can simply be set to `"resolution"` and `"offset"` respectively. 120 | 121 | Lastly, the `` component accepts a fallback slot, which is rendered when the browser cannot render the shader. 122 | 123 | #### WebGL parameters 124 | 125 | The `parameters` property is an array of objects with the following properties: 126 | 127 | - **`name`**: The name of the uniform parameter, e.g. `"my_uniform"`. 128 | This must match the name of the parameter in the shader code. 129 | 130 | - **`type`**: The type of the uniform parameter as it is written in the shader code, e.g. `"float"`. 131 | If the `value` property is a [built-in value](#webgl-built-in-values), such as `"resolution"`, 132 | the `type` will be determined automatically and should not be set. 133 | 134 | - **`value`**: The value of the uniform parameter, or a string specifying a [built-in value](#webgl-built-in-values). 135 | If not a built-in value, the type of this property must correspond to the `type` property, such that: 136 | - **`float`, `int`, `uint`** is a `number`, 137 | - **`vecN`, `ivecN`, `uvecN`** is a `number[]` with a length of `N`, e.g. `vec2` -> `[1.2, 3.4]`. 138 | - **`matN`** is a `number[]` with a length of `N * N`, e.g. `mat2` -> `[1, 2, 3, 4]`. 139 | 140 | ##### WebGL built-in values 141 | 142 | Some types of uniforms are used very often. These are implemented in Svader itself, and referred to as _built-in values_. 143 | To use these, the `value` property of the parameter object must be set to a string matching one of the following: 144 | 145 | - **`"resolution"`**: A `vec2` of the canvas width and height in physical device pixels. 146 | 147 | - **`"scale"`**: A `float` of the ratio between CSS pixels and physical device pixels, i.e. zoom level. 148 | For example, if the browser has been zoomed to 150%, the `scale` parameter will be `1.5`. 149 | 150 | - **`"time"`**: A `float` of the current time in seconds. 151 | NOTE: Passing this parameter to the shader will cause it to rerender every frame. 152 | 153 | - **`"offset"`**: A `vec2` to be added to the `gl_FragCoord.xy` of the fragment shader. 154 | Sometimes the size of the canvas is limited by hardware. 155 | To compensate for this, Svader creates a virtual canvas with a smaller cutout shifting around to cover the screen. 156 | The `"resolution"` parameter is automatically adjusted to match the size of this virtual canvas, but for technical reasons, 157 | the `gl_FragCoord.xy` cannot be adjusted from the outside. 158 | Therefore, the `"offset"` parameter is provided to be manually added to these coordinates. 159 | 160 | ### WebGPU 161 | 162 | The following is a minimal example of a WebGPU fragment shader component. 163 | 164 | [**View in playground**](https://svelte.dev/playground/498446d091964bb199e6a88bce90feae?version=5.16.3) 165 | 166 | ```svelte 167 | 182 | 183 | 192 |
WebGPU not supported in this environment.
193 |
194 | ``` 195 | 196 | This produces the following output: 197 | 198 | ![Output of the WebGPU shader](https://raw.githubusercontent.com/sockmaster27/svader/master/resources/debugShaderWebGpu.png) 199 | 200 | Here, the `shaderCode` variable is a string containing the [WGSL](https://google.github.io/tour-of-wgsl/) shader code. 201 | For simplicity, this is stored as a string, but it would typically be stored in a separate `myShader.wgsl` file. 202 | When loading the shader from a file, it might be useful to know that the `code` property accepts both a `string` and a `Promise`. 203 | 204 | What this code does is: 205 | 206 | 1. Add the given `offset` uniform variable to the 2D coordinates of the pixel given by `raw_pos.xy`. 207 | 2. Divide the resulting coordinates entrywise by the `resolution` uniform to normalize the coordinates between 0 and 1. 208 | 3. Return the normalized coordinates as the color of the pixel, such that the `x` coordinate becomes the red channel and the `y` coordinate becomes the green channel. The blue channel is always set to 0, and the alpha (opacity) channel is always set to 1 (fully opaque). 209 | 210 | In WGSL, these `var`s are the primary way to pass in parameters to the shader. 211 | These need to be passed in via the `parameters` property of the `` component. 212 | In this case, we need to pass in two uniforms: `resolution` and `offset`. 213 | Since these specific parameters are very commonly used, they are specially implemented in Svader 214 | such that the `value` property of each parameter can simply be set to `"resolution"` and `"offset"` respectively. 215 | 216 | Lastly, the `` component accepts a fallback slot, which is rendered when the browser cannot render the shader. 217 | 218 | #### WebGPU parameters 219 | 220 | The `parameters` property is an array of objects with the following properties: 221 | 222 | - **`label`**: The name of the parameter to be used for debugging. 223 | This does not have to correspond to the name of the parameter in the shader code. 224 | 225 | - **`binding`**: An integer used to match the parameter to the variable in the shader code. 226 | This has to match the `binding` property of the parameter in the shader code, e.g. for the variable declaration 227 | 228 | ```WGSL 229 | @group(0) @binding(42) var my_variable: f32; 230 | ``` 231 | 232 | the `binding` property should be `42`. 233 | 234 | - **`value`**: The value of the parameter, or a string specifying a [built-in value](#webgpu-built-in-values). 235 | If not a built-in value, this parameter should be an `ArrayBuffer`/`ArrayBufferView`. 236 | For example, to pass in a number to an `f32` parameter, it can be constructed like `new Float32Array([myNumberValue])`. 237 | 238 | - **`storage`**: [Optional - defaults to `false`] Whether the parameter is a storage variable rather than a uniform variable. 239 | This has to match the declaration in the shader code, e.g. for the variable declaration 240 | 241 | ```WGSL 242 | @group(0) @binding(0) var my_variable: f32; 243 | ``` 244 | 245 | the `storage` property should be `false` or omitted, and for 246 | 247 | ```WGSL 248 | @group(0) @binding(0) var my_variable: f32; 249 | ``` 250 | 251 | it should be `true`. 252 | Note that Svader currently only supports `var` and not `var`. 253 | 254 | ##### WebGPU built-in values 255 | 256 | Some types of inputs are used very often. These are implemented in Svader itself, and referred to as _built-in values_. 257 | To use these, the `value` property of the parameter object must be set to a string matching one of the following: 258 | 259 | - **`"resolution"`**: A `vec2f` of the canvas width and height in physical device pixels. 260 | 261 | - **`"scale"`**: An `f32` of the ratio between CSS pixels and physical device pixels, i.e. zoom level. 262 | For example, if the browser has been zoomed to 150%, the `scale` parameter will be `1.5`. 263 | 264 | - **`"time"`**: An `f32` of the current time in seconds. 265 | NOTE: Passing this parameter to the shader will cause it to rerender every frame. 266 | 267 | - **`"offset"`**: A `vec2f` to be added to the `@builtin(position)` of the fragment shader. 268 | Sometimes the size of the canvas is limited by hardware. 269 | To compensate for this, Svader creates a virtual canvas with a smaller cutout shifting around to cover the screen. 270 | The `"resolution"` parameter is automatically adjusted to match the size of this virtual canvas, but for technical reasons, 271 | the `@builtin(position)` cannot be adjusted from the outside. 272 | Therefore, the `"offset"` parameter is provided to be manually added to these coordinates. 273 | 274 | ## WebGL vs. WebGPU 275 | 276 | **For practical applications, default to using WebGL.** 277 | 278 | WebGL and WebGPU are both rendering APIs that allow web applications to render GPU-accelerated graphics. 279 | 280 | WebGL is the older of the two and is supported by [all modern browsers](https://caniuse.com/webgl). 281 | 282 | WebGPU is still in the experimental stage and is only supported in a [few browsers](https://caniuse.com/webgpu). 283 | However, it supports certain features that WebGL does not. For example, as of writing, WebGL in Google Chrome only supports having 8 canvases active in the document at once, while WebGPU supports a practically unlimited number. 284 | 285 | ## License 286 | 287 | Svader is licensed under the [MIT License](https://github.com/sockmaster27/svader/blob/master/LICENSE.md). 288 | -------------------------------------------------------------------------------- /packages/svader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svader", 3 | "version": "0.5.5", 4 | "description": "Create GPU-rendered Svelte components", 5 | "author": "Holger Dal Mogensen", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/sockmaster27/svader.git" 10 | }, 11 | "keywords": [ 12 | "svader", 13 | "svelte", 14 | "svelte4", 15 | "svelte5", 16 | "svelte-components", 17 | "webgpu", 18 | "webgl", 19 | "gpu", 20 | "accelerated", 21 | "graphics", 22 | "fragment", 23 | "shader", 24 | "shaders" 25 | ], 26 | "scripts": { 27 | "package": "svelte-kit sync && svelte-package && publint", 28 | "prepublishOnly": "npm run package", 29 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 30 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 31 | }, 32 | "exports": { 33 | ".": { 34 | "types": "./dist/index.d.ts", 35 | "svelte": "./dist/index.js" 36 | } 37 | }, 38 | "files": [ 39 | "dist", 40 | "!dist/**/*.test.*", 41 | "!dist/**/*.spec.*" 42 | ], 43 | "peerDependencies": { 44 | "svelte": "^5.0.0||^4.0.0" 45 | }, 46 | "devDependencies": { 47 | "@webgpu/types": "^0.1.66", 48 | "svelte": "^5.0.0" 49 | }, 50 | "svelte": "./dist/index.js", 51 | "types": "./dist/index.d.ts", 52 | "type": "module" 53 | } 54 | -------------------------------------------------------------------------------- /packages/svader/src/lib/BaseShader.svelte: -------------------------------------------------------------------------------- 1 | 185 | 186 | {#if canRender} 187 |
196 | 207 | 208 | 209 |
210 | {:else} 211 | 212 | {/if} 213 | 214 | 241 | -------------------------------------------------------------------------------- /packages/svader/src/lib/WebGlShader.svelte: -------------------------------------------------------------------------------- 1 | 77 | 78 | 445 | 446 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /packages/svader/src/lib/WebGpuShader.svelte: -------------------------------------------------------------------------------- 1 | 105 | 106 | 509 | 510 | 525 | 526 | 527 | -------------------------------------------------------------------------------- /packages/svader/src/lib/devicePixelResizeObserver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @template T 3 | * @template {Record} A 4 | * @typedef {import("svelte/action").ActionReturn} ActionReturn 5 | */ 6 | 7 | /** 8 | * @typedef {CustomEvent<{ width: number, height: number }>} DevicePixelResizeEvent 9 | * @typedef {{ "on:devicepixelresize"?: (e: DevicePixelResizeEvent) => void }} Attributes 10 | */ 11 | 12 | /** 13 | * Check if "devicePixelContentBoxSize" is supported (it's not in Safari). 14 | * 15 | * @returns {Promise} 16 | */ 17 | async function checkDevicePixelContentBox() { 18 | return new Promise(resolve => { 19 | const observer = new ResizeObserver(entries => { 20 | resolve( 21 | entries.every(entry => "devicePixelContentBoxSize" in entry), 22 | ); 23 | observer.disconnect(); 24 | }); 25 | observer.observe(document.body, { box: "device-pixel-content-box" }); 26 | }).catch(() => false); 27 | } 28 | const hasDevicePixelContentBoxPromise = checkDevicePixelContentBox(); 29 | let hasDevicePixelContentBox = false; 30 | 31 | /** 32 | * Observe all changes to the device-pixel-content-box of a node. 33 | * 34 | * @param {Element} node 35 | * @returns {ActionReturn} 36 | */ 37 | export function devicePixelResizeObserver(node) { 38 | let observer = new ResizeObserver(callback); 39 | 40 | /** @param {ResizeObserverEntry[]} entries */ 41 | function callback(entries) { 42 | const entry = entries[0]; 43 | 44 | let detail; 45 | if (hasDevicePixelContentBox) { 46 | detail = { 47 | width: entry.devicePixelContentBoxSize[0].inlineSize, 48 | height: entry.devicePixelContentBoxSize[0].blockSize, 49 | }; 50 | } else if (entry.contentBoxSize) { 51 | // Not perfect, but it's the best we can do in this case. 52 | detail = { 53 | width: 54 | entry.contentBoxSize[0].inlineSize * 55 | window.devicePixelRatio, 56 | height: 57 | entry.contentBoxSize[0].blockSize * window.devicePixelRatio, 58 | }; 59 | } else { 60 | // Fallback for older browsers without contentBoxSize support 61 | detail = { 62 | width: entry.contentRect.width * window.devicePixelRatio, 63 | height: entry.contentRect.height * window.devicePixelRatio, 64 | }; 65 | } 66 | 67 | node.dispatchEvent(new CustomEvent("devicepixelresize", { detail })); 68 | } 69 | 70 | hasDevicePixelContentBoxPromise.then(r => { 71 | hasDevicePixelContentBox = r; 72 | observer.observe(node, { 73 | box: hasDevicePixelContentBox 74 | ? "device-pixel-content-box" 75 | : "content-box", 76 | }); 77 | }); 78 | 79 | return { 80 | destroy() { 81 | observer.disconnect(); 82 | }, 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /packages/svader/src/lib/index.js: -------------------------------------------------------------------------------- 1 | export { default as WebGpuShader } from "./WebGpuShader.svelte"; 2 | export { default as WebGlShader } from "./WebGlShader.svelte"; 3 | -------------------------------------------------------------------------------- /packages/svader/src/lib/intersectionObserver.js: -------------------------------------------------------------------------------- 1 | import { clamp } from "./utils.js"; 2 | /** 3 | * @template T 4 | * @template {Record} A 5 | * @typedef {import("svelte/action").ActionReturn} ActionReturn 6 | */ 7 | 8 | /** 9 | * @typedef {CustomEvent} IntersectionEvent 10 | * @typedef {{ "on:intersectionchanged"?: (e: IntersectionEvent) => void }} Attributes 11 | */ 12 | 13 | /** 14 | * Observe all changes to the intersection of the given element with the target element, (by default the viewport). 15 | * 16 | * @param {Element} node 17 | * @param {{ root?: Element | Document | null, rootMargin?: string }} [params] 18 | * @returns {ActionReturn} 19 | */ 20 | export function intersectionObserver(node, params) { 21 | let previousThreshold = 0; 22 | /** @type {IntersectionObserverInit} */ 23 | const observerParams = { 24 | ...params, 25 | threshold: previousThreshold, 26 | }; 27 | 28 | let observer = new IntersectionObserver(callback, observerParams); 29 | observer.observe(node); 30 | 31 | /** @param {IntersectionObserverEntry[]} entries */ 32 | function callback(entries) { 33 | const entry = entries[0]; 34 | 35 | if (entry.intersectionRatio === previousThreshold) return; 36 | 37 | node.dispatchEvent( 38 | new CustomEvent("intersectionchanged", { detail: entry }), 39 | ); 40 | 41 | previousThreshold = entry.intersectionRatio; 42 | observer.disconnect(); 43 | 44 | // Epsilon is necessary to ensure that the callback is triggered even with weird scaling. 45 | const epsilon = 0.00001; 46 | const threshold = [ 47 | previousThreshold - epsilon, 48 | previousThreshold, 49 | previousThreshold + epsilon, 50 | ].map(t => clamp(0, t, 1)); 51 | 52 | const observerParams = { 53 | ...params, 54 | threshold, 55 | }; 56 | observer = new IntersectionObserver(callback, observerParams); 57 | observer.observe(node); 58 | } 59 | 60 | return { 61 | destroy() { 62 | observer.disconnect(); 63 | }, 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /packages/svader/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { readable } from "svelte/store"; 2 | 3 | /** Ratio between CSS pixels and actual physical pixels */ 4 | export const pixelScale = 5 | typeof window !== "undefined" 6 | ? readable(window.devicePixelRatio, set => { 7 | let removePrevious = () => {}; 8 | 9 | function updatePixelRatio() { 10 | removePrevious(); 11 | const queryString = `(resolution: ${window.devicePixelRatio}dppx)`; 12 | const media = matchMedia(queryString); 13 | media.addEventListener("change", updatePixelRatio); 14 | removePrevious = () => 15 | media.removeEventListener("change", updatePixelRatio); 16 | 17 | set(window.devicePixelRatio); 18 | } 19 | 20 | updatePixelRatio(); 21 | }) 22 | : readable(1); 23 | 24 | /** 25 | * Zip two arrays together, returning an array of pairs. 26 | * 27 | * ```ts 28 | * zip([1, 2, 3], ["a", "b", "c"]) // [[1, "a"], [2, "b"], [3, "c"]] 29 | * ``` 30 | * 31 | * @template A 32 | * @template B 33 | * @param {readonly A[]} a 34 | * @param {readonly B[]} b 35 | * @returns {[A, B][]} 36 | */ 37 | export function zip(a, b) { 38 | if (a.length !== b.length) 39 | throw new Error( 40 | `Arrays must be of equal length: a.length = ${a.length}, b.length = ${b.length}`, 41 | ); 42 | 43 | return a.map((e, i) => [e, b[i]]); 44 | } 45 | 46 | /** 47 | * Clamp a value between a minimum and maximum. 48 | * 49 | * ```ts 50 | * clamp(0, 5, 10) === 5 51 | * clamp(0, -5, 10) === 0 52 | * clamp(0, 15, 10) === 10 53 | * ``` 54 | * 55 | * @param {number} min 56 | * @param {number} value 57 | * @param {number} max 58 | * @returns {number} 59 | */ 60 | export function clamp(min, value, max) { 61 | return Math.min(Math.max(value, min), max); 62 | } 63 | -------------------------------------------------------------------------------- /packages/svader/svelte.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@sveltejs/kit').Config} */ 2 | const config = {}; 3 | 4 | export default config; 5 | -------------------------------------------------------------------------------- /packages/svader/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["@webgpu/types"], 5 | "allowJs": true, 6 | "checkJs": true, 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "sourceMap": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/tests-svelte4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests-svelte4", 3 | "private": true, 4 | "scripts": { 5 | "postinstall": "svelte-kit sync", 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite build && vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 11 | }, 12 | "devDependencies": { 13 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 14 | "svelte": "^4.0.0" 15 | }, 16 | "svelte": "./dist/index.js", 17 | "types": "./dist/index.d.ts", 18 | "type": "module" 19 | } 20 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/+layout.server.js: -------------------------------------------------------------------------------- 1 | export const trailingSlash = "always"; 2 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 |
    2 |
  1. Hello world
  2. 3 |
  3. Remounting canvas
  4. 4 |
  5. Oversized canvas
  6. 5 |
  7. Logo
  8. 6 |
  9. Landing page with bubbles
  10. 7 |
  11. Landing page with a halo
  12. 8 |
  13. Slider component
  14. 9 |
10 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/hello-world/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/hello-world/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 |
WebGL not supported in this environment.
22 |
23 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/hello-world/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | void main() { 10 | vec2 pos = gl_FragCoord.xy + u_offset; 11 | vec2 st = pos / u_resolution; 12 | fragColor = vec4(st, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/hello-world/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 |
WebGPU not supported in this environment.
24 |
25 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/hello-world/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | 4 | @fragment 5 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 6 | let pos = raw_pos.xy + offset; 7 | let st = pos / resolution; 8 | return vec4f(st, 0.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-bubbles/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-bubbles/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 20 | 21 | 25 | 26 | 27 |
28 |
29 | 55 |
56 | 57 |

SVADER

58 |
59 | 60 | 99 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-bubbles/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | uniform float u_scale; 9 | uniform float u_time; 10 | uniform vec3 u_color; 11 | 12 | 13 | // Determine the value of the pixel at `pos`, if we want to draw a circle at `center` with a the given `radius`. 14 | float circle(vec2 pos, vec2 center, float radius) { 15 | return 1.0 - smoothstep(radius - 1.5, radius, distance(pos, center)); 16 | } 17 | 18 | 19 | // Determine the value of the pixel at `pos`, if we want to draw a 'bubble' (wobbly circle) at `center` with a the given `radius`. 20 | // The `random_seed` is used to make all bubbles wobble a bit differently. 21 | float bubble(vec2 pos, vec2 center, float radius, float random_seed) { 22 | const float pi = 3.1415926535897932384626433832795; 23 | 24 | // Determine the angle between `pos` and `center`. 25 | float angle = atan(pos.y - center.y, pos.x - center.x) / pi; 26 | 27 | // Create wobble by layering multiple sine waves. 28 | // One with a frequency of 1, one with a frequency of 2, etc. up to 6. 29 | float wobble = 0.0; 30 | for (float i = 1.0; i <= 6.0; i++) { 31 | float random_offset = sin(random_seed) * pi * pow(i, 2.0); 32 | float speed = pow(i, 2.0) * 0.05; 33 | float amplitude = 0.05 / i; 34 | wobble += sin(angle * i * pi + u_time * speed + random_offset) * amplitude; 35 | } 36 | 37 | float wobbly_radius = radius * (wobble + 1.0); 38 | return circle(pos, center, wobbly_radius); 39 | } 40 | 41 | 42 | void main() { 43 | vec2 pos = gl_FragCoord.xy + u_offset; 44 | 45 | // Mirror the y-axis so that the origin is in the top-left corner. 46 | pos.y = u_resolution.y - pos.y; 47 | 48 | // Break the screen into square tiles with a width of 300 CSS pixels each. 49 | float tile_width = 300.0 * u_scale; 50 | vec2 tile_center = vec2(tile_width / 2.0); 51 | vec2 tile_coords = mod(pos + tile_center, tile_width); 52 | 53 | // Determine which tile we're in and use that as a random seed. 54 | vec2 which_tile = floor((pos + tile_center) / tile_width); 55 | float tile_index = which_tile.x + which_tile.y * 10.0; // Assume at most 10 tiles in width 56 | float random_seed = tile_index; 57 | 58 | float bubble_gap = 20.0 * u_scale; 59 | float radius = tile_width / 2.0 - bubble_gap; 60 | 61 | float v = bubble(tile_coords, tile_center, radius, random_seed); 62 | 63 | fragColor = vec4(u_color * v, v); 64 | } 65 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-bubbles/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 20 | 21 | 25 | 26 | 27 |
28 |
29 | 54 |
55 | 56 |

SVADER

57 |
58 | 59 | 98 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-bubbles/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var offset: vec2f; 2 | @group(0) @binding(1) var scale: f32; 3 | @group(0) @binding(2) var time: f32; 4 | @group(0) @binding(3) var color: vec3f; 5 | 6 | 7 | // Determine the value of the pixel at `pos`, if we want to draw a circle at `center` with a the given `radius`. 8 | fn circle(pos: vec2f, center: vec2f, radius: f32) -> f32 { 9 | return 1.0 - smoothstep(radius - 1.5, radius, distance(pos, center)); 10 | } 11 | 12 | 13 | // Determine the value of the pixel at `pos`, if we want to draw a 'bubble' (wobbly circle) at `center` with a the given `radius`. 14 | // The `random_seed` is used to make all bubbles wobble a bit differently. 15 | fn bubble(pos: vec2f, center: vec2f, radius: f32, random_seed: f32) -> f32 { 16 | let pi = 3.1415926535897932384626433832795; 17 | 18 | // Determine the angle between `pos` and `center`. 19 | let angle = atan2(pos.y - center.y, pos.x - center.x) / pi; 20 | 21 | // Create wobble by layering multiple sine waves. 22 | // One with a frequency of 1, one with a frequency of 2, etc. up to 6. 23 | var wobble = 0.0; 24 | for (var i = 1.0; i <= 6.0; i += 1.0) { 25 | let random_offset = sin(random_seed) * pi * pow(i, 2.0); 26 | let speed = pow(i, 2.0) * 0.05; 27 | let amplitude = 0.05 / i; 28 | wobble += sin(angle * i * pi + time * speed + random_offset) * amplitude; 29 | } 30 | 31 | let wobbly_radius = radius * (wobble + 1.0); 32 | return circle(pos, center, wobbly_radius); 33 | } 34 | 35 | 36 | @fragment 37 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 38 | let pos = raw_pos.xy + offset; 39 | 40 | // Break the screen into square tiles with a width of 300 CSS pixels each. 41 | let tile_width = 300.0 * scale; 42 | let tile_center = vec2f(tile_width / 2.0); 43 | let tile_coords = (pos + tile_center) % tile_width; 44 | 45 | // Determine which tile we're in and use that as a random seed. 46 | let which_tile = floor((pos + tile_center) / tile_width); 47 | let tile_index = which_tile.x + which_tile.y * 10.0; // Assume at most 10 tiles in width 48 | let random_seed = tile_index; 49 | 50 | let bubble_gap = 20.0 * scale; 51 | let radius = tile_width / 2.0 - bubble_gap; 52 | 53 | let v = bubble(tile_coords, tile_center, radius, random_seed); 54 | 55 | return vec4f(color * v, v); 56 | } 57 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-halo/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-halo/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 |
17 |
18 | 31 |
32 | 33 |

SVADER

34 |
35 | 36 | 74 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-halo/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | 10 | // Pick a pseudo-random number for the given `pos`. 11 | float random(vec2 pos) { 12 | vec2 st = pos / u_resolution; 13 | return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123); 14 | } 15 | 16 | 17 | // Determine the value of the pixel at `pos`, if we want to draw a halo at `center` with a the given `radius`. 18 | // The `side_length` represents the smaller dimension of the canvas, and is used to scale all values accordingly. 19 | float halo(vec2 pos, vec2 center, float radius, float side_length) { 20 | float halo_solid_feather = 0.02 * side_length; 21 | float halo_thickness = 0.017 * side_length; 22 | float halo_glow_spread = 0.3 * side_length; 23 | 24 | // Create a distance field from the center. 25 | float center_d = distance(pos, center); 26 | // Create a distence field for the halo. 27 | float halo_d = abs(center_d - radius); 28 | // Draw the 'solid' part of the halo. 29 | // By multiplying by 5.0 we generate a value greater than 1.0, 30 | // which will make the color saturate into a whiter nuance and give the halo sharper edges. 31 | float halo_solid = smoothstep(halo_d, halo_d + halo_solid_feather, halo_thickness) * 5.0; 32 | // Draw the glow around the halo. 33 | float halo_glow = pow(1.0 - clamp(halo_d / halo_glow_spread, 0.0, 1.0), 5.0) * 0.4 * (0.85 + random(pos) * 0.3); 34 | 35 | return halo_solid + halo_glow; 36 | } 37 | 38 | 39 | void main() { 40 | vec2 pos = gl_FragCoord.xy + u_offset; 41 | 42 | vec2 screen_center = u_resolution / 2.0; 43 | 44 | // This shader automatically scales to fit the smallest dimension of the canvas. 45 | float side_length = min(u_resolution.x, u_resolution.y); 46 | 47 | // Draw 5 halos to create a sense of depth. 48 | float v = 0.0; 49 | for (float i = 0.0; i < 5.0; i++) { 50 | // As the halos get further back they: 51 | // - Are placed slightly higher up 52 | vec2 center = screen_center + vec2(0.0, i * 0.005 * side_length); 53 | // - Get smaller 54 | float radius = (side_length * 0.4) / (i * 0.15 + 1.0); 55 | // - Gradually fade out 56 | float fade_factor = pow(0.3, i); 57 | 58 | v += halo(pos, center, radius, side_length) * fade_factor; 59 | } 60 | 61 | // Give the halos a yellowish color. 62 | float gradient_length = 0.5 * side_length; 63 | float gradient_value = clamp((-pos.y + (u_resolution.y + gradient_length) * 0.5) / gradient_length, 0.0, 1.0); 64 | vec4 warm_color = vec4(1.0, 0.69, 0.37, 1.0); 65 | vec4 cool_color = vec4(0.67, 0.82, 1.0, 1.0); 66 | vec4 color = mix(warm_color, cool_color, gradient_value); 67 | 68 | fragColor = color * v; 69 | } 70 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-halo/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 |
17 |
18 | 33 |
34 | 35 |

SVADER

36 |
37 | 38 | 76 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/landing-page-halo/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | @group(0) @binding(2) var scale: f32; 4 | 5 | 6 | // Pick a pseudo-random number for the given `pos`. 7 | fn random(pos: vec2f) -> f32 { 8 | let st = pos / resolution; 9 | return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123); 10 | } 11 | 12 | 13 | // Determine the value of the pixel at `pos`, if we want to draw a halo at `center` with a the given `radius`. 14 | // The `side_length` represents the smaller dimension of the canvas, and is used to scale all values accordingly. 15 | fn halo(pos: vec2f, center: vec2f, radius: f32, side_length: f32) -> f32 { 16 | let halo_solid_feather = 0.02 * side_length; 17 | let halo_thickness = 0.017 * side_length; 18 | let halo_glow_spread = 0.3 * side_length; 19 | 20 | // Create a distance field from the center. 21 | let center_d = distance(pos, center); 22 | // Create a distence field for the halo. 23 | let halo_d = abs(center_d - radius); 24 | // Draw the 'solid' part of the halo. 25 | // By multiplying by 5.0 we generate a value greater than 1.0, 26 | // which will make the color saturate into a whiter nuance and give the halo sharper edges. 27 | let halo_solid = smoothstep(halo_d, halo_d + halo_solid_feather, halo_thickness) * 5.0; 28 | // Draw the glow around the halo. 29 | let halo_glow = pow(1.0 - clamp(halo_d / halo_glow_spread, 0.0, 1.0), 5.0) * 0.4 * (0.85 + random(pos) * 0.3); 30 | 31 | return halo_solid + halo_glow; 32 | } 33 | 34 | 35 | @fragment 36 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 37 | let pos = raw_pos.xy + offset; 38 | 39 | let screen_center = resolution / 2.0; 40 | 41 | // This shader automatically scales to fit the smallest dimension of the canvas. 42 | let side_length = min(resolution.x, resolution.y); 43 | 44 | // Draw 5 halos to create a sense of depth. 45 | var v = 0.0; 46 | for (var i = 0.0; i < 5.0; i += 1.0) { 47 | // As the halos get further back they: 48 | // - Are placed slightly higher up 49 | let center = screen_center - vec2(0.0, i * 0.005 * side_length); 50 | // - Get smaller 51 | let radius = (side_length * 0.4) / (i * 0.15 + 1.0); 52 | // - Gradually fade out 53 | let fade_factor = pow(0.3, i); 54 | 55 | v += halo(pos, center, radius, side_length) * fade_factor; 56 | } 57 | 58 | // Give the halos a yellowish color. 59 | let gradient_length = 0.5 * side_length; 60 | let gradient_value = clamp((pos.y - (resolution.y - gradient_length) * 0.5) / gradient_length, 0.0, 1.0); 61 | let warm_color = vec4(1.0, 0.69, 0.37, 1.0); 62 | let cool_color = vec4(0.67, 0.82, 1.0, 1.0); 63 | let color = mix(warm_color, cool_color, gradient_value); 64 | 65 | return color * v; 66 | } 67 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/oversized-canvas/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/oversized-canvas/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 |
WebGL not supported in this environment.
22 |
23 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/oversized-canvas/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | void main() { 10 | vec2 pos = gl_FragCoord.xy + u_offset; 11 | vec2 st = pos / u_resolution; 12 | fragColor = vec4(st, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/oversized-canvas/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 |
WebGPU not supported in this environment.
24 |
25 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/oversized-canvas/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | 4 | @fragment 5 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 6 | let pos = raw_pos.xy + offset; 7 | let st = pos / resolution; 8 | return vec4f(st, 0.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/remount/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/remount/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | {#if show} 11 | 26 |
WebGL not supported in this environment.
27 |
28 | {/if} 29 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/remount/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | void main() { 10 | vec2 pos = gl_FragCoord.xy + u_offset; 11 | vec2 st = pos / u_resolution; 12 | fragColor = vec4(st, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/remount/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | {#if show} 11 | 28 |
WebGPU not supported in this environment.
29 |
30 | {/if} 31 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/remount/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | 4 | @fragment 5 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 6 | let pos = raw_pos.xy + offset; 7 | let st = pos / resolution; 8 | return vec4f(st, 0.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/slider/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/slider/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

SLIDE ME

10 | 11 | 12 |
13 | 35 |
36 | 37 |
38 |
39 | 40 | 79 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/slider/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | uniform float u_scale; 9 | uniform float u_value; 10 | 11 | 12 | // Pick a pseudo-random number for the given `pos`. 13 | float random(vec2 pos) { 14 | return fract(sin(dot(pos, vec2(12.9898, 78.233))) * 43758.5453); 15 | } 16 | 17 | 18 | void main() { 19 | vec2 pos = gl_FragCoord.xy + u_offset; 20 | 21 | // Set up the tweakable parameters. 22 | float thumb_radius = 30.0 * u_scale; 23 | float track_radius = 2.0 * u_scale; 24 | float filled_track_radius = 4.0 * u_scale; 25 | float thumb_weight = mix(1.5, 8.0, u_value) * u_scale; 26 | float gloop_factor = 0.4; 27 | 28 | vec4 filled_color = vec4(0.84, 0.32, 0.0, 1.0); 29 | vec4 unfilled_color = vec4(1.0, 0.84, 0.73, 1.0); 30 | vec4 glow_color = vec4(1.0, 0.83, 0.5, 1.0); 31 | 32 | // Draw the thumb "cloud", meaning a fuzzy shape with a value of 1.0 at the center of the thumb and 0.5 at the edge. 33 | // This allows us to let the thumb and the track "melt" into each other. 34 | vec2 thumb_center = vec2(thumb_radius + (u_value * (u_resolution.x - 2.0 * thumb_radius)), u_resolution.y / 2.0); 35 | float thumb_dist = distance(pos, thumb_center); 36 | float thumb_norm_dist = pow(thumb_dist, gloop_factor) / (2.0 * pow(thumb_weight, gloop_factor)); 37 | float thumb_cloud = clamp(1.0 - thumb_norm_dist, 0.0, 1.0); 38 | 39 | // Draw a cloud shape for the whole track and the filled part of the track (the part left of the thumb). 40 | float track_min_x = thumb_radius; 41 | float track_max_x = u_resolution.x - thumb_radius; 42 | float track_dist = distance(pos, vec2(clamp(pos.x, track_min_x, track_max_x), u_resolution.y / 2.0)); 43 | float filled_track_dist = distance(pos, vec2(clamp(pos.x, track_min_x, thumb_center.x), u_resolution.y / 2.0)); 44 | float track_norm_dist = pow(track_dist, gloop_factor) / (2.0 * pow(track_radius, gloop_factor)); 45 | float filled_track_norm_dist = pow(filled_track_dist, gloop_factor) / (2.0 * pow(filled_track_radius, gloop_factor)); 46 | float track_cloud = clamp(1.0 - track_norm_dist, 0.0, 1.0); 47 | float filled_track_cloud = clamp(1.0 - filled_track_norm_dist, 0.0, 1.0); 48 | 49 | float rand = random(pos); 50 | 51 | // Mix the shapes. 52 | float filled = step(0.5 + rand * 0.06, thumb_cloud + filled_track_cloud); 53 | float unfilled = smoothstep(0.5, 0.52, track_cloud); 54 | float glow = step(mix(0.5 + rand * 0.06, 0.2 + rand * 0.2, u_value), thumb_cloud + filled_track_cloud); 55 | 56 | // Draw the shapes. 57 | vec4 color = vec4(0.0, 0.0, 0.0, 0.0); 58 | color = mix(color, unfilled_color, unfilled); 59 | color = mix(color, glow_color, glow); 60 | color = mix(color, filled_color, filled); 61 | 62 | fragColor = color; 63 | } 64 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/slider/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

SLIDE ME

10 | 11 | 12 |
13 | 38 |
39 | 40 |
41 |
42 | 43 | 82 | -------------------------------------------------------------------------------- /packages/tests-svelte4/src/routes/slider/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | @group(0) @binding(2) var scale: f32; 4 | @group(0) @binding(3) var value: f32; 5 | 6 | 7 | // Pick a pseudo-random number for the given `pos`. 8 | fn random(pos: vec2f) -> f32 { 9 | return fract(sin(dot(pos, vec2f(12.9898, 78.233))) * 43758.5453); 10 | } 11 | 12 | 13 | @fragment 14 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 15 | let pos = raw_pos.xy + offset; 16 | 17 | // Set up the tweakable parameters. 18 | let thumb_radius = 30.0 * scale; 19 | let track_radius = 2.0 * scale; 20 | let filled_track_radius = 4.0 * scale; 21 | let thumb_weight = mix(1.5, 8.0, value) * scale; 22 | let gloop_factor = 0.4; 23 | 24 | let filled_color = vec4f(0.84, 0.32, 0.0, 1.0); 25 | let unfilled_color = vec4f(1.0, 0.84, 0.73, 1.0); 26 | let glow_color = vec4f(1.0, 0.83, 0.5, 1.0); 27 | 28 | // Draw the thumb "cloud", meaning a fuzzy shape with a value of 1.0 at the center of the thumb and 0.5 at the edge. 29 | // This allows us to let the thumb and the track "melt" into each other. 30 | let thumb_center = vec2f(thumb_radius + (value * (resolution.x - 2.0 * thumb_radius)), resolution.y / 2.0); 31 | let thumb_dist = distance(pos, thumb_center); 32 | let thumb_norm_dist = pow(thumb_dist, gloop_factor) / (2.0 * pow(thumb_weight, gloop_factor)); 33 | let thumb_cloud = clamp(1.0 - thumb_norm_dist, 0.0, 1.0); 34 | 35 | // Draw a cloud shape for the whole track and the filled part of the track (the part left of the thumb). 36 | let track_min_x = thumb_radius; 37 | let track_max_x = resolution.x - thumb_radius; 38 | let track_dist = distance(pos, vec2f(clamp(pos.x, track_min_x, track_max_x), resolution.y / 2.0)); 39 | let filled_track_dist = distance(pos, vec2f(clamp(pos.x, track_min_x, thumb_center.x), resolution.y / 2.0)); 40 | let track_norm_dist = pow(track_dist, gloop_factor) / (2.0 * pow(track_radius, gloop_factor)); 41 | let filled_track_norm_dist = pow(filled_track_dist, gloop_factor) / (2.0 * pow(filled_track_radius, gloop_factor)); 42 | let track_cloud = clamp(1.0 - track_norm_dist, 0.0, 1.0); 43 | let filled_track_cloud = clamp(1.0 - filled_track_norm_dist, 0.0, 1.0); 44 | 45 | let rand = random(pos); 46 | 47 | // Mix the shapes. 48 | let filled = step(0.5 + rand * 0.06, thumb_cloud + filled_track_cloud); 49 | let unfilled = smoothstep(0.5, 0.52, track_cloud); 50 | let glow = step(mix(0.5 + rand * 0.06, 0.2 + rand * 0.2, value), thumb_cloud + filled_track_cloud); 51 | 52 | // Draw the shapes. 53 | var color = vec4f(0.0, 0.0, 0.0, 0.0); 54 | color = mix(color, unfilled_color, unfilled); 55 | color = mix(color, glow_color, glow); 56 | color = mix(color, filled_color, filled); 57 | 58 | return color; 59 | } 60 | -------------------------------------------------------------------------------- /packages/tests-svelte4/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/packages/tests-svelte4/static/favicon.png -------------------------------------------------------------------------------- /packages/tests-svelte4/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-auto"; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | adapter: adapter(), 7 | }, 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /packages/tests-svelte4/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte4/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | // @ts-expect-error: Because we're using vite-plugin-svelte 3 and vite 6, 6 | // these types are not entriely compatible, but in practice it works. 7 | plugins: [sveltekit()], 8 | preview: { 9 | port: 4173, 10 | strictPort: true, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/tests-svelte5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests-svelte5", 3 | "private": true, 4 | "scripts": { 5 | "postinstall": "svelte-kit sync", 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite build && vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" 11 | }, 12 | "devDependencies": { 13 | "svelte": "^5.0.0" 14 | }, 15 | "svelte": "./dist/index.js", 16 | "types": "./dist/index.d.ts", 17 | "type": "module" 18 | } 19 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/+layout.server.js: -------------------------------------------------------------------------------- 1 | export const trailingSlash = "always"; 2 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 |
    2 |
  1. Hello world
  2. 3 |
  3. Remounting canvas
  4. 4 |
  5. Oversized canvas
  6. 5 |
  7. Landing page with bubbles
  8. 6 |
  9. Landing page with a halo
  10. 7 |
  11. Slider component
  12. 8 |
9 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/hello-world/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/hello-world/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 23 |
WebGL not supported in this environment.
24 |
25 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/hello-world/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | void main() { 10 | vec2 pos = gl_FragCoord.xy + u_offset; 11 | vec2 st = pos / u_resolution; 12 | fragColor = vec4(st, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/hello-world/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 25 |
WebGPU not supported in this environment.
26 |
27 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/hello-world/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | 4 | @fragment 5 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 6 | let pos = raw_pos.xy + offset; 7 | let st = pos / resolution; 8 | return vec4f(st, 0.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-bubbles/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-bubbles/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 22 | 23 | 27 | 28 | 29 |
30 |
31 | 57 |
58 | 59 |

SVADER

60 |
61 | 62 | 101 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-bubbles/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | uniform float u_scale; 9 | uniform float u_time; 10 | uniform vec3 u_color; 11 | 12 | 13 | // Determine the value of the pixel at `pos`, if we want to draw a circle at `center` with a the given `radius`. 14 | float circle(vec2 pos, vec2 center, float radius) { 15 | return 1.0 - smoothstep(radius - 1.5, radius, distance(pos, center)); 16 | } 17 | 18 | 19 | // Determine the value of the pixel at `pos`, if we want to draw a 'bubble' (wobbly circle) at `center` with a the given `radius`. 20 | // The `random_seed` is used to make all bubbles wobble a bit differently. 21 | float bubble(vec2 pos, vec2 center, float radius, float random_seed) { 22 | const float pi = 3.1415926535897932384626433832795; 23 | 24 | // Determine the angle between `pos` and `center`. 25 | float angle = atan(pos.y - center.y, pos.x - center.x) / pi; 26 | 27 | // Create wobble by layering multiple sine waves. 28 | // One with a frequency of 1, one with a frequency of 2, etc. up to 6. 29 | float wobble = 0.0; 30 | for (float i = 1.0; i <= 6.0; i++) { 31 | float random_offset = sin(random_seed) * pi * pow(i, 2.0); 32 | float speed = pow(i, 2.0) * 0.05; 33 | float amplitude = 0.05 / i; 34 | wobble += sin(angle * i * pi + u_time * speed + random_offset) * amplitude; 35 | } 36 | 37 | float wobbly_radius = radius * (wobble + 1.0); 38 | return circle(pos, center, wobbly_radius); 39 | } 40 | 41 | 42 | void main() { 43 | vec2 pos = gl_FragCoord.xy + u_offset; 44 | 45 | // Mirror the y-axis so that the origin is in the top-left corner. 46 | pos.y = u_resolution.y - pos.y; 47 | 48 | // Break the screen into square tiles with a width of 300 CSS pixels each. 49 | float tile_width = 300.0 * u_scale; 50 | vec2 tile_center = vec2(tile_width / 2.0); 51 | vec2 tile_coords = mod(pos + tile_center, tile_width); 52 | 53 | // Determine which tile we're in and use that as a random seed. 54 | vec2 which_tile = floor((pos + tile_center) / tile_width); 55 | float tile_index = which_tile.x + which_tile.y * 10.0; // Assume at most 10 tiles in width 56 | float random_seed = tile_index; 57 | 58 | float bubble_gap = 20.0 * u_scale; 59 | float radius = tile_width / 2.0 - bubble_gap; 60 | 61 | float v = bubble(tile_coords, tile_center, radius, random_seed); 62 | 63 | fragColor = vec4(u_color * v, v); 64 | } 65 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-bubbles/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 22 | 23 | 27 | 28 | 29 |
30 |
31 | 56 |
57 | 58 |

SVADER

59 |
60 | 61 | 100 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-bubbles/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var offset: vec2f; 2 | @group(0) @binding(1) var scale: f32; 3 | @group(0) @binding(2) var time: f32; 4 | @group(0) @binding(3) var color: vec3f; 5 | 6 | 7 | // Determine the value of the pixel at `pos`, if we want to draw a circle at `center` with a the given `radius`. 8 | fn circle(pos: vec2f, center: vec2f, radius: f32) -> f32 { 9 | return 1.0 - smoothstep(radius - 1.5, radius, distance(pos, center)); 10 | } 11 | 12 | 13 | // Determine the value of the pixel at `pos`, if we want to draw a 'bubble' (wobbly circle) at `center` with a the given `radius`. 14 | // The `random_seed` is used to make all bubbles wobble a bit differently. 15 | fn bubble(pos: vec2f, center: vec2f, radius: f32, random_seed: f32) -> f32 { 16 | let pi = 3.1415926535897932384626433832795; 17 | 18 | // Determine the angle between `pos` and `center`. 19 | let angle = atan2(pos.y - center.y, pos.x - center.x) / pi; 20 | 21 | // Create wobble by layering multiple sine waves. 22 | // One with a frequency of 1, one with a frequency of 2, etc. up to 6. 23 | var wobble = 0.0; 24 | for (var i = 1.0; i <= 6.0; i += 1.0) { 25 | let random_offset = sin(random_seed) * pi * pow(i, 2.0); 26 | let speed = pow(i, 2.0) * 0.05; 27 | let amplitude = 0.05 / i; 28 | wobble += sin(angle * i * pi + time * speed + random_offset) * amplitude; 29 | } 30 | 31 | let wobbly_radius = radius * (wobble + 1.0); 32 | return circle(pos, center, wobbly_radius); 33 | } 34 | 35 | 36 | @fragment 37 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 38 | let pos = raw_pos.xy + offset; 39 | 40 | // Break the screen into square tiles with a width of 300 CSS pixels each. 41 | let tile_width = 300.0 * scale; 42 | let tile_center = vec2f(tile_width / 2.0); 43 | let tile_coords = (pos + tile_center) % tile_width; 44 | 45 | // Determine which tile we're in and use that as a random seed. 46 | let which_tile = floor((pos + tile_center) / tile_width); 47 | let tile_index = which_tile.x + which_tile.y * 10.0; // Assume at most 10 tiles in width 48 | let random_seed = tile_index; 49 | 50 | let bubble_gap = 20.0 * scale; 51 | let radius = tile_width / 2.0 - bubble_gap; 52 | 53 | let v = bubble(tile_coords, tile_center, radius, random_seed); 54 | 55 | return vec4f(color * v, v); 56 | } 57 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-halo/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-halo/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 |
19 |
20 | 33 |
34 | 35 |

SVADER

36 |
37 | 38 | 76 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-halo/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | 10 | // Pick a pseudo-random number for the given `pos`. 11 | float random(vec2 pos) { 12 | vec2 st = pos / u_resolution; 13 | return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123); 14 | } 15 | 16 | 17 | // Determine the value of the pixel at `pos`, if we want to draw a halo at `center` with a the given `radius`. 18 | // The `side_length` represents the smaller dimension of the canvas, and is used to scale all values accordingly. 19 | float halo(vec2 pos, vec2 center, float radius, float side_length) { 20 | float halo_solid_feather = 0.02 * side_length; 21 | float halo_thickness = 0.017 * side_length; 22 | float halo_glow_spread = 0.3 * side_length; 23 | 24 | // Create a distance field from the center. 25 | float center_d = distance(pos, center); 26 | // Create a distence field for the halo. 27 | float halo_d = abs(center_d - radius); 28 | // Draw the 'solid' part of the halo. 29 | // By multiplying by 5.0 we generate a value greater than 1.0, 30 | // which will make the color saturate into a whiter nuance and give the halo sharper edges. 31 | float halo_solid = smoothstep(halo_d, halo_d + halo_solid_feather, halo_thickness) * 5.0; 32 | // Draw the glow around the halo. 33 | float halo_glow = pow(1.0 - clamp(halo_d / halo_glow_spread, 0.0, 1.0), 5.0) * 0.4 * (0.85 + random(pos) * 0.3); 34 | 35 | return halo_solid + halo_glow; 36 | } 37 | 38 | 39 | void main() { 40 | vec2 pos = gl_FragCoord.xy + u_offset; 41 | 42 | vec2 screen_center = u_resolution / 2.0; 43 | 44 | // This shader automatically scales to fit the smallest dimension of the canvas. 45 | float side_length = min(u_resolution.x, u_resolution.y); 46 | 47 | // Draw 5 halos to create a sense of depth. 48 | float v = 0.0; 49 | for (float i = 0.0; i < 5.0; i++) { 50 | // As the halos get further back they: 51 | // - Are placed slightly higher up 52 | vec2 center = screen_center + vec2(0.0, i * 0.005 * side_length); 53 | // - Get smaller 54 | float radius = (side_length * 0.4) / (i * 0.15 + 1.0); 55 | // - Gradually fade out 56 | float fade_factor = pow(0.3, i); 57 | 58 | v += halo(pos, center, radius, side_length) * fade_factor; 59 | } 60 | 61 | // Give the halos a yellowish color. 62 | float gradient_length = 0.5 * side_length; 63 | float gradient_value = clamp((-pos.y + (u_resolution.y + gradient_length) * 0.5) / gradient_length, 0.0, 1.0); 64 | vec4 warm_color = vec4(1.0, 0.69, 0.37, 1.0); 65 | vec4 cool_color = vec4(0.67, 0.82, 1.0, 1.0); 66 | vec4 color = mix(warm_color, cool_color, gradient_value); 67 | 68 | fragColor = color * v; 69 | } 70 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-halo/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 |
19 |
20 | 35 |
36 | 37 |

SVADER

38 |
39 | 40 | 78 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/landing-page-halo/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | @group(0) @binding(2) var scale: f32; 4 | 5 | 6 | // Pick a pseudo-random number for the given `pos`. 7 | fn random(pos: vec2f) -> f32 { 8 | let st = pos / resolution; 9 | return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123); 10 | } 11 | 12 | 13 | // Determine the value of the pixel at `pos`, if we want to draw a halo at `center` with a the given `radius`. 14 | // The `side_length` represents the smaller dimension of the canvas, and is used to scale all values accordingly. 15 | fn halo(pos: vec2f, center: vec2f, radius: f32, side_length: f32) -> f32 { 16 | let halo_solid_feather = 0.02 * side_length; 17 | let halo_thickness = 0.017 * side_length; 18 | let halo_glow_spread = 0.3 * side_length; 19 | 20 | // Create a distance field from the center. 21 | let center_d = distance(pos, center); 22 | // Create a distence field for the halo. 23 | let halo_d = abs(center_d - radius); 24 | // Draw the 'solid' part of the halo. 25 | // By multiplying by 5.0 we generate a value greater than 1.0, 26 | // which will make the color saturate into a whiter nuance and give the halo sharper edges. 27 | let halo_solid = smoothstep(halo_d, halo_d + halo_solid_feather, halo_thickness) * 5.0; 28 | // Draw the glow around the halo. 29 | let halo_glow = pow(1.0 - clamp(halo_d / halo_glow_spread, 0.0, 1.0), 5.0) * 0.4 * (0.85 + random(pos) * 0.3); 30 | 31 | return halo_solid + halo_glow; 32 | } 33 | 34 | 35 | @fragment 36 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 37 | let pos = raw_pos.xy + offset; 38 | 39 | let screen_center = resolution / 2.0; 40 | 41 | // This shader automatically scales to fit the smallest dimension of the canvas. 42 | let side_length = min(resolution.x, resolution.y); 43 | 44 | // Draw 5 halos to create a sense of depth. 45 | var v = 0.0; 46 | for (var i = 0.0; i < 5.0; i += 1.0) { 47 | // As the halos get further back they: 48 | // - Are placed slightly higher up 49 | let center = screen_center - vec2(0.0, i * 0.005 * side_length); 50 | // - Get smaller 51 | let radius = (side_length * 0.4) / (i * 0.15 + 1.0); 52 | // - Gradually fade out 53 | let fade_factor = pow(0.3, i); 54 | 55 | v += halo(pos, center, radius, side_length) * fade_factor; 56 | } 57 | 58 | // Give the halos a yellowish color. 59 | let gradient_length = 0.5 * side_length; 60 | let gradient_value = clamp((pos.y - (resolution.y - gradient_length) * 0.5) / gradient_length, 0.0, 1.0); 61 | let warm_color = vec4(1.0, 0.69, 0.37, 1.0); 62 | let cool_color = vec4(0.67, 0.82, 1.0, 1.0); 63 | let color = mix(warm_color, cool_color, gradient_value); 64 | 65 | return color * v; 66 | } 67 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/oversized-canvas/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/oversized-canvas/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 23 |
WebGL not supported in this environment.
24 |
25 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/oversized-canvas/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | void main() { 10 | vec2 pos = gl_FragCoord.xy + u_offset; 11 | vec2 st = pos / u_resolution; 12 | fragColor = vec4(st, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/oversized-canvas/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 25 |
WebGPU not supported in this environment.
26 |
27 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/oversized-canvas/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | 4 | @fragment 5 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 6 | let pos = raw_pos.xy + offset; 7 | let st = pos / resolution; 8 | return vec4f(st, 0.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/remount/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/remount/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | {#if show} 13 | 28 |
WebGL not supported in this environment.
29 |
30 | {/if} 31 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/remount/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | 9 | void main() { 10 | vec2 pos = gl_FragCoord.xy + u_offset; 11 | vec2 st = pos / u_resolution; 12 | fragColor = vec4(st, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/remount/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | {#if show} 13 | 30 |
WebGPU not supported in this environment.
31 |
32 | {/if} 33 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/remount/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | 4 | @fragment 5 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 6 | let pos = raw_pos.xy + offset; 7 | let st = pos / resolution; 8 | return vec4f(st, 0.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/slider/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/slider/webgl/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 |
11 |

SLIDE ME

12 | 13 | 14 |
15 | 37 |
38 | 39 |
40 |
41 | 42 | 81 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/slider/webgl/shader.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | out vec4 fragColor; 5 | 6 | uniform vec2 u_resolution; 7 | uniform vec2 u_offset; 8 | uniform float u_scale; 9 | uniform float u_value; 10 | 11 | 12 | // Pick a pseudo-random number for the given `pos`. 13 | float random(vec2 pos) { 14 | return fract(sin(dot(pos, vec2(12.9898, 78.233))) * 43758.5453); 15 | } 16 | 17 | 18 | void main() { 19 | vec2 pos = gl_FragCoord.xy + u_offset; 20 | 21 | // Set up the tweakable parameters. 22 | float thumb_radius = 30.0 * u_scale; 23 | float track_radius = 2.0 * u_scale; 24 | float filled_track_radius = 4.0 * u_scale; 25 | float thumb_weight = mix(1.5, 8.0, u_value) * u_scale; 26 | float gloop_factor = 0.4; 27 | 28 | vec4 filled_color = vec4(0.84, 0.32, 0.0, 1.0); 29 | vec4 unfilled_color = vec4(1.0, 0.84, 0.73, 1.0); 30 | vec4 glow_color = vec4(1.0, 0.83, 0.5, 1.0); 31 | 32 | // Draw the thumb "cloud", meaning a fuzzy shape with a value of 1.0 at the center of the thumb and 0.5 at the edge. 33 | // This allows us to let the thumb and the track "melt" into each other. 34 | vec2 thumb_center = vec2(thumb_radius + (u_value * (u_resolution.x - 2.0 * thumb_radius)), u_resolution.y / 2.0); 35 | float thumb_dist = distance(pos, thumb_center); 36 | float thumb_norm_dist = pow(thumb_dist, gloop_factor) / (2.0 * pow(thumb_weight, gloop_factor)); 37 | float thumb_cloud = clamp(1.0 - thumb_norm_dist, 0.0, 1.0); 38 | 39 | // Draw a cloud shape for the whole track and the filled part of the track (the part left of the thumb). 40 | float track_min_x = thumb_radius; 41 | float track_max_x = u_resolution.x - thumb_radius; 42 | float track_dist = distance(pos, vec2(clamp(pos.x, track_min_x, track_max_x), u_resolution.y / 2.0)); 43 | float filled_track_dist = distance(pos, vec2(clamp(pos.x, track_min_x, thumb_center.x), u_resolution.y / 2.0)); 44 | float track_norm_dist = pow(track_dist, gloop_factor) / (2.0 * pow(track_radius, gloop_factor)); 45 | float filled_track_norm_dist = pow(filled_track_dist, gloop_factor) / (2.0 * pow(filled_track_radius, gloop_factor)); 46 | float track_cloud = clamp(1.0 - track_norm_dist, 0.0, 1.0); 47 | float filled_track_cloud = clamp(1.0 - filled_track_norm_dist, 0.0, 1.0); 48 | 49 | float rand = random(pos); 50 | 51 | // Mix the shapes. 52 | float filled = step(0.5 + rand * 0.06, thumb_cloud + filled_track_cloud); 53 | float unfilled = smoothstep(0.5, 0.52, track_cloud); 54 | float glow = step(mix(0.5 + rand * 0.06, 0.2 + rand * 0.2, u_value), thumb_cloud + filled_track_cloud); 55 | 56 | // Draw the shapes. 57 | vec4 color = vec4(0.0, 0.0, 0.0, 0.0); 58 | color = mix(color, unfilled_color, unfilled); 59 | color = mix(color, glow_color, glow); 60 | color = mix(color, filled_color, filled); 61 | 62 | fragColor = color; 63 | } 64 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/slider/webgpu/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 |
11 |

SLIDE ME

12 | 13 | 14 |
15 | 40 |
41 | 42 |
43 |
44 | 45 | 84 | -------------------------------------------------------------------------------- /packages/tests-svelte5/src/routes/slider/webgpu/shader.wgsl: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var resolution: vec2f; 2 | @group(0) @binding(1) var offset: vec2f; 3 | @group(0) @binding(2) var scale: f32; 4 | @group(0) @binding(3) var value: f32; 5 | 6 | 7 | // Pick a pseudo-random number for the given `pos`. 8 | fn random(pos: vec2f) -> f32 { 9 | return fract(sin(dot(pos, vec2f(12.9898, 78.233))) * 43758.5453); 10 | } 11 | 12 | 13 | @fragment 14 | fn main(@builtin(position) raw_pos: vec4f) -> @location(0) vec4f { 15 | let pos = raw_pos.xy + offset; 16 | 17 | // Set up the tweakable parameters. 18 | let thumb_radius = 30.0 * scale; 19 | let track_radius = 2.0 * scale; 20 | let filled_track_radius = 4.0 * scale; 21 | let thumb_weight = mix(1.5, 8.0, value) * scale; 22 | let gloop_factor = 0.4; 23 | 24 | let filled_color = vec4f(0.84, 0.32, 0.0, 1.0); 25 | let unfilled_color = vec4f(1.0, 0.84, 0.73, 1.0); 26 | let glow_color = vec4f(1.0, 0.83, 0.5, 1.0); 27 | 28 | // Draw the thumb "cloud", meaning a fuzzy shape with a value of 1.0 at the center of the thumb and 0.5 at the edge. 29 | // This allows us to let the thumb and the track "melt" into each other. 30 | let thumb_center = vec2f(thumb_radius + (value * (resolution.x - 2.0 * thumb_radius)), resolution.y / 2.0); 31 | let thumb_dist = distance(pos, thumb_center); 32 | let thumb_norm_dist = pow(thumb_dist, gloop_factor) / (2.0 * pow(thumb_weight, gloop_factor)); 33 | let thumb_cloud = clamp(1.0 - thumb_norm_dist, 0.0, 1.0); 34 | 35 | // Draw a cloud shape for the whole track and the filled part of the track (the part left of the thumb). 36 | let track_min_x = thumb_radius; 37 | let track_max_x = resolution.x - thumb_radius; 38 | let track_dist = distance(pos, vec2f(clamp(pos.x, track_min_x, track_max_x), resolution.y / 2.0)); 39 | let filled_track_dist = distance(pos, vec2f(clamp(pos.x, track_min_x, thumb_center.x), resolution.y / 2.0)); 40 | let track_norm_dist = pow(track_dist, gloop_factor) / (2.0 * pow(track_radius, gloop_factor)); 41 | let filled_track_norm_dist = pow(filled_track_dist, gloop_factor) / (2.0 * pow(filled_track_radius, gloop_factor)); 42 | let track_cloud = clamp(1.0 - track_norm_dist, 0.0, 1.0); 43 | let filled_track_cloud = clamp(1.0 - filled_track_norm_dist, 0.0, 1.0); 44 | 45 | let rand = random(pos); 46 | 47 | // Mix the shapes. 48 | let filled = step(0.5 + rand * 0.06, thumb_cloud + filled_track_cloud); 49 | let unfilled = smoothstep(0.5, 0.52, track_cloud); 50 | let glow = step(mix(0.5 + rand * 0.06, 0.2 + rand * 0.2, value), thumb_cloud + filled_track_cloud); 51 | 52 | // Draw the shapes. 53 | var color = vec4f(0.0, 0.0, 0.0, 0.0); 54 | color = mix(color, unfilled_color, unfilled); 55 | color = mix(color, glow_color, glow); 56 | color = mix(color, filled_color, filled); 57 | 58 | return color; 59 | } 60 | -------------------------------------------------------------------------------- /packages/tests-svelte5/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/packages/tests-svelte5/static/favicon.png -------------------------------------------------------------------------------- /packages/tests-svelte5/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-auto"; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | adapter: adapter(), 7 | }, 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /packages/tests-svelte5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/tests-svelte5/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from "@sveltejs/kit/vite"; 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | preview: { 7 | port: 4174, 8 | strictPort: true, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | /** 4 | * See https://playwright.dev/docs/test-configuration. 5 | */ 6 | export default defineConfig({ 7 | testDir: "./tests", 8 | snapshotPathTemplate: 9 | "{testDir}/.generated-screenshots/{testFilePath}/{arg}{ext}", 10 | /* Run tests in files in parallel */ 11 | fullyParallel: true, 12 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 13 | forbidOnly: !!process.env.CI, 14 | /* Opt out of parallel tests on CI. */ 15 | workers: process.env.CI ? 1 : 4, 16 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 17 | reporter: "html", 18 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 19 | use: { 20 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 21 | trace: "on-first-retry", 22 | contextOptions: { 23 | // Disable animations so screenshots are comparable 24 | reducedMotion: "reduce", 25 | }, 26 | }, 27 | 28 | projects: [ 29 | { 30 | name: "Firefox ", 31 | use: { 32 | ...devices["Desktop Firefox"], 33 | }, 34 | }, 35 | { 36 | name: "Chromium", 37 | use: { 38 | ...devices["Desktop Chrome"], 39 | channel: "chrome", 40 | launchOptions: { 41 | args: [ 42 | "--high-dpi-support=1", 43 | "--force-device-scale-factor=1", 44 | ], 45 | }, 46 | }, 47 | }, 48 | { 49 | name: "WebKit ", 50 | use: { ...devices["Desktop Safari"] }, 51 | }, 52 | /* TODO: Test these as well */ 53 | // { 54 | // name: 'Mobile Chrome', 55 | // use: { ...devices['Pixel 5'] }, 56 | // }, 57 | // { 58 | // name: 'Mobile Safari', 59 | // use: { ...devices['iPhone 12'] }, 60 | // }, 61 | ], 62 | 63 | webServer: [ 64 | { 65 | command: "npm run preview:v4", 66 | port: 4173, 67 | reuseExistingServer: !process.env.CI, 68 | }, 69 | { 70 | command: "npm run preview:v5", 71 | port: 4174, 72 | reuseExistingServer: !process.env.CI, 73 | }, 74 | ], 75 | }); 76 | -------------------------------------------------------------------------------- /resources/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/banner.png -------------------------------------------------------------------------------- /resources/bubbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/bubbles.png -------------------------------------------------------------------------------- /resources/collage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/collage.png -------------------------------------------------------------------------------- /resources/debugShaderWebGl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/debugShaderWebGl.png -------------------------------------------------------------------------------- /resources/debugShaderWebGpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/debugShaderWebGpu.png -------------------------------------------------------------------------------- /resources/halo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/halo.png -------------------------------------------------------------------------------- /resources/logoDark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/logoDark.png -------------------------------------------------------------------------------- /resources/logoLight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/logoLight.png -------------------------------------------------------------------------------- /resources/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/resources/slider.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/hello-world-webgl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/hello-world-webgl.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/hello-world-webgpu-unsupported.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/hello-world-webgpu-unsupported.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/hello-world-webgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/hello-world-webgpu.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/landing-page-bubbles-webgl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/landing-page-bubbles-webgl.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/landing-page-bubbles-webgpu-unsupported.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/landing-page-bubbles-webgpu-unsupported.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/landing-page-bubbles-webgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/landing-page-bubbles-webgpu.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/landing-page-halo-webgl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/landing-page-halo-webgl.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/landing-page-halo-webgpu-unsupported.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/landing-page-halo-webgpu-unsupported.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/landing-page-halo-webgpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/landing-page-halo-webgpu.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgl-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgl-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgl-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgl-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-unsupported-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-unsupported-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-unsupported-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/oversized-canvas-webgpu-unsupported-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/remount-webgl-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/remount-webgl-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/remount-webgl-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/remount-webgl-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-unsupported-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-unsupported-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-unsupported-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/remount-webgpu-unsupported-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgl-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgl-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgl-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgl-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgl-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgl-3.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-3.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-unsupported-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-unsupported-1.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-unsupported-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-unsupported-2.png -------------------------------------------------------------------------------- /tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-unsupported-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sockmaster27/svader/55b9e50a25a083118441d9d53cb8e975dec2f7b5/tests/.generated-screenshots/test-sites.spec.ts/slider-webgpu-unsupported-3.png -------------------------------------------------------------------------------- /tests/test-sites.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, TestInfo, Page } from "@playwright/test"; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | /** Bind the console to the Playwright test runner, rather than the browser */ 5 | const logOuter = console.log; 6 | page.on("console", msg => { 7 | logOuter(`[console.${msg.type()}] ${msg.text()}`); 8 | }); 9 | page.on("pageerror", msg => { 10 | expect.soft(false, `Error thrown:\n${msg.message}`).toBeTruthy(); 11 | }); 12 | }); 13 | 14 | /** 15 | * Takes a screenshot of the current page and compares it to a reference, as indexed by the given arguments. 16 | * If a reference screenshot does not exist, it will be created and used for future assertions. 17 | * 18 | * @param page 19 | * @param info 20 | * @param name A name that identifies this test. 21 | * @param api The API used in the test, either `"webgl"` or `"webgpu"`. 22 | * @param number A number identifying the specific screeenshot within the test, if multiple are included. 23 | */ 24 | async function assertScreenshot( 25 | page: Page, 26 | info: TestInfo, 27 | name: string, 28 | api: "webgl" | "webgpu", 29 | number?: number, 30 | ) { 31 | const projectName = info.project.name; 32 | 33 | const isWebGpu = api === "webgpu"; 34 | const isWebGpuUnsupported = projectName.includes(""); 35 | const unsupportedString = 36 | isWebGpu && isWebGpuUnsupported ? "-unsupported" : ""; 37 | 38 | const isMobile = info.project.use.isMobile ?? false; 39 | const mobileString = isMobile ? "-mobile" : ""; 40 | 41 | const numberString = number !== undefined ? `-${number}` : ""; 42 | 43 | const fileName = `${name}-${api}${unsupportedString}${mobileString}${numberString}.png`; 44 | 45 | await expect 46 | .soft(page) 47 | .toHaveScreenshot(fileName, { maxDiffPixelRatio: 0.01 }); 48 | } 49 | 50 | const apis = ["webgl", "webgpu"] as const; 51 | const builds = [ 52 | { name: "svelte4", port: 4173 }, 53 | { name: "svelte5", port: 4174 }, 54 | ] as const; 55 | 56 | builds.forEach(({ name, port }) => { 57 | test.describe(name, () => { 58 | test.use({ baseURL: `http://localhost:${port}` }); 59 | 60 | apis.forEach(api => { 61 | test.describe(api, () => { 62 | test("Hello world", async ({ page }, info) => { 63 | const pageName = "hello-world"; 64 | 65 | await page.goto(`/${pageName}/${api}`); 66 | await assertScreenshot(page, info, pageName, api); 67 | }); 68 | 69 | test("Remounting canvas", async ({ page }, info) => { 70 | const pageName = "remount"; 71 | 72 | await page.goto(`/${pageName}/${api}`); 73 | const show = page.getByLabel("Show"); 74 | await show.uncheck(); 75 | await assertScreenshot(page, info, pageName, api, 1); 76 | for (let i = 0; i < 10; i++) { 77 | await show.check(); 78 | await show.uncheck(); 79 | } 80 | await show.check(); 81 | await assertScreenshot(page, info, pageName, api, 2); 82 | }); 83 | 84 | test("Oversized canvas", async ({ page }, info) => { 85 | const pageName = "oversized-canvas"; 86 | 87 | await page.goto(`/${pageName}/${api}`); 88 | await assertScreenshot(page, info, pageName, api, 1); 89 | // Scroll to bottom-right corner 90 | await page.evaluate(() => 91 | window.scrollBy( 92 | document.body.scrollWidth, 93 | document.body.scrollHeight, 94 | ), 95 | ); 96 | await assertScreenshot(page, info, pageName, api, 2); 97 | }); 98 | 99 | test("Landing page with bubbles", async ({ page }, info) => { 100 | const pageName = "landing-page-bubbles"; 101 | 102 | await page.goto(`/${pageName}/${api}`); 103 | await assertScreenshot(page, info, pageName, api); 104 | }); 105 | 106 | test("Landing page with a halo", async ({ page }, info) => { 107 | const pageName = "landing-page-halo"; 108 | 109 | await page.goto(`/${pageName}/${api}`); 110 | await assertScreenshot(page, info, pageName, api); 111 | }); 112 | 113 | test("Slider component", async ({ page }, info) => { 114 | const pageName = "slider"; 115 | 116 | await page.goto(`/${pageName}/${api}`); 117 | const slider = page.getByRole("slider"); 118 | await assertScreenshot(page, info, pageName, api, 1); 119 | await slider.fill("1"); 120 | await assertScreenshot(page, info, pageName, api, 2); 121 | await slider.fill("0"); 122 | await assertScreenshot(page, info, pageName, api, 3); 123 | }); 124 | }); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["node"], 4 | "strict": true 5 | } 6 | } 7 | --------------------------------------------------------------------------------