From 1959dc7639ea08db52a8bfa17089551c6c51bb9d Mon Sep 17 00:00:00 2001 From: Julien Sulpis Date: Wed, 27 Nov 2024 22:36:56 +0100 Subject: [PATCH] use spaces instead of tabs --- .prettierrc | 3 +- docs/src/content/config.ts | 6 +- .../content/docs/introduction/quick-start.mdx | 22 +- docs/tsconfig.json | 2 +- eslint.config.mjs | 12 +- lib/CHANGELOG.md | 11 +- lib/astro.config.mjs | 24 +- lib/package.json | 104 +++---- lib/playground/src/components/renderCount.ts | 4 +- lib/playground/src/components/routes.ts | 14 +- lib/playground/src/env.d.ts | 2 +- lib/playground/tsconfig.json | 14 +- lib/playwright.config.ts | 128 ++++----- lib/src/core/attribute.ts | 96 +++---- lib/src/core/buffer.ts | 18 +- lib/src/core/program.ts | 42 +-- lib/src/core/renderTarget.ts | 90 +++--- lib/src/core/shader.ts | 64 ++--- lib/src/hooks/useBoundingRect.ts | 102 +++---- lib/src/hooks/useCompositor.ts | 74 ++--- lib/src/hooks/useEffectPass.ts | 2 +- lib/src/hooks/useLoop.ts | 154 +++++------ lib/src/hooks/usePointerEvents.ts | 96 +++---- lib/src/hooks/useQuadRenderPass.ts | 62 ++--- lib/src/hooks/useRenderPass.ts | 258 +++++++++--------- lib/src/hooks/useResizeObserver.ts | 88 +++--- lib/src/hooks/useWebGLContext.ts | 34 +-- lib/src/internal/findName.ts | 14 +- lib/src/internal/useAttributes.ts | 68 ++--- lib/src/internal/useLifeCycleCallback.ts | 10 +- lib/src/internal/useUniforms.ts | 204 +++++++------- lib/src/types.ts | 68 ++--- lib/tsconfig.json | 20 +- 33 files changed, 954 insertions(+), 956 deletions(-) diff --git a/.prettierrc b/.prettierrc index ebf1ed4..d986270 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,4 @@ { - "printWidth": 100 + "printWidth": 100, + "useTabs": false } diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts index 45f60b0..a4eec59 100644 --- a/docs/src/content/config.ts +++ b/docs/src/content/config.ts @@ -1,6 +1,6 @@ -import { defineCollection } from 'astro:content'; -import { docsSchema } from '@astrojs/starlight/schema'; +import { defineCollection } from "astro:content"; +import { docsSchema } from "@astrojs/starlight/schema"; export const collections = { - docs: defineCollection({ schema: docsSchema() }), + docs: defineCollection({ schema: docsSchema() }), }; diff --git a/docs/src/content/docs/introduction/quick-start.mdx b/docs/src/content/docs/introduction/quick-start.mdx index 7a8cfed..d550a18 100644 --- a/docs/src/content/docs/introduction/quick-start.mdx +++ b/docs/src/content/docs/introduction/quick-start.mdx @@ -15,21 +15,21 @@ A proper documentation will come soon ! - ## Usage +`} /> diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 032ad64..b7243b9 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -4,4 +4,4 @@ "jsx": "react-jsx", "jsxImportSource": "react" } -} \ No newline at end of file +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 6837e43..2b0bba7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,10 +1,10 @@ import unjs from "eslint-config-unjs"; export default unjs({ - rules: { - "unicorn/filename-case": "off", - "unicorn/no-null": "off", - "@typescript-eslint/consistent-type-imports": "error", - "@typescript-eslint/triple-slash-reference": "off", - }, + rules: { + "unicorn/filename-case": "off", + "unicorn/no-null": "off", + "@typescript-eslint/consistent-type-imports": "error", + "@typescript-eslint/triple-slash-reference": "off", + }, }); diff --git a/lib/CHANGELOG.md b/lib/CHANGELOG.md index bb519e4..f5856f0 100644 --- a/lib/CHANGELOG.md +++ b/lib/CHANGELOG.md @@ -1,6 +1,5 @@ # Changelog - ## v0.2.0 [compare changes](https://github.com/jsulpis/usegl/compare/v0.1.0...v0.2.0) @@ -11,7 +10,7 @@ - Allow to provide the canvas as a CSS selector ([a7f8d0f](https://github.com/jsulpis/usegl/commit/a7f8d0f)) - Add useLoop hook with play/pause controls ([f3e2cf3](https://github.com/jsulpis/usegl/commit/f3e2cf3)) - Add useBoundingRect hook ([8d1851e](https://github.com/jsulpis/usegl/commit/8d1851e)) -- ⚠️ Change onPointerEvents to usePointerEvents and add more events ([ddeabd9](https://github.com/jsulpis/usegl/commit/ddeabd9)) +- ⚠️ Change onPointerEvents to usePointerEvents and add more events ([ddeabd9](https://github.com/jsulpis/usegl/commit/ddeabd9)) ### 🩹 Fixes @@ -20,7 +19,7 @@ ### 💅 Refactors -- ⚠️ Change `onCanvasResize` to `useResizeObserver` and provide controls on the observer ([31e38ff](https://github.com/jsulpis/usegl/commit/31e38ff)) +- ⚠️ Change `onCanvasResize` to `useResizeObserver` and provide controls on the observer ([31e38ff](https://github.com/jsulpis/usegl/commit/31e38ff)) ### 🏡 Chore @@ -39,12 +38,11 @@ #### ⚠️ Breaking Changes -- ⚠️ Change onPointerEvents to usePointerEvents and add more events ([ddeabd9](https://github.com/jsulpis/usegl/commit/ddeabd9)) -- ⚠️ Change `onCanvasResize` to `useResizeObserver` and provide controls on the observer ([31e38ff](https://github.com/jsulpis/usegl/commit/31e38ff)) +- ⚠️ Change onPointerEvents to usePointerEvents and add more events ([ddeabd9](https://github.com/jsulpis/usegl/commit/ddeabd9)) +- ⚠️ Change `onCanvasResize` to `useResizeObserver` and provide controls on the observer ([31e38ff](https://github.com/jsulpis/usegl/commit/31e38ff)) ## v0.1.0 - ### 🚀 Enhancements - Add a first version of useWebGLCanvas, onCanvasResize, and add a gradient demo ([f464f08](https://github.com/jsulpis/usegl/commit/f464f08)) @@ -79,4 +77,3 @@ - Separate packages for docs and lib ([1857c1c](https://github.com/jsulpis/usegl/commit/1857c1c)) - Add license ([e0801ca](https://github.com/jsulpis/usegl/commit/e0801ca)) - Setup unjs package template ([0dc0d0c](https://github.com/jsulpis/usegl/commit/0dc0d0c)) - diff --git a/lib/astro.config.mjs b/lib/astro.config.mjs index ef74a55..9c17ff2 100644 --- a/lib/astro.config.mjs +++ b/lib/astro.config.mjs @@ -6,16 +6,16 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); // https://astro.build/config export default defineConfig({ - srcDir: "./playground/src", - publicDir: "./playground/public", - vite: { - resolve: { - alias: { - usegl: resolve(__dirname, "./src/index.ts"), - }, - }, - }, - devToolbar: { - enabled: false, - }, + srcDir: "./playground/src", + publicDir: "./playground/public", + vite: { + resolve: { + alias: { + usegl: resolve(__dirname, "./src/index.ts"), + }, + }, + }, + devToolbar: { + enabled: false, + }, }); diff --git a/lib/package.json b/lib/package.json index b35ef56..d5e53f5 100644 --- a/lib/package.json +++ b/lib/package.json @@ -1,54 +1,54 @@ { - "name": "usegl", - "version": "0.2.0", - "description": "Lightweight hooks library for WebGL", - "repository": "jsulpis/usegl", - "license": "MIT", - "author": "Julien SULPIS", - "sideEffects": false, - "type": "module", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs" - } - }, - "main": "./dist/index.mjs", - "module": "./dist/index.mjs", - "types": "./dist/index.d.ts", - "files": [ - "dist" - ], - "scripts": { - "build": "unbuild", - "dev": "astro dev", - "format": "prettier src --check", - "format:fix": "prettier src --write", - "lint": "eslint src", - "lint:fix": "eslint src --fix", - "prepack": "pnpm build", - "release": "changelogen --release --clean", - "test": "playwright test", - "test:local": "docker build -t usegl . && docker run --rm -v $(pwd)/test-results:/app/test-results usegl /bin/sh -c 'xvfb-run pnpm run test'", - "test:ui": "playwright test --ui", - "test:update": "docker build -t usegl . && docker run --rm -v $(pwd)/test-results:/app/test-results -v $(pwd)/tests/__screenshots__:/app/tests/__screenshots__ usegl", - "typecheck": "tsc --noEmit && astro check" - }, - "devDependencies": { - "@astrojs/check": "0.9.4", - "@playwright/test": "1.48.1", - "@types/node": "22.1.0", - "astro": "4.16.7", - "changelogen": "0.5.5", - "eslint": "9.8.0", - "eslint-config-unjs": "0.3.2", - "prettier": "3.3.3", - "typescript": "5.5.4", - "unbuild": "3.0.0-rc.7" - }, - "changelog": { - "excludeAuthors": [ - "" - ] - } + "name": "usegl", + "version": "0.2.0", + "description": "Lightweight hooks library for WebGL", + "repository": "jsulpis/usegl", + "license": "MIT", + "author": "Julien SULPIS", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "unbuild", + "dev": "astro dev", + "format": "prettier src --check", + "format:fix": "prettier src --write", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "prepack": "pnpm build", + "release": "changelogen --release --clean", + "test": "playwright test", + "test:local": "docker build -t usegl . && docker run --rm -v $(pwd)/test-results:/app/test-results usegl /bin/sh -c 'xvfb-run pnpm run test'", + "test:ui": "playwright test --ui", + "test:update": "docker build -t usegl . && docker run --rm -v $(pwd)/test-results:/app/test-results -v $(pwd)/tests/__screenshots__:/app/tests/__screenshots__ usegl", + "typecheck": "tsc --noEmit && astro check" + }, + "devDependencies": { + "@astrojs/check": "0.9.4", + "@playwright/test": "1.48.1", + "@types/node": "22.1.0", + "astro": "4.16.7", + "changelogen": "0.5.5", + "eslint": "9.8.0", + "eslint-config-unjs": "0.3.2", + "prettier": "3.3.3", + "typescript": "5.5.4", + "unbuild": "3.0.0-rc.7" + }, + "changelog": { + "excludeAuthors": [ + "" + ] + } } diff --git a/lib/playground/src/components/renderCount.ts b/lib/playground/src/components/renderCount.ts index d796286..e983cd3 100644 --- a/lib/playground/src/components/renderCount.ts +++ b/lib/playground/src/components/renderCount.ts @@ -1,4 +1,4 @@ export function incrementRenderCount() { - const renderCountElement = document.querySelector("#renderCount"); - renderCountElement.textContent = `${Number(renderCountElement.textContent) + 1}`; + const renderCountElement = document.querySelector("#renderCount"); + renderCountElement.textContent = `${Number(renderCountElement.textContent) + 1}`; } diff --git a/lib/playground/src/components/routes.ts b/lib/playground/src/components/routes.ts index 8e62052..03bee62 100644 --- a/lib/playground/src/components/routes.ts +++ b/lib/playground/src/components/routes.ts @@ -1,14 +1,14 @@ import { readdirSync } from "node:fs"; export const sections = readdirSync("playground/src/pages", { withFileTypes: true }) - .filter((file) => file.isDirectory()) - .map((folder) => folder.name); + .filter((file) => file.isDirectory()) + .map((folder) => folder.name); export const routes = sections.flatMap((section) => { - const files = readdirSync(`playground/src/pages/${section}`); + const files = readdirSync(`playground/src/pages/${section}`); - return files.map((file) => { - const route = file.replace(".astro", ""); - return { section, route }; - }); + return files.map((file) => { + const route = file.replace(".astro", ""); + return { section, route }; + }); }); diff --git a/lib/playground/src/env.d.ts b/lib/playground/src/env.d.ts index 4e6b85c..36328f2 100644 --- a/lib/playground/src/env.d.ts +++ b/lib/playground/src/env.d.ts @@ -1 +1 @@ -/// \ No newline at end of file +/// diff --git a/lib/playground/tsconfig.json b/lib/playground/tsconfig.json index 5039e89..1a0d7d6 100644 --- a/lib/playground/tsconfig.json +++ b/lib/playground/tsconfig.json @@ -1,9 +1,9 @@ { - "extends": "astro/tsconfigs/base", - "include": ["../src", "src"], - "compilerOptions": { - "paths": { - "usegl": ["../src/index.ts"] - } - } + "extends": "astro/tsconfigs/base", + "include": ["../src", "src"], + "compilerOptions": { + "paths": { + "usegl": ["../src/index.ts"] + } + } } diff --git a/lib/playwright.config.ts b/lib/playwright.config.ts index cfb58cc..55fcac3 100644 --- a/lib/playwright.config.ts +++ b/lib/playwright.config.ts @@ -9,68 +9,68 @@ const serverUrl = "http://localhost:4321"; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./tests", - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 3 : 0, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", - use: { - trace: "on-first-retry", - baseURL: serverUrl, - }, - snapshotPathTemplate: "{testDir}/__screenshots__/{testName}/{testName}-{projectName}{ext}", - expect: { - toHaveScreenshot: { - maxDiffPixelRatio: 0.02, - }, - }, - projects: [ - { - name: "chromium", - use: { - ...devices["Desktop Chrome"], - viewport: desktopViewport, - launchOptions: { - args: ["--use-angle=gl"], - }, - }, - }, - { - name: "firefox", - use: { - ...devices["Desktop Firefox"], - viewport: desktopViewport, - launchOptions: { - headless: false, - }, - }, - grepInvert: /play \/ pause controls/, // Flaky on firefox, and there is no fancy API in the play/pause controls, so the other browsers are enough - }, - { - name: "safari", - use: { ...devices["Desktop Safari"], viewport: desktopViewport }, - }, - { - name: "android", - use: { - ...devices["Pixel 5"], - viewport: mobileViewport, - launchOptions: { - args: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"], - }, - }, - }, - { - name: "iphone", - use: { ...devices["iPhone 12"], viewport: mobileViewport }, - }, - ], - webServer: { - command: "pnpm dev", - url: serverUrl, - reuseExistingServer: !process.env.CI, - }, + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 3 : 0, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + use: { + trace: "on-first-retry", + baseURL: serverUrl, + }, + snapshotPathTemplate: "{testDir}/__screenshots__/{testName}/{testName}-{projectName}{ext}", + expect: { + toHaveScreenshot: { + maxDiffPixelRatio: 0.02, + }, + }, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + viewport: desktopViewport, + launchOptions: { + args: ["--use-angle=gl"], + }, + }, + }, + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + viewport: desktopViewport, + launchOptions: { + headless: false, + }, + }, + grepInvert: /play \/ pause controls/, // Flaky on firefox, and there is no fancy API in the play/pause controls, so the other browsers are enough + }, + { + name: "safari", + use: { ...devices["Desktop Safari"], viewport: desktopViewport }, + }, + { + name: "android", + use: { + ...devices["Pixel 5"], + viewport: mobileViewport, + launchOptions: { + args: ["--use-gl=egl", "--ignore-gpu-blocklist", "--use-gl=angle"], + }, + }, + }, + { + name: "iphone", + use: { ...devices["iPhone 12"], viewport: mobileViewport }, + }, + ], + webServer: { + command: "pnpm dev", + url: serverUrl, + reuseExistingServer: !process.env.CI, + }, }); diff --git a/lib/src/core/attribute.ts b/lib/src/core/attribute.ts index 8340af3..ac91317 100644 --- a/lib/src/core/attribute.ts +++ b/lib/src/core/attribute.ts @@ -2,59 +2,59 @@ import type { Attribute } from "../types"; import { createAndBindBuffer, isSharedBufferSource } from "./buffer"; export function setAttribute( - gl: WebGL2RenderingContext, - program: WebGLProgram, - name: string, - attribute: Attribute, + gl: WebGL2RenderingContext, + program: WebGLProgram, + name: string, + attribute: Attribute, ) { - const bufferData = getBufferData(attribute.data, name === "index"); - const location = gl.getAttribLocation(program, name); - - if (name === "index") { - createAndBindBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, bufferData); - } - - if (location === -1) { - return { location, vertexCount: 0 }; - } - - createAndBindBuffer(gl, gl.ARRAY_BUFFER, bufferData); - - gl.enableVertexAttribArray(location); - gl.vertexAttribPointer( - location, - attribute.size, - attribute.type || getGLType(gl, bufferData), - attribute.normalize || false, - attribute.stride || 0, - attribute.offset || 0, - ); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - const vertexCount = attribute.stride - ? bufferData.byteLength / attribute.stride - : bufferData.length / attribute.size; - - return { location, vertexCount }; + const bufferData = getBufferData(attribute.data, name === "index"); + const location = gl.getAttribLocation(program, name); + + if (name === "index") { + createAndBindBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, bufferData); + } + + if (location === -1) { + return { location, vertexCount: 0 }; + } + + createAndBindBuffer(gl, gl.ARRAY_BUFFER, bufferData); + + gl.enableVertexAttribArray(location); + gl.vertexAttribPointer( + location, + attribute.size, + attribute.type || getGLType(gl, bufferData), + attribute.normalize || false, + attribute.stride || 0, + attribute.offset || 0, + ); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + const vertexCount = attribute.stride + ? bufferData.byteLength / attribute.stride + : bufferData.length / attribute.size; + + return { location, vertexCount }; } function getBufferData(data: Attribute["data"], isIndex: boolean) { - if (isSharedBufferSource(data)) { - return data; - } - if (isIndex) { - return data.length < 65_536 ? new Uint16Array(data) : new Uint32Array(data); - } - return new Float32Array(data); + if (isSharedBufferSource(data)) { + return data; + } + if (isIndex) { + return data.length < 65_536 ? new Uint16Array(data) : new Uint32Array(data); + } + return new Float32Array(data); } function getGLType(gl: WebGL2RenderingContext, data: ArrayBufferView) { - if (data instanceof Float32Array) return gl.FLOAT; - if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) return gl.UNSIGNED_BYTE; - if (data instanceof Int8Array) return gl.BYTE; - if (data instanceof Uint16Array) return gl.UNSIGNED_SHORT; - if (data instanceof Int16Array) return gl.SHORT; - if (data instanceof Uint32Array) return gl.UNSIGNED_INT; - if (data instanceof Int32Array) return gl.INT; - return gl.FLOAT; + if (data instanceof Float32Array) return gl.FLOAT; + if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) return gl.UNSIGNED_BYTE; + if (data instanceof Int8Array) return gl.BYTE; + if (data instanceof Uint16Array) return gl.UNSIGNED_SHORT; + if (data instanceof Int16Array) return gl.SHORT; + if (data instanceof Uint32Array) return gl.UNSIGNED_INT; + if (data instanceof Int32Array) return gl.INT; + return gl.FLOAT; } diff --git a/lib/src/core/buffer.ts b/lib/src/core/buffer.ts index 3b339f5..634114d 100644 --- a/lib/src/core/buffer.ts +++ b/lib/src/core/buffer.ts @@ -1,16 +1,16 @@ export function createAndBindBuffer( - gl: WebGLRenderingContext, - target: GLenum, - data: AllowSharedBufferSource | number[], + gl: WebGLRenderingContext, + target: GLenum, + data: AllowSharedBufferSource | number[], ) { - const buffer = gl.createBuffer(); - const bufferData = isSharedBufferSource(data) ? data : new Float32Array(data); - gl.bindBuffer(target, buffer); - gl.bufferData(target, bufferData, gl.STATIC_DRAW); + const buffer = gl.createBuffer(); + const bufferData = isSharedBufferSource(data) ? data : new Float32Array(data); + gl.bindBuffer(target, buffer); + gl.bufferData(target, bufferData, gl.STATIC_DRAW); - return buffer; + return buffer; } export function isSharedBufferSource(value: unknown): value is AllowSharedBufferSource { - return value instanceof ArrayBuffer || ArrayBuffer.isView(value); + return value instanceof ArrayBuffer || ArrayBuffer.isView(value); } diff --git a/lib/src/core/program.ts b/lib/src/core/program.ts index e68c732..ed6cfa1 100644 --- a/lib/src/core/program.ts +++ b/lib/src/core/program.ts @@ -1,29 +1,29 @@ import { createShader } from "./shader"; export function createProgram( - gl: WebGL2RenderingContext, - fragment: string | WebGLShader, - vertex: string | WebGLShader, + gl: WebGL2RenderingContext, + fragment: string | WebGLShader, + vertex: string | WebGLShader, ) { - const vertexShader = - vertex instanceof WebGLShader ? vertex : createShader(gl, vertex, gl.VERTEX_SHADER); - const fragmentShader = - fragment instanceof WebGLShader ? fragment : createShader(gl, fragment, gl.FRAGMENT_SHADER); + const vertexShader = + vertex instanceof WebGLShader ? vertex : createShader(gl, vertex, gl.VERTEX_SHADER); + const fragmentShader = + fragment instanceof WebGLShader ? fragment : createShader(gl, fragment, gl.FRAGMENT_SHADER); - const program = gl.createProgram(); - if (program === null || vertexShader == null || fragmentShader == null) { - console.error("could not create program"); - gl.deleteProgram(program); - return null; - } - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); + const program = gl.createProgram(); + if (program === null || vertexShader == null || fragmentShader == null) { + console.error("could not create program"); + gl.deleteProgram(program); + return null; + } + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - console.error("could not link program: " + gl.getProgramInfoLog(program)); - gl.deleteProgram(program); - } + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + console.error("could not link program: " + gl.getProgramInfoLog(program)); + gl.deleteProgram(program); + } - return program; + return program; } diff --git a/lib/src/core/renderTarget.ts b/lib/src/core/renderTarget.ts index 6d36221..deed687 100644 --- a/lib/src/core/renderTarget.ts +++ b/lib/src/core/renderTarget.ts @@ -2,63 +2,63 @@ import type { RenderTarget } from "../types"; import { createTexture } from "./texture"; export function createRenderTarget( - gl: WebGL2RenderingContext, - size?: { width: number; height: number }, + gl: WebGL2RenderingContext, + size?: { width: number; height: number }, ): RenderTarget { - let _width = size?.width ?? gl.canvas.width; - let _height = size?.height ?? gl.canvas.height; + let _width = size?.width ?? gl.canvas.width; + let _height = size?.height ?? gl.canvas.height; - const framebuffer = gl.createFramebuffer(); + const framebuffer = gl.createFramebuffer(); - let _texture = createTexture(gl, { - data: null, - width: _width, - height: _height, - generateMipmaps: false, - }); + let _texture = createTexture(gl, { + data: null, + width: _width, + height: _height, + generateMipmaps: false, + }); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, _texture, 0); - gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, _texture, 0); + gl.bindTexture(gl.TEXTURE_2D, null); - function setSize(width: number, height: number) { - _width = width; - _height = height; + function setSize(width: number, height: number) { + _width = width; + _height = height; - const newTexture = createTexture(gl, { data: null, width, height, generateMipmaps: false }); + const newTexture = createTexture(gl, { data: null, width, height, generateMipmaps: false }); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.bindTexture(gl.TEXTURE_2D, newTexture); - gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, newTexture, 0); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.bindTexture(gl.TEXTURE_2D, newTexture); + gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, newTexture, 0); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.bindTexture(gl.TEXTURE_2D, null); - gl.deleteTexture(_texture); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.deleteTexture(_texture); - _texture = newTexture; - } + _texture = newTexture; + } - return { - framebuffer, - get texture() { - return _texture; - }, - get width() { - return _width; - }, - get height() { - return _height; - }, - setSize, - }; + return { + framebuffer, + get texture() { + return _texture; + }, + get width() { + return _width; + }, + get height() { + return _height; + }, + setSize, + }; } export function setRenderTarget(gl: WebGL2RenderingContext, target: RenderTarget | null) { - const framebuffer = target?.framebuffer || null; - const { width, height } = target || gl.canvas; + const framebuffer = target?.framebuffer || null; + const { width, height } = target || gl.canvas; - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.viewport(0, 0, width, height); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 0, width, height); } diff --git a/lib/src/core/shader.ts b/lib/src/core/shader.ts index a4055c4..01a7888 100644 --- a/lib/src/core/shader.ts +++ b/lib/src/core/shader.ts @@ -1,42 +1,42 @@ export function createShader(gl: WebGL2RenderingContext, source: string, type: GLenum) { - const shader = gl.createShader(type); - if (shader == null) { - console.error("could not create shader"); - gl.deleteShader(shader); - return null; - } - gl.shaderSource(shader, convertToGLSL300(source)); - gl.compileShader(shader); + const shader = gl.createShader(type); + if (shader == null) { + console.error("could not create shader"); + gl.deleteShader(shader); + return null; + } + gl.shaderSource(shader, convertToGLSL300(source)); + gl.compileShader(shader); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - console.error("could not compile shader: " + gl.getShaderInfoLog(shader)); - gl.deleteShader(shader); - } - return shader; + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.error("could not compile shader: " + gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + } + return shader; } function convertToGLSL300(shader: string): string { - let glsl300Shader = shader.replace(/\battribute\b/g, "in").replace(/\btexture2D\b/g, "texture"); + let glsl300Shader = shader.replace(/\battribute\b/g, "in").replace(/\btexture2D\b/g, "texture"); - if (shader.includes("gl_FragColor")) { - // Fragment shader - glsl300Shader = "out vec4 fragColor;\n" + glsl300Shader; - glsl300Shader = glsl300Shader - .replace(/\bvarying\b/g, "in") - .replace(/\bgl_FragColor\b/g, "fragColor"); - } else { - // Vertex shader - glsl300Shader = glsl300Shader.replace(/\bvarying\b/g, "out"); - } + if (shader.includes("gl_FragColor")) { + // Fragment shader + glsl300Shader = "out vec4 fragColor;\n" + glsl300Shader; + glsl300Shader = glsl300Shader + .replace(/\bvarying\b/g, "in") + .replace(/\bgl_FragColor\b/g, "fragColor"); + } else { + // Vertex shader + glsl300Shader = glsl300Shader.replace(/\bvarying\b/g, "out"); + } - const precisionRegex = /precision\s+(highp|mediump|lowp)\s+float\s*;/; - if (!precisionRegex.test(glsl300Shader)) { - glsl300Shader = glsl300Shader.replace(/^(#version 300 es)?/, "$1\nprecision highp float;\n"); - } + const precisionRegex = /precision\s+(highp|mediump|lowp)\s+float\s*;/; + if (!precisionRegex.test(glsl300Shader)) { + glsl300Shader = glsl300Shader.replace(/^(#version 300 es)?/, "$1\nprecision highp float;\n"); + } - if (!shader.startsWith("#version")) { - glsl300Shader = "#version 300 es\n" + glsl300Shader; - } + if (!shader.startsWith("#version")) { + glsl300Shader = "#version 300 es\n" + glsl300Shader; + } - return glsl300Shader; + return glsl300Shader; } diff --git a/lib/src/hooks/useBoundingRect.ts b/lib/src/hooks/useBoundingRect.ts index 9aed9f6..bf23f2b 100644 --- a/lib/src/hooks/useBoundingRect.ts +++ b/lib/src/hooks/useBoundingRect.ts @@ -1,72 +1,72 @@ import { useResizeObserver } from "./useResizeObserver"; export interface UseBoundingRectOptions { - /** - * Listen to window resize event - * - * @default true - */ - windowResize?: boolean; - /** - * Listen to window scroll event - * - * @default true - */ - windowScroll?: boolean; + /** + * Listen to window resize event + * + * @default true + */ + windowResize?: boolean; + /** + * Listen to window scroll event + * + * @default true + */ + windowScroll?: boolean; } export interface BoundingRect { - width: number; - height: number; - top: number; - right: number; - bottom: number; - left: number; - x: number; - y: number; + width: number; + height: number; + top: number; + right: number; + bottom: number; + left: number; + x: number; + y: number; } /** * Dynamically get the bounding rectangle of an HTML element */ export function useBoundingRect(target: HTMLElement, options: UseBoundingRectOptions = {}) { - const { - windowResize = typeof window !== "undefined", - windowScroll = typeof window !== "undefined", - } = options; + const { + windowResize = typeof window !== "undefined", + windowScroll = typeof window !== "undefined", + } = options; - const rect: BoundingRect = { - width: 0, - height: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - x: 0, - y: 0, - }; + const rect: BoundingRect = { + width: 0, + height: 0, + top: 0, + right: 0, + bottom: 0, + left: 0, + x: 0, + y: 0, + }; - const center = { x: 0, y: 0 }; + const center = { x: 0, y: 0 }; - function update() { - const newRect = target.getBoundingClientRect(); + function update() { + const newRect = target.getBoundingClientRect(); - // update the rect object instead of reassagning to allow destructuring the output of the function - for (const key of Object.keys(rect) as Array) { - rect[key] = newRect[key]; - } + // update the rect object instead of reassagning to allow destructuring the output of the function + for (const key of Object.keys(rect) as Array) { + rect[key] = newRect[key]; + } - center.x = (rect.left + rect.right) / 2; - center.y = (rect.top + rect.bottom) / 2; - } + center.x = (rect.left + rect.right) / 2; + center.y = (rect.top + rect.bottom) / 2; + } - useResizeObserver(target, update); + useResizeObserver(target, update); - if (windowScroll) window.addEventListener("scroll", update, { capture: true, passive: true }); - if (windowResize) window.addEventListener("resize", update, { passive: true }); + if (windowScroll) window.addEventListener("scroll", update, { capture: true, passive: true }); + if (windowResize) window.addEventListener("resize", update, { passive: true }); - return { - rect: rect as Readonly, - center: center as Readonly, - }; + return { + rect: rect as Readonly, + center: center as Readonly, + }; } diff --git a/lib/src/hooks/useCompositor.ts b/lib/src/hooks/useCompositor.ts index 346b8a6..15be15f 100644 --- a/lib/src/hooks/useCompositor.ts +++ b/lib/src/hooks/useCompositor.ts @@ -3,42 +3,42 @@ import type { PostEffect, RenderPass, Uniforms } from "../types"; import { findUniformName } from "../internal/findName"; export function useCompositor( - gl: WebGL2RenderingContext, - primaryPass: RenderPass, - effects: PostEffect[], + gl: WebGL2RenderingContext, + primaryPass: RenderPass, + effects: PostEffect[], ) { - if (effects.length > 0 && primaryPass.target === null) { - primaryPass.setTarget(createRenderTarget(gl)); - } - - for (const [index, effect] of effects.entries()) { - effect.initialize(gl); - effect.setTarget(index === effects.length - 1 ? null : createRenderTarget(gl)); - - const textureUniformName = - findUniformName(effect.fragment, "image") || - findUniformName(effect.fragment, "texture") || - findUniformName(effect.fragment, "pass"); - - if (textureUniformName && effect.uniforms[textureUniformName] === undefined) { - effect.uniforms[textureUniformName] = () => - (index > 0 ? effects[index - 1] : primaryPass).target?.texture; - } - } - - const allPasses = [primaryPass, ...effects]; - - function render() { - for (const pass of allPasses) { - pass.render(); - } - } - - function setSize(size: { width: number; height: number }) { - for (const pass of allPasses) { - pass.setSize(size); - } - } - - return { render, setSize, allPasses }; + if (effects.length > 0 && primaryPass.target === null) { + primaryPass.setTarget(createRenderTarget(gl)); + } + + for (const [index, effect] of effects.entries()) { + effect.initialize(gl); + effect.setTarget(index === effects.length - 1 ? null : createRenderTarget(gl)); + + const textureUniformName = + findUniformName(effect.fragment, "image") || + findUniformName(effect.fragment, "texture") || + findUniformName(effect.fragment, "pass"); + + if (textureUniformName && effect.uniforms[textureUniformName] === undefined) { + effect.uniforms[textureUniformName] = () => + (index > 0 ? effects[index - 1] : primaryPass).target?.texture; + } + } + + const allPasses = [primaryPass, ...effects]; + + function render() { + for (const pass of allPasses) { + pass.render(); + } + } + + function setSize(size: { width: number; height: number }) { + for (const pass of allPasses) { + pass.setSize(size); + } + } + + return { render, setSize, allPasses }; } diff --git a/lib/src/hooks/useEffectPass.ts b/lib/src/hooks/useEffectPass.ts index 93e5678..6ceafcc 100644 --- a/lib/src/hooks/useEffectPass.ts +++ b/lib/src/hooks/useEffectPass.ts @@ -3,5 +3,5 @@ import type { QuadPassOptions } from "./useQuadRenderPass"; import { useQuadRenderPass } from "./useQuadRenderPass"; export function useEffectPass(options: QuadPassOptions): RenderPass { - return useQuadRenderPass(undefined, options); + return useQuadRenderPass(undefined, options); } diff --git a/lib/src/hooks/useLoop.ts b/lib/src/hooks/useLoop.ts index 9686453..6385876 100644 --- a/lib/src/hooks/useLoop.ts +++ b/lib/src/hooks/useLoop.ts @@ -1,35 +1,35 @@ interface LoopData { - /** - * time elapsed in milliseconds since the loop started, excluding pauses. - * - * This timer is paused when the loop is paused, to avoid jumps in animations. If you want to get the time elapsed including pauses, use `elapsedTime` instead. - */ - time: number; - /** - * Δt in milliseconds since the previous loop iteration. - */ - deltaTime: number; - /** - * time elapsed in milliseconds since the loop started, including pauses. - * - * This timer is NOT paused when the loop is paused, which can cause jumps in animations. If you want to get the time elapsed excluding pauses, use `time` instead. - */ - elapsedTime: number; + /** + * time elapsed in milliseconds since the loop started, excluding pauses. + * + * This timer is paused when the loop is paused, to avoid jumps in animations. If you want to get the time elapsed including pauses, use `elapsedTime` instead. + */ + time: number; + /** + * Δt in milliseconds since the previous loop iteration. + */ + deltaTime: number; + /** + * time elapsed in milliseconds since the loop started, including pauses. + * + * This timer is NOT paused when the loop is paused, which can cause jumps in animations. If you want to get the time elapsed excluding pauses, use `time` instead. + */ + elapsedTime: number; } export interface UseLoopOptions { - /** - * If true, the loop will start immediately. - * - * If false, the loop will start when the `play` method is called. - * @default true - */ - immediate?: boolean; + /** + * If true, the loop will start immediately. + * + * If false, the loop will start when the `play` method is called. + * @default true + */ + immediate?: boolean; } interface LoopObj { - play: () => void; - pause: () => void; + play: () => void; + pause: () => void; } const allLoops: Array = []; @@ -41,78 +41,78 @@ const allLoops: Array = []; * @returns An object with `play` and `pause` methods to control the animation loop. */ export function useLoop( - callback: ({ time, deltaTime }: LoopData) => void, - options?: UseLoopOptions, + callback: ({ time, deltaTime }: LoopData) => void, + options?: UseLoopOptions, ) { - let animationFrameHandle: number; - let pauseTime: number | null; - let loopStartTime: number; - let delay = 0; + let animationFrameHandle: number; + let pauseTime: number | null; + let loopStartTime: number; + let delay = 0; - const { immediate = true } = options || {}; + const { immediate = true } = options || {}; - function loopFn(previousTime: number, delay = 0) { - const currentTime = performance.now(); - const elapsedTime = currentTime - loopStartTime; - const time = elapsedTime - delay; - const deltaTime = currentTime - previousTime; - callback({ time, elapsedTime, deltaTime }); + function loopFn(previousTime: number, delay = 0) { + const currentTime = performance.now(); + const elapsedTime = currentTime - loopStartTime; + const time = elapsedTime - delay; + const deltaTime = currentTime - previousTime; + callback({ time, elapsedTime, deltaTime }); - animationFrameHandle = requestAnimationFrame(() => loopFn(currentTime, delay)); - } + animationFrameHandle = requestAnimationFrame(() => loopFn(currentTime, delay)); + } - function play() { - const currentTime = performance.now(); - if (loopStartTime === undefined) { - loopStartTime = performance.now(); - } - delay += currentTime - (pauseTime || currentTime); - cancelAnimationFrame(animationFrameHandle); - animationFrameHandle = requestAnimationFrame(() => loopFn(currentTime, delay)); - pauseTime = null; - } + function play() { + const currentTime = performance.now(); + if (loopStartTime === undefined) { + loopStartTime = performance.now(); + } + delay += currentTime - (pauseTime || currentTime); + cancelAnimationFrame(animationFrameHandle); + animationFrameHandle = requestAnimationFrame(() => loopFn(currentTime, delay)); + pauseTime = null; + } - function pause() { - if (pauseTime == null) { - pauseTime = performance.now(); - } - cancelAnimationFrame(animationFrameHandle); - } + function pause() { + if (pauseTime == null) { + pauseTime = performance.now(); + } + cancelAnimationFrame(animationFrameHandle); + } - if (immediate) { - play(); - } + if (immediate) { + play(); + } - const loop = { - /** - * Play the animation loop. - */ - play, - /** - * Pause the animation loop. - */ - pause, - }; + const loop = { + /** + * Play the animation loop. + */ + play, + /** + * Pause the animation loop. + */ + pause, + }; - allLoops.push(loop); + allLoops.push(loop); - return loop; + return loop; } /** * Play all loops that have been registered with `useLoop`. */ export function playAllLoops() { - for (const loop of allLoops) { - loop.play(); - } + for (const loop of allLoops) { + loop.play(); + } } /** * Pause all loops that have been registered with `useLoop`. */ export function pauseAllLoops() { - for (const loop of allLoops) { - loop.pause(); - } + for (const loop of allLoops) { + loop.pause(); + } } diff --git a/lib/src/hooks/usePointerEvents.ts b/lib/src/hooks/usePointerEvents.ts index b09fd25..ee50c14 100644 --- a/lib/src/hooks/usePointerEvents.ts +++ b/lib/src/hooks/usePointerEvents.ts @@ -2,61 +2,61 @@ import type { BoundingRect } from "./useBoundingRect"; import { useBoundingRect } from "./useBoundingRect"; type HandlerArgs = { - pointer: { - x: number; - y: number; - }; - canvasRect: BoundingRect; - canvasCenter: { - x: number; - y: number; - }; + pointer: { + x: number; + y: number; + }; + canvasRect: BoundingRect; + canvasCenter: { + x: number; + y: number; + }; }; type PointerEventsHandlers = { - enter?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; - move?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; - leave?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; - down?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; - up?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; + enter?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; + move?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; + leave?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; + down?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; + up?: ({ pointer, canvasRect, canvasCenter }: HandlerArgs) => void; }; /** * Listen to common pointer events and provide additional infos about the canvas */ export function usePointerEvents(canvas: HTMLCanvasElement, handlers: PointerEventsHandlers) { - const { rect: canvasRect, center: canvasCenter } = useBoundingRect(canvas); - - const activeHandlers = Object.fromEntries( - Object.entries(handlers) - .filter(([, handler]) => typeof handler === "function") - .map(([handlerName, handlerFunction]) => [ - handlerName, - (e: PointerEvent) => { - handlerFunction({ - pointer: { x: e.clientX, y: e.clientY }, - canvasRect, - canvasCenter, - }); - }, - ]), - ); - - function listen() { - for (const [event, handler] of Object.entries(activeHandlers)) { - canvas.addEventListener(`pointer${event as keyof PointerEventsHandlers}`, handler, { - passive: true, - }); - } - } - - function stop() { - for (const [event, handler] of Object.entries(activeHandlers)) { - canvas.removeEventListener(`pointer${event as keyof PointerEventsHandlers}`, handler); - } - } - - listen(); - - return { stop, listen }; + const { rect: canvasRect, center: canvasCenter } = useBoundingRect(canvas); + + const activeHandlers = Object.fromEntries( + Object.entries(handlers) + .filter(([, handler]) => typeof handler === "function") + .map(([handlerName, handlerFunction]) => [ + handlerName, + (e: PointerEvent) => { + handlerFunction({ + pointer: { x: e.clientX, y: e.clientY }, + canvasRect, + canvasCenter, + }); + }, + ]), + ); + + function listen() { + for (const [event, handler] of Object.entries(activeHandlers)) { + canvas.addEventListener(`pointer${event as keyof PointerEventsHandlers}`, handler, { + passive: true, + }); + } + } + + function stop() { + for (const [event, handler] of Object.entries(activeHandlers)) { + canvas.removeEventListener(`pointer${event as keyof PointerEventsHandlers}`, handler); + } + } + + listen(); + + return { stop, listen }; } diff --git a/lib/src/hooks/useQuadRenderPass.ts b/lib/src/hooks/useQuadRenderPass.ts index 853b077..c7dcd48 100644 --- a/lib/src/hooks/useQuadRenderPass.ts +++ b/lib/src/hooks/useQuadRenderPass.ts @@ -3,41 +3,41 @@ import { useRenderPass, type RenderPassOptions } from "./useRenderPass"; import { findAttributeName, findVaryingName } from "../internal/findName"; export type QuadPassOptions> = Omit< - RenderPassOptions, - "vertex" + RenderPassOptions, + "vertex" > & { vertex?: string }; export function useQuadRenderPass( - gl: WebGL2RenderingContext | undefined, - { attributes = {}, fragment, vertex, ...renderPassOptions }: QuadPassOptions, + gl: WebGL2RenderingContext | undefined, + { attributes = {}, fragment, vertex, ...renderPassOptions }: QuadPassOptions, ) { - const uvVaryingName = findVaryingName(fragment, "uv"); - - const vertexShader = - vertex || - (uvVaryingName - ? quadVertexShaderSource.replace(/\bvUv\b/g, uvVaryingName) - : quadVertexShaderSource); - - const hasPositionAttribute = Object.keys(attributes).some((attributeName) => - attributeName.toLocaleLowerCase().endsWith("position"), - ); - - if (!hasPositionAttribute) { - const positionAttributeName = findAttributeName(vertex, "position") || "aPosition"; - - attributes[positionAttributeName] = { - size: 2, - data: quadVertexPositions, - }; - } - - return useRenderPass(gl, { - ...renderPassOptions, - attributes, - fragment, - vertex: vertexShader, - }); + const uvVaryingName = findVaryingName(fragment, "uv"); + + const vertexShader = + vertex || + (uvVaryingName + ? quadVertexShaderSource.replace(/\bvUv\b/g, uvVaryingName) + : quadVertexShaderSource); + + const hasPositionAttribute = Object.keys(attributes).some((attributeName) => + attributeName.toLocaleLowerCase().endsWith("position"), + ); + + if (!hasPositionAttribute) { + const positionAttributeName = findAttributeName(vertex, "position") || "aPosition"; + + attributes[positionAttributeName] = { + size: 2, + data: quadVertexPositions, + }; + } + + return useRenderPass(gl, { + ...renderPassOptions, + attributes, + fragment, + vertex: vertexShader, + }); } const quadVertexShaderSource = /*glsl*/ `#version 300 es diff --git a/lib/src/hooks/useRenderPass.ts b/lib/src/hooks/useRenderPass.ts index 55f372f..7a496d3 100644 --- a/lib/src/hooks/useRenderPass.ts +++ b/lib/src/hooks/useRenderPass.ts @@ -1,10 +1,10 @@ import type { - Attribute, - DrawMode, - RenderCallback, - RenderPass, - RenderTarget, - Uniforms, + Attribute, + DrawMode, + RenderCallback, + RenderPass, + RenderTarget, + Uniforms, } from "../types"; import { createProgram } from "../core/program"; import { setRenderTarget } from "../core/renderTarget"; @@ -14,131 +14,131 @@ import { useAttributes } from "../internal/useAttributes"; import { useLifeCycleCallback } from "../internal/useLifeCycleCallback"; export type RenderPassOptions> = { - target?: RenderTarget | null; - fragment: string; - vertex: string; - attributes?: Record; - uniforms?: U; - drawMode?: DrawMode; + target?: RenderTarget | null; + fragment: string; + vertex: string; + attributes?: Record; + uniforms?: U; + drawMode?: DrawMode; }; export function useRenderPass( - gl: WebGL2RenderingContext | undefined, - { - target = null, - fragment, - vertex, - attributes = {}, - uniforms: userUniforms = {} as U, - drawMode: userDrawMode, - }: RenderPassOptions, + gl: WebGL2RenderingContext | undefined, + { + target = null, + fragment, + vertex, + attributes = {}, + uniforms: userUniforms = {} as U, + drawMode: userDrawMode, + }: RenderPassOptions, ): RenderPass { - /** - * INIT - */ - - let _target = target; - let _program: WebGLProgram; - let _gl: WebGL2RenderingContext; - - const { - initialize: initializeUniforms, - onUpdated, - setUniforms, - getUniformsSnapshot, - uniformsProxy, - } = useUniforms(userUniforms); - const { - initialize: initializeAttributes, - getVertexCount, - bindVAO, - hasIndices, - indexType, - } = useAttributes(attributes); - - function initialize(gl: WebGL2RenderingContext) { - _gl = gl; - const program = createProgram(_gl, fragment, vertex); - if (program == null) { - throw new Error("could not initialize the render pass"); - } - _program = program; - _gl.useProgram(_program); - - initializeUniforms(_gl, _program); - initializeAttributes(_gl, _program); - } - - if (gl) { - initialize(gl); - } - - /** - * UPDATE - */ - - const resolutionUniformName = findUniformName(fragment + vertex, "resolution"); - - function setSize(size: { width: number; height: number }) { - if (resolutionUniformName && userUniforms[resolutionUniformName] === undefined) { - (uniformsProxy as Record)[resolutionUniformName] = [size.width, size.height]; - } - if (_target != null) { - _target.setSize(size.width, size.height); - } - } - - function setTarget(target: RenderTarget | null) { - _target = target; - } - - /** - * RENDER - */ - - const drawMode = userDrawMode || (vertex.includes("gl_PointSize") ? "POINTS" : "TRIANGLES"); - - const [beforeRenderCallbacks, onBeforeRender] = useLifeCycleCallback>(); - const [afterRenderCallbacks, onAfterRender] = useLifeCycleCallback>(); - - function render() { - if (_gl == undefined) { - throw new Error("The render pass must be initialized before calling the render function"); - } - for (const callback of beforeRenderCallbacks) { - callback({ uniforms: getUniformsSnapshot() }); - } - - setRenderTarget(_gl, _target); - _gl.useProgram(_program); - - bindVAO(); - setUniforms(); - - if (hasIndices) { - _gl.drawElements(_gl[drawMode], getVertexCount(), indexType, 0); - } else { - _gl.drawArrays(_gl[drawMode], 0, getVertexCount()); - } - - for (const callback of afterRenderCallbacks) { - callback({ uniforms: getUniformsSnapshot() }); - } - } - - return { - render, - initialize, - setTarget, - get target() { - return _target; - }, - setSize, - uniforms: uniformsProxy, - vertex, - fragment, - onUpdated, - onBeforeRender, - onAfterRender, - }; + /** + * INIT + */ + + let _target = target; + let _program: WebGLProgram; + let _gl: WebGL2RenderingContext; + + const { + initialize: initializeUniforms, + onUpdated, + setUniforms, + getUniformsSnapshot, + uniformsProxy, + } = useUniforms(userUniforms); + const { + initialize: initializeAttributes, + getVertexCount, + bindVAO, + hasIndices, + indexType, + } = useAttributes(attributes); + + function initialize(gl: WebGL2RenderingContext) { + _gl = gl; + const program = createProgram(_gl, fragment, vertex); + if (program == null) { + throw new Error("could not initialize the render pass"); + } + _program = program; + _gl.useProgram(_program); + + initializeUniforms(_gl, _program); + initializeAttributes(_gl, _program); + } + + if (gl) { + initialize(gl); + } + + /** + * UPDATE + */ + + const resolutionUniformName = findUniformName(fragment + vertex, "resolution"); + + function setSize(size: { width: number; height: number }) { + if (resolutionUniformName && userUniforms[resolutionUniformName] === undefined) { + (uniformsProxy as Record)[resolutionUniformName] = [size.width, size.height]; + } + if (_target != null) { + _target.setSize(size.width, size.height); + } + } + + function setTarget(target: RenderTarget | null) { + _target = target; + } + + /** + * RENDER + */ + + const drawMode = userDrawMode || (vertex.includes("gl_PointSize") ? "POINTS" : "TRIANGLES"); + + const [beforeRenderCallbacks, onBeforeRender] = useLifeCycleCallback>(); + const [afterRenderCallbacks, onAfterRender] = useLifeCycleCallback>(); + + function render() { + if (_gl == undefined) { + throw new Error("The render pass must be initialized before calling the render function"); + } + for (const callback of beforeRenderCallbacks) { + callback({ uniforms: getUniformsSnapshot() }); + } + + setRenderTarget(_gl, _target); + _gl.useProgram(_program); + + bindVAO(); + setUniforms(); + + if (hasIndices) { + _gl.drawElements(_gl[drawMode], getVertexCount(), indexType, 0); + } else { + _gl.drawArrays(_gl[drawMode], 0, getVertexCount()); + } + + for (const callback of afterRenderCallbacks) { + callback({ uniforms: getUniformsSnapshot() }); + } + } + + return { + render, + initialize, + setTarget, + get target() { + return _target; + }, + setSize, + uniforms: uniformsProxy, + vertex, + fragment, + onUpdated, + onBeforeRender, + onAfterRender, + }; } diff --git a/lib/src/hooks/useResizeObserver.ts b/lib/src/hooks/useResizeObserver.ts index 3375d8d..8ca8fc8 100644 --- a/lib/src/hooks/useResizeObserver.ts +++ b/lib/src/hooks/useResizeObserver.ts @@ -2,54 +2,54 @@ * Listen for changes to the size of an element. */ export function useResizeObserver( - target: HTMLElement, - callback: (args: { - /** - * size of the observed element in CSS pixels - */ - size: { width: number; height: number }; - /** - * size of the observed element in device pixels - */ - devicePixelSize: { width: number; height: number }; - /** - * untouched, native observer entries - */ - entries: ResizeObserverEntry[]; - }) => void, + target: HTMLElement, + callback: (args: { + /** + * size of the observed element in CSS pixels + */ + size: { width: number; height: number }; + /** + * size of the observed element in device pixels + */ + devicePixelSize: { width: number; height: number }; + /** + * untouched, native observer entries + */ + entries: ResizeObserverEntry[]; + }) => void, ) { - let size: ResizeObserverSize; - let devicePixelSize: ResizeObserverSize; + let size: ResizeObserverSize; + let devicePixelSize: ResizeObserverSize; - const observer = new ResizeObserver((entries) => { - const entry = entries.find((entry) => entry.target === target)!; + const observer = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === target)!; - size = entry.contentBoxSize[0]; - devicePixelSize = entry.devicePixelContentBoxSize?.[0] || { - blockSize: Math.round(size.blockSize * window.devicePixelRatio), - inlineSize: Math.round(size.inlineSize * window.devicePixelRatio), - }; + size = entry.contentBoxSize[0]; + devicePixelSize = entry.devicePixelContentBoxSize?.[0] || { + blockSize: Math.round(size.blockSize * window.devicePixelRatio), + inlineSize: Math.round(size.inlineSize * window.devicePixelRatio), + }; - // call the callback after the next paint, otherwise there are glitches when resizing a canvas - // with an active render loop - setTimeout(() => { - callback({ - size: { width: size.inlineSize, height: size.blockSize }, - devicePixelSize: { width: devicePixelSize.inlineSize, height: devicePixelSize.blockSize }, - entries, - }); - }, 0); - }); + // call the callback after the next paint, otherwise there are glitches when resizing a canvas + // with an active render loop + setTimeout(() => { + callback({ + size: { width: size.inlineSize, height: size.blockSize }, + devicePixelSize: { width: devicePixelSize.inlineSize, height: devicePixelSize.blockSize }, + entries, + }); + }, 0); + }); - observer.observe(target); + observer.observe(target); - return { - disconnect: observer.disconnect, - observe: () => { - observer.observe(target); - }, - unobserve: () => { - observer.unobserve(target); - }, - }; + return { + disconnect: observer.disconnect, + observe: () => { + observer.observe(target); + }, + unobserve: () => { + observer.unobserve(target); + }, + }; } diff --git a/lib/src/hooks/useWebGLContext.ts b/lib/src/hooks/useWebGLContext.ts index 885f6e6..626b607 100644 --- a/lib/src/hooks/useWebGLContext.ts +++ b/lib/src/hooks/useWebGLContext.ts @@ -1,24 +1,24 @@ export function useWebGLContext( - canvas: HTMLCanvasElement | OffscreenCanvas | string, - options?: WebGLContextAttributes, + canvas: HTMLCanvasElement | OffscreenCanvas | string, + options?: WebGLContextAttributes, ) { - const canvasElement = - typeof canvas === "string" ? document.querySelector(canvas) : canvas; + const canvasElement = + typeof canvas === "string" ? document.querySelector(canvas) : canvas; - if (canvasElement == null) { - throw new Error("Canvas element not found."); - } + if (canvasElement == null) { + throw new Error("Canvas element not found."); + } - const gl = canvasElement.getContext("webgl2", options) as WebGL2RenderingContext; - if (!gl) { - throw new Error("No WebGL2 context available."); - } + const gl = canvasElement.getContext("webgl2", options) as WebGL2RenderingContext; + if (!gl) { + throw new Error("No WebGL2 context available."); + } - function setSize(width: number, height: number) { - canvasElement!.width = width; - canvasElement!.height = height; - gl.viewport(0, 0, width, height); - } + function setSize(width: number, height: number) { + canvasElement!.width = width; + canvasElement!.height = height; + gl.viewport(0, 0, width, height); + } - return { canvas: canvasElement, gl, setSize }; + return { canvas: canvasElement, gl, setSize }; } diff --git a/lib/src/internal/findName.ts b/lib/src/internal/findName.ts index 59b7a1a..566ba24 100644 --- a/lib/src/internal/findName.ts +++ b/lib/src/internal/findName.ts @@ -1,18 +1,18 @@ function findName(source: string | undefined, keyword: string, word: string) { - return source - ?.split("\n") - .find((line) => new RegExp(`^${keyword}.*${word};`, "i").test(line.trim())) - ?.match(/(\w+);$/)?.[1]; + return source + ?.split("\n") + .find((line) => new RegExp(`^${keyword}.*${word};`, "i").test(line.trim())) + ?.match(/(\w+);$/)?.[1]; } export function findUniformName(source: string | undefined, word: string) { - return findName(source, "uniform", word); + return findName(source, "uniform", word); } export function findVaryingName(source: string | undefined, word: string) { - return findName(source, "varying", word) || findName(source, "in", word); + return findName(source, "varying", word) || findName(source, "in", word); } export function findAttributeName(source: string | undefined, word: string) { - return findName(source, "attribute", word) || findName(source, "in", word); + return findName(source, "attribute", word) || findName(source, "in", word); } diff --git a/lib/src/internal/useAttributes.ts b/lib/src/internal/useAttributes.ts index 9036359..a97490c 100644 --- a/lib/src/internal/useAttributes.ts +++ b/lib/src/internal/useAttributes.ts @@ -5,38 +5,38 @@ const UNSIGNED_INT = WebGL2RenderingContext.UNSIGNED_INT; const UNSIGNED_SHORT = WebGL2RenderingContext.UNSIGNED_SHORT; export function useAttributes(attributes: Record) { - let _gl: WebGL2RenderingContext; - let _vao: WebGLVertexArrayObject | null; - - let vertexCount = 0; - - function initialize(gl: WebGL2RenderingContext, program: WebGLProgram) { - _gl = gl; - _vao = _gl.createVertexArray(); - _gl.bindVertexArray(_vao); - - for (const [attributeName, attributeObj] of Object.entries(attributes)) { - const attr = setAttribute(_gl, program, attributeName, attributeObj); - vertexCount = Math.max(vertexCount, attr.vertexCount); - } - } - - const hasIndices = attributes.index != undefined; - const indexType = attributes.index?.data.length < Math.pow(2, 16) ? UNSIGNED_SHORT : UNSIGNED_INT; - - function getVertexCount() { - return vertexCount; - } - - function bindVAO() { - _gl.bindVertexArray(_vao); - } - - return { - initialize, - getVertexCount, - bindVAO, - hasIndices, - indexType, - }; + let _gl: WebGL2RenderingContext; + let _vao: WebGLVertexArrayObject | null; + + let vertexCount = 0; + + function initialize(gl: WebGL2RenderingContext, program: WebGLProgram) { + _gl = gl; + _vao = _gl.createVertexArray(); + _gl.bindVertexArray(_vao); + + for (const [attributeName, attributeObj] of Object.entries(attributes)) { + const attr = setAttribute(_gl, program, attributeName, attributeObj); + vertexCount = Math.max(vertexCount, attr.vertexCount); + } + } + + const hasIndices = attributes.index != undefined; + const indexType = attributes.index?.data.length < Math.pow(2, 16) ? UNSIGNED_SHORT : UNSIGNED_INT; + + function getVertexCount() { + return vertexCount; + } + + function bindVAO() { + _gl.bindVertexArray(_vao); + } + + return { + initialize, + getVertexCount, + bindVAO, + hasIndices, + indexType, + }; } diff --git a/lib/src/internal/useLifeCycleCallback.ts b/lib/src/internal/useLifeCycleCallback.ts index 2059569..124e3d8 100644 --- a/lib/src/internal/useLifeCycleCallback.ts +++ b/lib/src/internal/useLifeCycleCallback.ts @@ -1,9 +1,9 @@ export function useLifeCycleCallback() { - const callbacks: C[] = []; + const callbacks: C[] = []; - function addCallback(callback: C) { - callbacks.push(callback); - } + function addCallback(callback: C) { + callbacks.push(callback); + } - return [callbacks, addCallback] as const; + return [callbacks, addCallback] as const; } diff --git a/lib/src/internal/useUniforms.ts b/lib/src/internal/useUniforms.ts index ad6b0fe..9d343f9 100644 --- a/lib/src/internal/useUniforms.ts +++ b/lib/src/internal/useUniforms.ts @@ -5,109 +5,109 @@ import { useLifeCycleCallback } from "./useLifeCycleCallback"; let textureUnitIndex = 0; export function useUniforms(uniforms: U) { - type UniformName = Extract; - - let _gl: WebGL2RenderingContext; - let _program: WebGLProgram; - - const [onUpdatedCallbacks, onUpdated] = useLifeCycleCallback>(); - - const uniformsLocations = new Map(); - - function initialize(gl: WebGL2RenderingContext, program: WebGLProgram) { - _gl = gl; - _program = program; - - const uniformsCount = _gl.getProgramParameter(_program, _gl.ACTIVE_UNIFORMS); - for (let i = 0; i < uniformsCount; i++) { - const uniformName = _gl.getActiveUniform(_program, i)?.name as UniformName; - uniformsLocations.set(uniformName, _gl.getUniformLocation(_program, uniformName) ?? -1); - } - } - - const uniformsProxy = new Proxy( - { ...uniforms }, - { - set(target, uniform: string, value) { - if (value !== target[uniform]) { - const oldTarget = getSnapshot(target); - target[uniform as keyof U] = value; - const newTarget = getSnapshot(target); - for (const callback of onUpdatedCallbacks) callback(newTarget, oldTarget); - } - return true; - }, - }, - ); - - const textureUnits = new Map(); - - function setUniforms() { - for (const [uniformName, uniformValue] of Object.entries(uniformsProxy)) { - setUniform( - uniformName as UniformName, - (typeof uniformValue === "function" ? uniformValue() : uniformValue) as U[UniformName], - ); - } - } - - function setUniform(name: Uname, value: U[Uname] & UniformValue) { - const uniformLocation = uniformsLocations.get(name) || -1; - if (uniformLocation === -1) return -1; - - if (typeof value === "number") return _gl.uniform1f(uniformLocation, value); - - if (value instanceof WebGLTexture) { - if (!textureUnits.has(name)) { - textureUnits.set(name, { index: textureUnitIndex++ }); - } - const { index } = textureUnits.get(name)!; - _gl.activeTexture(_gl.TEXTURE0 + index); - _gl.bindTexture(_gl.TEXTURE_2D, value); - - return _gl.uniform1i(uniformLocation, index); - } - - if (Array.isArray(value)) { - switch (value.length) { - case 2: { - return _gl.uniform2fv(uniformLocation, value); - } - case 3: { - return _gl.uniform3fv(uniformLocation, value); - } - case 4: { - return _gl.uniform4fv(uniformLocation, value); - } - } - } - - if (value.src || value.data) { - if (!textureUnits.has(name)) { - const texture = createTexture(_gl, value); - textureUnits.set(name, { index: textureUnitIndex++, texture }); - } - const { index, texture } = textureUnits.get(name)!; - _gl.activeTexture(_gl.TEXTURE0 + index); - _gl.bindTexture(_gl.TEXTURE_2D, texture!); - - return _gl.uniform1i(uniformLocation, index); - } - } - - function getUniformsSnapshot() { - return getSnapshot({ ...uniformsProxy }); - } - - return { - initialize, - uniformsProxy, - onUpdated, - setUniforms, - getUniformsSnapshot, - }; + type UniformName = Extract; + + let _gl: WebGL2RenderingContext; + let _program: WebGLProgram; + + const [onUpdatedCallbacks, onUpdated] = useLifeCycleCallback>(); + + const uniformsLocations = new Map(); + + function initialize(gl: WebGL2RenderingContext, program: WebGLProgram) { + _gl = gl; + _program = program; + + const uniformsCount = _gl.getProgramParameter(_program, _gl.ACTIVE_UNIFORMS); + for (let i = 0; i < uniformsCount; i++) { + const uniformName = _gl.getActiveUniform(_program, i)?.name as UniformName; + uniformsLocations.set(uniformName, _gl.getUniformLocation(_program, uniformName) ?? -1); + } + } + + const uniformsProxy = new Proxy( + { ...uniforms }, + { + set(target, uniform: string, value) { + if (value !== target[uniform]) { + const oldTarget = getSnapshot(target); + target[uniform as keyof U] = value; + const newTarget = getSnapshot(target); + for (const callback of onUpdatedCallbacks) callback(newTarget, oldTarget); + } + return true; + }, + }, + ); + + const textureUnits = new Map(); + + function setUniforms() { + for (const [uniformName, uniformValue] of Object.entries(uniformsProxy)) { + setUniform( + uniformName as UniformName, + (typeof uniformValue === "function" ? uniformValue() : uniformValue) as U[UniformName], + ); + } + } + + function setUniform(name: Uname, value: U[Uname] & UniformValue) { + const uniformLocation = uniformsLocations.get(name) || -1; + if (uniformLocation === -1) return -1; + + if (typeof value === "number") return _gl.uniform1f(uniformLocation, value); + + if (value instanceof WebGLTexture) { + if (!textureUnits.has(name)) { + textureUnits.set(name, { index: textureUnitIndex++ }); + } + const { index } = textureUnits.get(name)!; + _gl.activeTexture(_gl.TEXTURE0 + index); + _gl.bindTexture(_gl.TEXTURE_2D, value); + + return _gl.uniform1i(uniformLocation, index); + } + + if (Array.isArray(value)) { + switch (value.length) { + case 2: { + return _gl.uniform2fv(uniformLocation, value); + } + case 3: { + return _gl.uniform3fv(uniformLocation, value); + } + case 4: { + return _gl.uniform4fv(uniformLocation, value); + } + } + } + + if (value.src || value.data) { + if (!textureUnits.has(name)) { + const texture = createTexture(_gl, value); + textureUnits.set(name, { index: textureUnitIndex++, texture }); + } + const { index, texture } = textureUnits.get(name)!; + _gl.activeTexture(_gl.TEXTURE0 + index); + _gl.bindTexture(_gl.TEXTURE_2D, texture!); + + return _gl.uniform1i(uniformLocation, index); + } + } + + function getUniformsSnapshot() { + return getSnapshot({ ...uniformsProxy }); + } + + return { + initialize, + uniformsProxy, + onUpdated, + setUniforms, + getUniformsSnapshot, + }; } function getSnapshot>(object: Obj): Obj { - return Object.freeze({ ...object }); + return Object.freeze({ ...object }); } diff --git a/lib/src/types.ts b/lib/src/types.ts index d4ccb64..cd0b195 100644 --- a/lib/src/types.ts +++ b/lib/src/types.ts @@ -1,9 +1,9 @@ import type { TextureOptions } from "./core/texture"; export type VectorUniform = - | [number, number] - | [number, number, number] - | [number, number, number, number]; + | [number, number] + | [number, number, number] + | [number, number, number, number]; export type TextureUniform = TextureOptions | WebGLTexture; export type UniformValue = number | VectorUniform | TextureUniform; export type Uniforms = Record UniformValue)>; @@ -11,33 +11,33 @@ export type Uniforms = Record UniformValue)>; type TypedArray = ArrayBufferView & { length: number }; export interface Attribute { - size: 1 | 2 | 3 | 4; - data: TypedArray | number[]; - type?: GLenum; - normalize?: boolean; - stride?: number; - offset?: number; + size: 1 | 2 | 3 | 4; + data: TypedArray | number[]; + type?: GLenum; + normalize?: boolean; + stride?: number; + offset?: number; } export interface RenderTarget { - framebuffer: WebGLFramebuffer | null; - texture: WebGLTexture | null; - width: number; - height: number; - setSize: (width: number, height: number) => void; + framebuffer: WebGLFramebuffer | null; + texture: WebGLTexture | null; + width: number; + height: number; + setSize: (width: number, height: number) => void; } export interface RenderPass> extends Resizable { - render: () => void; - target: RenderTarget | null; - setTarget: (target: RenderTarget | null) => void; - uniforms: U; - vertex: string; - fragment: string; - onUpdated: (callback: UpdatedCallback) => void; - onBeforeRender: (callback: RenderCallback) => void; - onAfterRender: (callback: RenderCallback) => void; - initialize: (gl: WebGL2RenderingContext) => void; + render: () => void; + target: RenderTarget | null; + setTarget: (target: RenderTarget | null) => void; + uniforms: U; + vertex: string; + fragment: string; + onUpdated: (callback: UpdatedCallback) => void; + onBeforeRender: (callback: RenderCallback) => void; + onAfterRender: (callback: RenderCallback) => void; + initialize: (gl: WebGL2RenderingContext) => void; } export type CompositeEffect = RenderPass[]; @@ -45,21 +45,21 @@ export type CompositeEffect = RenderPass[]; export type PostEffect = RenderPass; export type DrawMode = - | "POINTS" - | "LINES" - | "LINE_STRIP" - | "LINE_LOOP" - | "TRIANGLES" - | "TRIANGLE_STRIP" - | "TRIANGLE_FAN"; + | "POINTS" + | "LINES" + | "LINE_STRIP" + | "LINE_LOOP" + | "TRIANGLES" + | "TRIANGLE_STRIP" + | "TRIANGLE_FAN"; export interface Resizable { - setSize: ({ width, height }: { width: number; height: number }) => void; + setSize: ({ width, height }: { width: number; height: number }) => void; } export type RenderCallback = (args: Readonly<{ uniforms: U }>) => void; export type UpdatedCallback = ( - uniforms: Readonly, - oldUniforms: Readonly, + uniforms: Readonly, + oldUniforms: Readonly, ) => void; diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 61a4892..d1cdfd7 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -1,12 +1,12 @@ { - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Bundler", - "esModuleInterop": true, - "isolatedModules": true, - "strict": true, - "skipLibCheck": true - }, - "include": ["src", "playwright.config.ts"] + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "isolatedModules": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src", "playwright.config.ts"] }