New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

fragment-shader

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fragment-shader - npm Package Compare versions

Comparing version 0.0.3 to 0.1.0

css/codemirror.css

207

classes/Editor.ts

@@ -1,3 +0,2 @@

import { EditorConfig } from './../types/editor';
import Shader from './Shader';
import Shader, { type UniformValue } from './Shader';
import { EditorView, basicSetup } from 'codemirror';

@@ -9,10 +8,27 @@ import { EditorViewConfig, ViewUpdate, keymap } from '@codemirror/view';

import { StreamLanguage } from '@codemirror/language';
import { createStyleSheet } from '../util/dom';
import { formatShadertoySource } from '../util/shadertoy';
import { oneDark } from '@codemirror/theme-one-dark';
import { BLOCKS, KEYWORDS, MATH, TYPES, RAW_UTILS } from '../constants/glsl';
import type { Uniform } from '../types/shader';
import { DEFAULT_FRAGMENT_SHADER, DEFAULT_UNIFORMS } from '../constants/shader';
import '../util/glslx';
import styles from '../css/codemirror.css?inline';
const buildAtoms = (uniforms: Uniform[]) => ({
...uniforms.reduce((acc: any, key: any) => {
acc[key] = true;
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[]) => ({
...uniforms.reduce((acc: any, uniform: any) => {
acc[uniform[0]] = true;
return acc;

@@ -40,11 +56,68 @@ }, {}),

uniforms: DEFAULT_UNIFORMS,
debug: true,
onError: () => {},
onSuccess: () => {},
onUpdate: () => {},
width: window.innerWidth,
height: window.innerHeight,
dpr: window.devicePixelRatio,
fillViewport: true,
};
const errorStylesheet = createStyleSheet();
let errorStyleTimeout = 0;
function setErrorStyle({
message,
line,
}: {
message: string;
line: string;
}): void {
clearTimeout(errorStyleTimeout);
errorStyleTimeout = setTimeout(() => {
errorStylesheet.textContent = /*css*/ `
.cm-line:nth-child(${line}) {
position: relative;
outline: 1px solid var(--red);
}
.cm-gutterElement:nth-child(${line + 1}) {
background: var(--red);
transition: all var(--hover-duration) var(--easing);
outline: 1px solid var(--red);
}
.cm-line:nth-child(${line}):after {
transition: all var(--hover-duration) var(--easing);
content: "${message}";
position: absolute;
top: 0;
left: 100%;
background: var(--red);
color: var(--white);
z-index: 100;
padding: 0 .5rem;
outline: 1px solid var(--red);
}
`;
}, 500);
}
function hideError() {
clearTimeout(errorStyleTimeout);
errorStylesheet.textContent = '';
}
export default class Editor {
private editorView: EditorView;
private editorView: EditorView | undefined;
private shader: Shader;
private uniforms: Uniform[];
private uniforms: UniformValue[];
private config: EditorConfig;
private _onUpdate: Function | undefined;
private container: HTMLElement;
constructor(config: EditorConfig) {
constructor(config: EditorConfig = DEFAULT_CONFIG) {
this.config = {

@@ -55,3 +128,3 @@ ...DEFAULT_CONFIG,

const { target, shader, uniforms } = this.config;
const { target, shader, uniforms }: any = this.config;

@@ -61,18 +134,81 @@ if (!target || !shader || !uniforms)

this.shader = new Shader({ target, shader, uniforms, debug: true });
this.uniforms = [...uniforms] as Uniform[];
this.editorView = this.createEditorView({
target,
createStyleSheet(styles);
this.container = document.createElement('section');
this.container.classList.add('editor');
this.sizeContainer();
target?.appendChild(this.container);
this.shader = new Shader({
target: this.container,
shader,
uniforms,
size: {
width: '100vw',
height: '100vh',
},
debug: this.config.debug,
onError: this.onError.bind(this),
onSuccess: this.onSuccess.bind(this),
width: this.config.width,
height: this.config.height,
dpr: this.config.dpr,
fillViewport: this.config.fillViewport,
});
this._onUpdate = this.config.onUpdate;
this.uniforms = [...uniforms] as UniformValue[];
this.createEditorView();
this.onPaste = this.onPaste.bind(this);
this.sizeContainer = this.sizeContainer.bind(this);
window.addEventListener('paste', this.onPaste);
window.addEventListener('resize', this.sizeContainer);
}
sizeContainer() {
const width = this.config.fillViewport
? window.innerWidth
: this.config.width;
const height = this.config.fillViewport
? window.innerHeight
: this.config.height;
this.container.style.position = 'relative';
this.container.style.width = width + 'px';
this.container.style.height = height + 'px';
this.container.style.overflow = 'hidden';
}
onError({ line, message }: any) {
this.config?.onError?.({ line, message });
setErrorStyle({ line, message });
}
onSuccess() {
this.config?.onSuccess?.();
hideError();
}
onPaste({ clipboardData }: ClipboardEvent) {
if (clipboardData === null) return;
const string = clipboardData.getData('text');
const isShaderToy = string.indexOf('mainImage') !== -1;
if (isShaderToy) {
this.config.shader = formatShadertoySource(string);
this.shader.rebuild({
shader: this.config.shader,
uniforms: this.uniforms,
});
this.createEditorView();
}
}
createState(
shader: string = DEFAULT_FRAGMENT_SHADER,
uniforms: Uniform[any][] = DEFAULT_UNIFORMS
uniforms: UniformValue[any][] = DEFAULT_UNIFORMS
): EditorState {

@@ -91,19 +227,7 @@ return EditorState.create({

createEditorView({
target,
shader,
uniforms,
}: {
target: HTMLElement;
shader: string;
uniforms: Uniform[];
size: {
width: string;
height: string;
};
}): EditorView {
createEditorView(): void {
this.editorView?.destroy?.();
return new EditorView({
parent: target,
state: this.createState(shader, uniforms as any),
this.editorView = new EditorView({
parent: this.container,
state: this.createState(this.config.shader, this.config.uniforms),
} as EditorViewConfig);

@@ -115,7 +239,12 @@ }

if (!docChanged) return;
this.shader.rebuild({
shader: state.doc.toString(),
uniforms: this.uniforms,
});
const shader = state.doc.toString();
this._onUpdate?.(shader);
hideError();
this.shader.rebuild({ shader, uniforms: this.uniforms });
}
destroy() {
window.removeEventListener('paste', this.onPaste);
this.editorView?.destroy();
}
}

122

classes/Shader.ts

@@ -6,3 +6,2 @@ import Uniform from './Uniform';

import { GLSL_UTILS } from '../constants/glsl';
import { ShaderConfig, ShaderState } from '../types/shader';
import { HasResolution } from '../types/dimensions';

@@ -20,2 +19,22 @@ import {

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 = {

@@ -31,2 +50,4 @@ target: document.body,

debug: false,
onError: () => {},
onSuccess: () => {},
};

@@ -70,2 +91,8 @@

if (this.config.fillViewport) {
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
}
sizeCanvas(this.canvas, this.config as HasResolution);

@@ -125,42 +152,27 @@

get vertexShader(): string {
return `
${DEFAULT_DEFS}
${DEFAULT_UNIFORM_DECLARATIONS}
${this.uniformDeclarations}
${GLSL_UTILS}
${DEFAULT_VERTEX_SHADER}
`;
return `${DEFAULT_DEFS}\n${DEFAULT_UNIFORM_DECLARATIONS}\n${this.uniformDeclarations}\n${GLSL_UTILS}\n${DEFAULT_VERTEX_SHADER}`;
}
get fragmentShader(): string {
return `
${DEFAULT_DEFS}
${DEFAULT_UNIFORM_DECLARATIONS}
${this.uniformDeclarations}
${GLSL_UTILS}
${this.config.shader}
`;
return `${DEFAULT_DEFS}\n${DEFAULT_UNIFORM_DECLARATIONS}\n${this.uniformDeclarations}\n${GLSL_UTILS}\n${this.config.shader}`;
}
set uniforms(uniforms: Uniform[]) {
set uniforms(uniforms: UniformValue[]) {
this.config.uniforms = uniforms;
}
set size({
width,
height,
dpr = window.devicePixelRatio,
}: {
width: number;
height: number;
dpr: number;
}) {
set size(resolution: HasResolution) {
const {
width = window.innerWidth,
height = window.innerHeight,
dpr = window.devicePixelRatio,
} = resolution;
this.config.width = width;
this.config.height = height;
this.config.dpr = dpr;
sizeCanvas(this.canvas, this.config as HasResolution);
this.ctx?.viewport(0, 0, this.canvas.width, this.canvas.height);
}
buildUniforms(): Uniform[] {
buildUniforms(): UniformValue[] {
const uniforms = [...INTERNAL_UNIFORMS, ...(this.config.uniforms || [])];

@@ -212,6 +224,13 @@

this.ctx.attachShader(this.program, this.shaders[type]);
this.config?.onSuccess?.();
if (type === this.ctx.FRAGMENT_SHADER) this.config?.onSuccess?.();
} else {
const hiddenLines = this.fragmentShader
.split(this.config.shader || '')[0]
.split('\n').length;
const error = this.ctx.getShaderInfoLog(this.shaders[type]);
this.config?.onError?.(error);
const data = error?.split(':').map(v => v.trim()) || [];
const line = parseInt(data[2], 10) + 1 - hiddenLines;
const message = data[4].split('\n')[0];
this.config?.onError?.({ line, message });
}

@@ -233,2 +252,3 @@

sizeCanvas(this.canvas, this.config as HasResolution);
this.ctx?.viewport(0, 0, this.canvas.width, this.canvas.height);
}

@@ -264,31 +284,31 @@

this.ctx?.deleteShader(this.shaders?.[this.ctx?.FRAGMENT_SHADER]);
this.ctx?.deleteProgram(this.program);
this.program = this.ctx?.createProgram() as any;
this.compileShader(this.ctx?.VERTEX_SHADER, this.vertexShader);
this.compileShader(this.ctx?.FRAGMENT_SHADER, this.fragmentShader);
this.ctx?.linkProgram(this.program as any);
this.ctx?.useProgram(this.program);
this.ctx?.viewport(0, 0, this.canvas.width, this.canvas.height);
this._uniforms = this.buildUniforms();
this.plane = new Plane(this.ctx);
this.ctx?.enableVertexAttribArray(0);
this.ctx?.vertexAttribPointer(
this.ctx?.getAttribLocation(this.program as any, 'position'),
2,
this.ctx?.FLOAT,
false,
0,
0
);
} catch (e) {
console.log(e);
// console.log(e);
}
this.program = this.ctx?.createProgram() as any;
this.compileShader(this.ctx?.VERTEX_SHADER, this.vertexShader);
this.compileShader(this.ctx?.FRAGMENT_SHADER, this.fragmentShader);
this.ctx?.linkProgram(this.program as any);
this.ctx?.useProgram(this.program);
this.ctx?.viewport(0, 0, this.canvas.width, this.canvas.height);
this._uniforms = this.buildUniforms();
this.plane = new Plane(this.ctx);
this.ctx?.enableVertexAttribArray(0);
this.ctx?.vertexAttribPointer(
this.ctx?.getAttribLocation(this.program as any, 'position'),
2,
this.ctx?.FLOAT,
false,
0,
0
);
}
tick(now: DOMHighResTimeStamp): void {
const time = now;
const time = now / 1000;
this._uniforms?.resolution?.set([this.canvas.width, this.canvas.height]);
this._uniforms?.time?.set(time / 1000);
this._uniforms?.stream?.set(this.stream ?? time / 1000);
this._uniforms?.time?.set(time);
this._uniforms?.stream?.set(time);
this._uniforms?.volume?.set(this.volume);

@@ -295,0 +315,0 @@ this.config?.uniforms?.forEach(uniform => {

@@ -1,2 +0,2 @@

export const hue = /*glsl*/ `
export const k_hue = /*glsl*/ `
vec4 k_hue(vec4 color, float shift) {

@@ -18,3 +18,3 @@ float I = dot(color, vec4(0.596, -0.275, -0.321, 0.0));

export const kale = /*glsl*/ `
export const k_kale = /*glsl*/ `
vec2 k_kale(vec2 uv, vec2 offset, float sides) {

@@ -32,3 +32,3 @@ float angle = atan(uv.y, uv.x);

export const orb = /*glsl*/ `
export const k_orb = /*glsl*/ `
vec4 k_orb(vec2 uv, float size, vec2 position, vec3 color, float contrast) {

@@ -39,3 +39,3 @@ return pow(vec4(size / length(uv + position) * color, 1.), vec4(contrast));

export const rainbow = /*glsl*/ `
export const k_rainbow = /*glsl*/ `
vec3 k_rainbow(float progress, float stretch, float offset) {

@@ -46,3 +46,3 @@ return vec3(cos(vec3(-2, 0, -1) * 3.14159265359 * 2. / 3. + (2. * 3.14159265359) * (progress * stretch) + offset) * 0.5 + 0.5);

export const rotate2d = /* glsl */ `
export const k_rotate2d = /* glsl */ `
mat2 k_rotate2d(float angle) {

@@ -53,3 +53,3 @@ return mat2(cos(angle), -sin(angle), sin(angle), cos(angle));

export const swap = /* glsl */ `
export const k_swap = /* glsl */ `
vec2 k_swap(vec2 uv, vec2 uv2, bool val, bool valTween, float valTweenProgress) {

@@ -63,8 +63,8 @@ return valTween

export const RAW_UTILS = {
hue,
kale,
orb,
rainbow,
rotate2d,
swap,
k_hue,
k_kale,
k_orb,
k_rainbow,
k_rotate2d,
k_swap,
} as any;

@@ -71,0 +71,0 @@

@@ -11,3 +11,10 @@ export const DEFAULT_VERTEX_SHADER = /*glsl*/ `

void main () {
gl_FragColor = vec4(pink, 1.);
vec2 uv = -1. + 2. * gl_FragCoord.xy / resolution.xy;
uv.x *= resolution.x / resolution.y;
float r = sin(uv.x + time);
float g = cos(uv.y + time);
float b = sin(uv.y - time);
gl_FragColor = vec4(r, g, b, 1.);
}

@@ -14,0 +21,0 @@ `;

@@ -1,2 +0,3 @@

export { default as Shader } from './classes/Shader';
export { default as Editor } from './classes/Editor';
export { default as Shader, type ShaderConfig } from './classes/Shader';
export { default as Editor, type EditorConfig } from './classes/Editor';
export * from './types/dimensions';
{
"name": "fragment-shader",
"version": "0.0.3",
"description": "A lightweight, performant WebGL fragment shader renderer.",
"version": "0.1.0",
"description": "A lightweight, performant WebGL fragment shader renderer + editor. ",
"main": "index.js",

@@ -15,2 +15,3 @@ "scripts": {

"WebGL",
"fragment",
"shader",

@@ -32,3 +33,4 @@ "renderer"

"@codemirror/view": "^6.12.0",
"codemirror": "^6.0.1"
"codemirror": "^6.0.1",
"glslx": "^0.3.0"
},

@@ -35,0 +37,0 @@ "devDependencies": {

# **fragment-shader**
> A lightweight & highly performant WebGL fragment shader renderer written in TypeScript.
> • `/classes/Shader.ts` – A lightweight & highly performant WebGL fragment shader renderer written in TypeScript.
- Smaller than `3kb`.
- Appx. `3kb` (gzipped).
- Phenomal performance characteristics, both in rendering speed and in memory consumption.
- Extremely minimal taxing of the garbage collector.
- Certified jank-free – if your experience differs, please let me know!
- Zero-configuration instantiation (see `Hello World` section for details on all default behaviors.
- Zero-configuration instantiation (see `Shader.ts` section for details on all default behaviors).
> • `/classes/Editor.ts` – A live, in-browser GLSL code editor implemented with Codemirror, synced with an instance of `Shader.ts`.
- Appx. `165kb` (gzipped).
- Live rendering! Shader re-renders on every keystroke.
- Smart syntax highlighting and bracket matching.
- Realtime autocomplete surfaces GLSL keywords, uniform names & more as you write your shader.
- Shader compilation errors surface in the editor on relevant lines.
- `ShaderToy` support! Paste your favorite shaders into the editor (work in progress).
## **Installation** _( NPM )_

@@ -19,8 +28,12 @@

## **Hello World** _( Default / Bare Bones Implementation )_
To begin let's look at the core renderer, found in `/classes/Shader.ts`.
## **Shader.ts**
---
> **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 (shader language) syntax highlighting within template literals by prefacing them with `/*glsl*/` – doesn't seem to work on GitHub though.
### **Bare Bones Implementation**
```javascript

@@ -48,10 +61,10 @@ import { Shader } from 'fragment-shader';

## **Configured Implementation**
### **Configured Implementation**
---
If you wish for the renderer to behave differently than its default configuration, you can do so by passing the constructor a configuration object.
If you wish for the renderer to behave differently than its default configuration, you can do so by passing the constructor a configuration object. The object's shape (and its default values) look like this:
```typescript
import { Shader } from 'fragment-shader';
import { Shader, type ShaderConfig } from 'fragment-shader';

@@ -66,9 +79,9 @@ const config: ShaderConfig = {

uniforms: [],
width: 800,
height: 600,
width: window.innerWidth,
height: window.innerHeight,
dpr: window.devicePixelRatio,
fillViewport: false,
fillViewport: true,
onSuccess: () => {},
onError: () => {},
animate: false,
animate: true,
debug: false,

@@ -83,3 +96,3 @@ };

```javascript
import { Shader } from 'fragment-shader';
import { Shader, type ShaderConfig } from 'fragment-shader';

@@ -98,13 +111,15 @@ const config: ShaderConfig = { ... }

```javascript
import { Shader } from 'fragment-shader';
import { Shader, type ShaderConfig } from 'fragment-shader';
const shader = new Shader(
/*glsl*/ `
void main () {
gl_FragColor = vec4(.8, .2, .7, 1.);
}
`,
{ animate: false }
);
const config: ShaderConfig = {
shader: /*glsl*/ `
void main () {
gl_FragColor = vec4(.8, .2, .7, 1.);
}
`,
animate: false,
};
const shader = new Shader(config);
const tick = (now: DOMHighResTimeStamp) => {

@@ -117,1 +132,23 @@ requestAnimationFrame(tick);

```
Now that we can easily render shaders in the browser, let's experiment with authoring them too!
---
## **Editor.ts**
Instantiating an `Editor` should feel familiar after working with the `Shader` class:
```javascript
import { Editor } from 'fragment-shader';
const glsl = /*glsl*/ `
void main () {
gl_FragColor = vec4(.8, .2, .7, 1.);
}
`;
const editor = new Editor(glsl);
```
> ( More documentation coming soon! )

@@ -1,2 +0,2 @@

import type { Uniform } from './shader';
import type { UniformValue } from '../classes/Shader';

@@ -6,3 +6,3 @@ export interface EditorConfig {

shader?: string;
uniforms?: Uniform[];
uniforms?: UniformValue[];
}

@@ -24,1 +24,18 @@ import { HasResolution } from './../types/dimensions';

}
export function createStyleSheet(src: string = ''): HTMLStyleElement {
const style: HTMLStyleElement = document.createElement('style');
style.textContent = src;
document.head.appendChild(style);
return style;
}
export function loadExternalScript(uri: string) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = uri;
script.addEventListener('load', resolve);
script.addEventListener('error', reject);
document.body.appendChild(script);
});
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc