fragment-shader
Advanced tools
Comparing version 0.1.3 to 0.1.5
@@ -1,2 +0,2 @@ | ||
import Shader, { type UniformValue } from './Shader'; | ||
import Shader from './Shader'; | ||
import { EditorView, basicSetup } from 'codemirror'; | ||
@@ -13,18 +13,6 @@ import { EditorViewConfig, ViewUpdate, keymap } from '@codemirror/view'; | ||
import { DEFAULT_FRAGMENT_SHADER, DEFAULT_UNIFORMS } from '../constants/shader'; | ||
import { type UniformValue } from '../types/shader'; | ||
import { type EditorConfig } from '../types/editor'; | ||
import '../css/editor.css'; | ||
export interface EditorConfig { | ||
target?: HTMLElement; | ||
shader?: string; | ||
uniforms?: UniformValue[]; | ||
debug?: boolean; | ||
onError?: Function; | ||
onSuccess?: Function; | ||
onUpdate?: Function; | ||
width?: number; | ||
height?: number; | ||
dpr?: number; | ||
fillViewport?: boolean; | ||
} | ||
const buildAtoms = (uniforms: UniformValue[]) => ({ | ||
@@ -41,3 +29,3 @@ ...uniforms.reduce((acc: any, uniform: any) => { | ||
const defineLanguageDetails = (uniforms: any) => | ||
const defineLanguageDetails = (uniforms: UniformValue[]) => | ||
clike({ | ||
@@ -56,3 +44,3 @@ name: 'FragmentShader', | ||
uniforms: DEFAULT_UNIFORMS, | ||
debug: true, | ||
showErrors: true, | ||
onError: () => {}, | ||
@@ -65,2 +53,3 @@ onSuccess: () => {}, | ||
fillViewport: true, | ||
showLineNumbers: true, | ||
}; | ||
@@ -123,7 +112,18 @@ | ||
constructor(config: EditorConfig = DEFAULT_CONFIG) { | ||
this.config = { | ||
...DEFAULT_CONFIG, | ||
...config, | ||
}; | ||
constructor( | ||
configOrShader: EditorConfig | string = DEFAULT_CONFIG, | ||
config: EditorConfig = DEFAULT_CONFIG | ||
) { | ||
if (typeof configOrShader === 'string') { | ||
this.config = { | ||
...DEFAULT_CONFIG, | ||
...config, | ||
shader: configOrShader, | ||
}; | ||
} else { | ||
this.config = { | ||
...DEFAULT_CONFIG, | ||
...configOrShader, | ||
}; | ||
} | ||
@@ -144,2 +144,4 @@ if ( | ||
this.container.classList.add('editor'); | ||
if (!this.config.showLineNumbers) | ||
this.container.classList.add('no-line-numbers'); | ||
@@ -154,3 +156,3 @@ this.sizeContainer(); | ||
uniforms, | ||
debug: this.config.debug, | ||
debug: this.config.showErrors, | ||
onError: this.onError.bind(this), | ||
@@ -161,3 +163,3 @@ onSuccess: this.onSuccess.bind(this), | ||
dpr: this.config.dpr, | ||
fillViewport: this.config.fillViewport, | ||
fillViewport: false, | ||
}); | ||
@@ -181,6 +183,6 @@ | ||
? window.innerWidth | ||
: this.config.width; | ||
: this.config.width || window.innerWidth; | ||
const height = this.config.fillViewport | ||
? window.innerHeight | ||
: this.config.height; | ||
: this.config.height || window.innerHeight; | ||
this.container.style.position = 'relative'; | ||
@@ -190,2 +192,8 @@ this.container.style.width = width + 'px'; | ||
this.container.style.overflow = 'hidden'; | ||
if (!this.shader) return; | ||
this.shader.size = { | ||
width, | ||
height, | ||
dpr: window.devicePixelRatio, | ||
}; | ||
} | ||
@@ -204,22 +212,47 @@ | ||
onPaste({ clipboardData }: ClipboardEvent) { | ||
if (clipboardData === null) return; | ||
const string = clipboardData.getData('text'); | ||
const string = clipboardData?.getData?.('text') || ''; | ||
const isShaderToy = string.indexOf('mainImage') !== -1; | ||
if (isShaderToy) { | ||
this.config.shader = formatShadertoySource(string); | ||
if (clipboardData === null || !isShaderToy) return; | ||
this.shader.rebuild({ | ||
shader: this.config.shader, | ||
uniforms: this.uniforms, | ||
}); | ||
this.config.shader = formatShadertoySource(string); | ||
this.createEditorView(); | ||
} | ||
this.shader.rebuild({ | ||
shader: this.config.shader, | ||
uniforms: this.uniforms, | ||
}); | ||
this.editorView?.setState( | ||
this.createState(this.config.shader, this.uniforms) | ||
); | ||
} | ||
rebuild(sketch: { shader: string; uniforms?: UniformValue[] }) { | ||
const { shader = '', uniforms = [] } = sketch; | ||
this.config.shader = shader; | ||
this.config.uniforms = uniforms; | ||
this.shader.rebuild({ | ||
shader, | ||
uniforms, | ||
}); | ||
this.editorView?.setState(this.createState(shader, uniforms)); | ||
} | ||
setUniform(key: string, value: any) { | ||
this.shader.setUniform(key, value); | ||
} | ||
start() { | ||
this.shader.start(); | ||
} | ||
stop() { | ||
this.shader.stop(); | ||
} | ||
createState( | ||
shader: string = DEFAULT_FRAGMENT_SHADER, | ||
uniforms: UniformValue[any][] = DEFAULT_UNIFORMS | ||
uniforms: UniformValue[] = DEFAULT_UNIFORMS | ||
): EditorState { | ||
@@ -258,3 +291,5 @@ return EditorState.create({ | ||
this.editorView?.destroy(); | ||
this.shader?.destroy(); | ||
this.container.remove(); | ||
} | ||
} |
@@ -8,2 +8,7 @@ import Uniform from './Uniform'; | ||
import { | ||
type ShaderConfig, | ||
type ShaderState, | ||
type UniformValue, | ||
} from '../types/shader'; | ||
import { | ||
DEFAULT_FRAGMENT_SHADER, | ||
@@ -19,22 +24,2 @@ DEFAULT_UNIFORMS, | ||
export interface ShaderConfig { | ||
target?: HTMLElement; | ||
shader?: string; | ||
uniforms?: any[]; | ||
width?: number; | ||
height?: number; | ||
fillViewport?: boolean; | ||
dpr?: number; | ||
onSuccess?: Function; | ||
onError?: Function; | ||
animate?: boolean; | ||
debug?: boolean; | ||
} | ||
export interface ShaderState { | ||
active: boolean; | ||
} | ||
export type UniformValue = [string, number, any[]]; | ||
const DEFAULT_CONFIG: ShaderConfig = { | ||
@@ -55,3 +40,3 @@ target: document.body, | ||
export default class Shader { | ||
private config: ShaderConfig; | ||
public config: ShaderConfig; | ||
private canvas: HTMLCanvasElement; | ||
@@ -67,2 +52,3 @@ private raf: any; | ||
public volume: number; | ||
private _uniformMap: any; | ||
@@ -125,2 +111,3 @@ constructor( | ||
this.setUniform = this.setUniform.bind(this); | ||
this.raf = raf(this.tick.bind(this)); | ||
@@ -153,7 +140,17 @@ | ||
get vertexShader(): string { | ||
return `${DEFAULT_DEFS}\n${DEFAULT_UNIFORM_DECLARATIONS}\n${this.uniformDeclarations}\n${GLSL_UTILS}\n${DEFAULT_VERTEX_SHADER}`; | ||
return ` | ||
${DEFAULT_DEFS} | ||
${DEFAULT_UNIFORM_DECLARATIONS} | ||
${this.uniformDeclarations} | ||
${GLSL_UTILS} | ||
${DEFAULT_VERTEX_SHADER}`; | ||
} | ||
get fragmentShader(): string { | ||
return `${DEFAULT_DEFS}\n${DEFAULT_UNIFORM_DECLARATIONS}\n${this.uniformDeclarations}\n${GLSL_UTILS}\n${this.config.shader}`; | ||
return ` | ||
${DEFAULT_DEFS} | ||
${DEFAULT_UNIFORM_DECLARATIONS} | ||
${this.uniformDeclarations} | ||
${GLSL_UTILS} | ||
${this.config.shader}`; | ||
} | ||
@@ -178,6 +175,22 @@ | ||
setUniform(key: string, value: any) { | ||
try { | ||
const index = this._uniformMap[key]; | ||
if (this.config.uniforms?.[index][2]) { | ||
this.config.uniforms[index][2] = value; | ||
} | ||
} catch (e) { | ||
console.warn(`Error setting uniform ${key}.`); | ||
} | ||
} | ||
buildUniforms(): UniformValue[] { | ||
const uniforms = [...INTERNAL_UNIFORMS, ...(this.config.uniforms || [])]; | ||
return uniforms.reduce((acc, uniform: any) => { | ||
this._uniformMap = this.config.uniforms?.reduce((acc, [name], i) => { | ||
acc[name] = i; | ||
return acc; | ||
}, {}); | ||
return uniforms.reduce((acc, uniform: any, i) => { | ||
acc[uniform[0]] = new Uniform( | ||
@@ -271,16 +284,3 @@ this.ctx, | ||
try { | ||
this.ctx?.detachShader( | ||
this.program as any, | ||
this.shaders?.[this.ctx?.VERTEX_SHADER] | ||
); | ||
this.ctx?.detachShader( | ||
this.program as any, | ||
this.shaders?.[this.ctx?.FRAGMENT_SHADER] | ||
); | ||
this.ctx?.deleteShader(this.shaders?.[this.ctx?.FRAGMENT_SHADER]); | ||
this.ctx?.deleteShader(this.shaders?.[this.ctx?.VERTEX_SHADER]); | ||
this.ctx?.deleteShader(this.shaders?.[this.ctx?.FRAGMENT_SHADER]); | ||
this.ctx?.deleteProgram(this.program); | ||
this.destroy({ rebuild: true }); | ||
this.program = this.ctx?.createProgram() as any; | ||
@@ -303,2 +303,6 @@ this.compileShader(this.ctx?.VERTEX_SHADER, this.vertexShader); | ||
); | ||
if (this.config.animate) { | ||
this.start(); | ||
} | ||
} catch (e) { | ||
@@ -325,5 +329,28 @@ // console.log(e); | ||
destroy() { | ||
destroy({ rebuild = false } = {}) { | ||
if (this.config.animate) { | ||
this.stop(); | ||
} | ||
this.ctx?.detachShader( | ||
this.program as any, | ||
this.shaders?.[this.ctx?.VERTEX_SHADER] | ||
); | ||
this.ctx?.detachShader( | ||
this.program as any, | ||
this.shaders?.[this.ctx?.FRAGMENT_SHADER] | ||
); | ||
this.ctx?.deleteShader(this.shaders?.[this.ctx?.FRAGMENT_SHADER]); | ||
this.ctx?.deleteShader(this.shaders?.[this.ctx?.VERTEX_SHADER]); | ||
this.ctx?.deleteShader(this.shaders?.[this.ctx?.FRAGMENT_SHADER]); | ||
this.ctx?.deleteProgram(this.program); | ||
if (rebuild) return; | ||
this.canvas.remove(); | ||
window.removeEventListener('resize', this.onWindowResize); | ||
} | ||
} |
@@ -118,2 +118,3 @@ export const k_hue = /*glsl*/ ` | ||
'attribute', | ||
'bool', | ||
'bvec2', | ||
@@ -120,0 +121,0 @@ 'bvec3', |
@@ -51,3 +51,3 @@ export const DEFAULT_VERTEX_SHADER = /*glsl*/ ` | ||
export const DEFAULT_DEFS = [ | ||
'precision lowp float;', | ||
'precision mediump float;', | ||
'#define PI 3.14159265359', | ||
@@ -57,2 +57,3 @@ '#define TWO_PI 2. * PI', | ||
'#define iResolution resolution', | ||
'#define iMouse vec2(0., 0.)', | ||
].join('\n') as any; | ||
@@ -59,0 +60,0 @@ |
@@ -1,3 +0,6 @@ | ||
export { default as Shader, type ShaderConfig } from './classes/Shader'; | ||
export { default as Editor, type EditorConfig } from './classes/Editor'; | ||
export { default as Shader } from './classes/Shader'; | ||
export { default as Editor } from './classes/Editor'; | ||
export * from './types/dimensions'; | ||
export * from './types/editor'; | ||
export * from './types/raf'; | ||
export * from './types/shader'; |
{ | ||
"name": "fragment-shader", | ||
"version": "0.1.3", | ||
"version": "0.1.5", | ||
"description": "A lightweight, performant WebGL fragment shader renderer + editor. ", | ||
@@ -17,3 +17,4 @@ "main": "index.js", | ||
"shader", | ||
"renderer" | ||
"renderer", | ||
"editor" | ||
], | ||
@@ -33,5 +34,3 @@ "author": "Zach Winter", | ||
"@codemirror/view": "^6.12.0", | ||
"codemirror": "^6.0.1", | ||
"fragment-shader": "^0.1.1", | ||
"glslx": "^0.3.0" | ||
"codemirror": "^6.0.1" | ||
}, | ||
@@ -38,0 +37,0 @@ "devDependencies": { |
# **fragment-shader** | ||
This project owns two primary features: a minimalist shader renderer and a live code editor. | ||
> • `/classes/Shader.ts` – A lightweight & highly performant WebGL fragment shader renderer written in TypeScript. | ||
@@ -30,3 +32,3 @@ | ||
> **Note** there are several plugins in modern IDEs (VSCode, etc.) that enable GLSL (shader language) syntax highlighting within template literals by prefacing them with `/*glsl*/` – doesn't seem to work on GitHub though. | ||
> **Note** there are several plugins in modern IDEs (VSCode, etc.) that enable GLSL syntax highlighting within template literals by prefacing them with `/*glsl*/` – doesn't seem to work on GitHub though. | ||
@@ -85,4 +87,6 @@ ### **Bare Bones Implementation** | ||
Or, if you become accustomed to the shader being the first argument of the constructor, you can instantiate this way: | ||
> **Note** If you explicitly set `width` or `height`, the renderer sets `fillViewport` to `false`. | ||
If you become accustomed to the shader being the first argument of the constructor, you can instantiate this way: | ||
```javascript | ||
@@ -100,3 +104,3 @@ import { Shader, type ShaderConfig } from 'fragment-shader'; | ||
> **Note** If you set `animate` to `false`, the shader will render its initial frame, but from thereon out you will be responsible for calling the `tick()` method on the Shader if you wish to update it – for example, within a `requestAnimationFrame` loop. | ||
> **Note** If you set `animate` to `false`, the shader will render its initial frame, but from thereon out you will be responsible for calling the `tick()` method on the Shader if you wish to update it – for example, within a `requestAnimationFrame` loop: | ||
@@ -125,4 +129,50 @@ ```javascript | ||
Now that we can easily render shaders in the browser, let's experiment with authoring them too! | ||
### **Uniforms** | ||
We can pass any number of `Uniform` values to our shaders. Uniforms passed to the `Shader` class are automatically injected into our shaders without having to define them explicitly. The renderer expects an array of uniforms, each of type `UniformValue`. The first index (`[0]`) of a `UniformValue` defines its `name`, the second (`[1]`) defines its `type`, and the third (`[2]`) defines its `value`. | ||
> **Note** Please note that uniforms of type `bool` are unique in that their values aren't contained within an array. | ||
```javascript | ||
import { Shader, type UniformValue } from 'fragment-shader'; | ||
const zoom: UniformValue = ['zoom', 0, [2.5]]; | ||
const color: UniformValue = ['color', 3, [0.8, 0.2, 0.6]]; | ||
const warp: UniformValue = ['warp', 1, false]; | ||
const shader = new Shader({ | ||
shader: /*glsl*/ ` | ||
void main () { | ||
gl_FragColor = vec4(color, 1.); | ||
} | ||
`, | ||
uniforms: [zoom, color, warp], | ||
}); | ||
``` | ||
Types are mapped as follows: | ||
```javascript | ||
const SHADER_TYPE_MAP = { | ||
0: 'float', | ||
1: 'bool', | ||
2: 'vec2', | ||
3: 'vec3', | ||
4: 'vec4', | ||
}; | ||
``` | ||
### **Updating / Cleanup** | ||
```javascript | ||
// Update a uniform value. | ||
shader.setUniform('color', [0.1, 0.6, 0.9]); | ||
// Rebuild with a new shader. | ||
shader.rebuild({ shader, uniforms }); | ||
// Destroy shader instance, elements, and event handlers. | ||
shader.destroy(); | ||
``` | ||
## **Editor.ts** | ||
@@ -133,13 +183,37 @@ | ||
```javascript | ||
import { Editor } from 'fragment-shader'; | ||
import { Editor, type EditorConfig } from 'fragment-shader'; | ||
const glsl = /*glsl*/ ` | ||
void main () { | ||
gl_FragColor = vec4(.8, .2, .7, 1.); | ||
} | ||
`; | ||
const config: EditorConfig = { | ||
shader: /*glsl*/ ` | ||
void main () { | ||
gl_FragColor = vec4(.8, .2, .7, 1.); | ||
} | ||
`, | ||
uniforms: [] | ||
target: document.body, | ||
width: window.innerWidth, | ||
height: window.innerHeight, | ||
dpr: window.devicePixelRatio, | ||
fillViewport: true, | ||
showLineNumbers: true, | ||
showErrors: true, | ||
onError: () => {}, | ||
onSuccess: () => {}, | ||
onUpdate: () => {}, | ||
}; | ||
const editor = new Editor(glsl); | ||
const editor = new Editor(config); | ||
``` | ||
( More documentation coming soon! ) | ||
An `Editor` shares several `Shader` methods: | ||
```javascript | ||
// Update a uniform value. | ||
editor.setUniform('warp', false); | ||
// Rebuild with a new shader. | ||
editor.rebuild({ shader, uniforms }); | ||
// Destroy instance. | ||
editor.destroy(); | ||
``` |
@@ -1,2 +0,2 @@ | ||
import type { UniformValue } from '../classes/Shader'; | ||
import type { UniformValue } from './shader'; | ||
@@ -7,2 +7,11 @@ export interface EditorConfig { | ||
uniforms?: UniformValue[]; | ||
showErrors?: boolean; | ||
onError?: Function; | ||
onSuccess?: Function; | ||
onUpdate?: Function; | ||
width?: number; | ||
height?: number; | ||
dpr?: number; | ||
fillViewport?: boolean; | ||
showLineNumbers?: boolean; | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
7
3
215
0
44300
1270
1
- Removedfragment-shader@^0.1.1
- Removedglslx@^0.3.0
- Removedglslx@0.3.0(transitive)