const simple _vs _src = ` #version 300 es
in vec2 a _pos ;
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
out vec2 v _uv ;
flat out int v _quad _id ;
void main ( ) {
vec2 screen01 = ( a _pos * u _scale + u _translation ) / u _res ;
vec2 screen02 = screen01 * 2.0 ;
screen02 . y = 2.0 - screen02 . y ;
int vertex _index = gl _VertexID % 6 ;
if ( vertex _index == 0 ) {
v _uv = vec2 ( 0.0 , 0.0 ) ;
} else if ( vertex _index == 1 || vertex _index == 5 ) {
v _uv = vec2 ( 1.0 , 0.0 ) ;
} else if ( vertex _index == 2 || vertex _index == 4 ) {
v _uv = vec2 ( 0.0 , 1.0 ) ;
} else {
v _uv = vec2 ( 1.0 , 1.0 ) ;
}
v _quad _id = gl _VertexID / 6 ;
gl _Position = vec4 ( screen02 - 1.0 , 0.0 , 1.0 ) ;
}
` ;
const simple _fs _src = ` #version 300 es
precision highp float ;
in vec2 v _uv ;
flat in int v _quad _id ;
layout ( location = 0 ) out vec4 FragColor ;
void main ( ) {
vec2 pixel = fwidth ( v _uv ) ;
vec2 border = 2.0 * pixel ;
if ( border . x <= v _uv . x && v _uv . x <= 1.0 - border . x && border . y <= v _uv . y && v _uv . y <= 1.0 - border . y ) {
discard ;
} else {
vec3 color = vec3 ( float ( v _quad _id * 869363 % 255 ) / 255.0 , float ( v _quad _id * 278975 % 255 ) / 255.0 , float ( v _quad _id * 587286 % 255 ) / 255.0 ) ;
float alpha = 0.5 ;
FragColor = vec4 ( color * alpha , alpha ) ;
}
}
` ;
const opaque _vs _src = ` #version 300 es
in vec3 a _pos ; // .z is radius
in vec4 a _line ;
in int a _stroke _id ;
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
uniform int u _stroke _count ;
flat out int v _stroke _id ;
void main ( ) {
// Do not inflate quad (as opposed to the full sdf shader), thus only leaving the opaque part
// Shrink to not include the caps
vec2 line _dir = normalize ( a _line . zw - a _line . xy ) ;
int vertex _index = gl _VertexID % 4 ;
vec2 pos = a _pos . xy ;
if ( vertex _index == 0 || vertex _index == 2 ) {
// vertices on the "beginning" side of the line
pos . xy += line _dir * a _pos . z / 2.0 ;
} else {
// on the "ending" side of the line
pos . xy -= line _dir * a _pos . z / 2.0 ;
}
vec2 screen01 = ( pos * u _scale + u _translation ) / u _res ;
vec2 screen02 = screen01 * 2.0 ;
screen02 . y = 2.0 - screen02 . y ;
v _stroke _id = a _stroke _id ;
gl _Position = vec4 ( screen02 - 1.0 , ( float ( a _stroke _id ) / float ( u _stroke _count ) ) * 2.0 - 1.0 , 1.0 ) ;
}
` ;
const nop _fs _src = ` #version 300 es
precision highp float ;
flat in int v _stroke _id ;
layout ( location = 0 ) out vec4 FragColor ;
void main ( ) {
vec3 color = vec3 ( float ( v _stroke _id * 3245 % 255 ) / 255.0 , float ( v _stroke _id * 7343 % 255 ) / 255.0 , float ( v _stroke _id * 5528 % 255 ) / 255.0 ) ;
FragColor = vec4 ( color , 1.0 ) ;
}
` ;
const sdf _vs _src = ` #version 300 es
in vec2 a _a ; // point from
in vec2 a _b ; // point to
in int a _stroke _id ;
in vec2 a _pressure ;
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
uniform int u _stroke _count ;
uniform int u _stroke _texture _size ;
uniform highp usampler2D u _stroke _data ;
out vec4 v _line ;
out vec2 v _texcoord ;
out vec3 v _color ;
flat out vec2 v _thickness ;
void main ( ) {
vec2 screen02 ;
float apron = 1.0 ; // google "futanari inflation rule 34"
int stroke _data _y = a _stroke _id / u _stroke _texture _size ;
int stroke _data _x = a _stroke _id % u _stroke _texture _size ;
uvec4 stroke _data = texelFetch ( u _stroke _data , ivec2 ( stroke _data _x , stroke _data _y ) , 0 ) ;
float radius = float ( stroke _data . w ) ;
vec2 line _dir = normalize ( a _b - a _a ) ;
vec2 up _dir = vec2 ( line _dir . y , - line _dir . x ) ;
vec2 pixel = vec2 ( 2.0 ) / u _res * apron ;
float rscale = apron / u _scale . x ;
int vertex _index = gl _VertexID % 6 ;
vec2 outwards ;
vec2 origin ;
if ( vertex _index == 0 ) {
// "top left" aka "p1"
origin = a _a ;
outwards = up _dir - line _dir ;
} else if ( vertex _index == 1 || vertex _index == 5 ) {
// "top right" aka "p2"
origin = a _b ;
outwards = up _dir + line _dir ;
} else if ( vertex _index == 2 || vertex _index == 4 ) {
// "bottom left" aka "p3"
origin = a _a ;
outwards = - up _dir - line _dir ;
} else {
// "bottom right" aka "p4"
origin = a _b ;
outwards = - up _dir + line _dir ;
}
vec2 pos = origin + normalize ( outwards ) * radius * 2.0 * max ( a _pressure . x , a _pressure . y ) ; // doubling is to account for max possible pressure
screen02 = ( pos . xy * u _scale + u _translation ) / u _res * 2.0 + outwards * pixel ;
v _texcoord = pos . xy + outwards * rscale ;
screen02 . y = 2.0 - screen02 . y ;
v _line = vec4 ( a _a , a _b ) ;
v _thickness = radius * a _pressure ; // pressure 0.5 is the "neutral" pressure
v _color = vec3 ( stroke _data . xyz ) / 255.0 ;
if ( a _stroke _id >> 31 != 0 ) {
screen02 += vec2 ( 100.0 ) ; // shift offscreen
}
gl _Position = vec4 ( screen02 - 1.0 , ( float ( a _stroke _id ) / float ( u _stroke _count ) ) * 2.0 - 1.0 , 1.0 ) ;
}
` ;
const sdf _fs _src = ` #version 300 es
precision highp float ;
uniform int u _debug _mode ;
in vec4 v _line ;
in vec2 v _texcoord ;
in vec3 v _color ;
flat in vec2 v _thickness ;
layout ( location = 0 ) out vec4 FragColor ;
void main ( ) {
if ( u _debug _mode == 0 ) {
vec2 a = v _line . xy ;
vec2 b = v _line . zw ;
vec2 pa = v _texcoord - a . xy , ba = b . xy - a . xy ;
float h = clamp ( dot ( pa , ba ) / dot ( ba , ba ) , 0.0 , 1.0 ) ;
float dist = length ( v _texcoord - ( a + ba * h ) ) - mix ( v _thickness . x , v _thickness . y , h ) ;
float fade = 0.5 * length ( fwidth ( v _texcoord ) ) ;
float alpha = 1.0 - smoothstep ( - fade , fade , dist ) ;
FragColor = vec4 ( v _color * alpha , alpha ) ;
} else {
FragColor = vec4 ( 0.2 , 0.0 , 0.0 , 0.2 ) ;
}
}
` ;
const tquad _vs _src = ` #version 300 es
in vec2 a _pos ;
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
out vec2 v _texcoord ;
void main ( ) {
vec2 screen01 = ( a _pos * u _scale + u _translation ) / u _res ;
vec2 screen02 = screen01 * 2.0 ;
screen02 . y = 2.0 - screen02 . y ;
vec2 screen11 = screen02 - 1.0 ;
int vertex _index = gl _VertexID % 6 ;
if ( vertex _index == 0 ) {
v _texcoord = vec2 ( 0.0 , 0.0 ) ;
} else if ( vertex _index == 1 || vertex _index == 5 ) {
v _texcoord = vec2 ( 1.0 , 0.0 ) ;
} else if ( vertex _index == 2 || vertex _index == 4 ) {
v _texcoord = vec2 ( 0.0 , 1.0 ) ;
} else {
v _texcoord = vec2 ( 1.0 , 1.0 ) ;
}
gl _Position = vec4 ( screen11 , 0 , 1 ) ;
}
` ;
const tquad _fs _src = ` #version 300 es
precision highp float ;
in vec2 v _texcoord ;
uniform sampler2D u _texture ;
layout ( location = 0 ) out vec4 FragColor ;
void main ( ) {
FragColor = texture ( u _texture , v _texcoord ) ;
}
` ;
const grid _vs _src = ` #version 300 es
in vec2 a _data ; // per-instance
out float v _fadeout ;
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
uniform float u _fadeout ;
void main ( ) {
vec2 origin ;
vec2 minor _offset ;
vec2 major _offset ;
vec2 pixel = 2.0 / u _res ;
if ( a _data . x > 0.0 ) {
// Vertical, treat Y as X
float x = a _data . y ;
origin = vec2 ( x , 0.0 ) ;
minor _offset = pixel * vec2 ( 1.0 , 0.0 ) ;
major _offset = vec2 ( 0.0 , 2.0 ) ;
} else {
// Horizontal, treat Y as Y
float y = a _data . y ;
origin = vec2 ( 0.0 , y ) ;
minor _offset = pixel * vec2 ( 0.0 , 1.0 ) ;
major _offset = vec2 ( 2.0 , 0.0 ) ;
}
vec2 v = ( origin * u _scale + u _translation ) / u _res * 2.0 ;
vec2 pos ;
if ( a _data . x > 0.0 ) {
v . y = 0.0 ;
} else {
v . x = 0.0 ;
}
if ( gl _VertexID % 6 == 0 ) {
pos = v ;
} else if ( gl _VertexID % 6 == 1 || gl _VertexID % 6 == 5 ) {
pos = v + ( a _data . x > 0.0 ? minor _offset : major _offset ) ;
//pos = v + minor_offset;
} else if ( gl _VertexID % 6 == 2 || gl _VertexID % 6 == 4 ) {
pos = v + ( a _data . x > 0.0 ? major _offset : minor _offset ) ;
//pos = v + major_offset;
} else if ( gl _VertexID % 6 == 3 ) {
pos = v + major _offset + minor _offset ;
//pos = v + major_offset + minor_offset;
}
vec2 screen02 = pos ;
screen02 . y = 2.0 - screen02 . y ;
v _fadeout = u _fadeout ;
gl _Position = vec4 ( screen02 - 1.0 , 0.0 , 1.0 ) ;
}
` ;
const dots _vs _src = ` #version 300 es
in vec2 a _center ; // per-instance
out float v _fadeout ;
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
uniform float u _fadeout ;
void main ( ) {
vec2 v = ( a _center * u _scale + u _translation ) / u _res * 2.0 ;
vec2 pos ;
vec2 pixel = 2.0 / u _res ;
if ( gl _VertexID % 6 == 0 ) {
pos = v + pixel * vec2 ( - 1.0 ) ;
} else if ( gl _VertexID % 6 == 1 ) {
pos = v + pixel * vec2 ( 1.0 , - 1.0 ) ;
} else if ( gl _VertexID % 6 == 2 ) {
pos = v + pixel * vec2 ( - 1.0 , 1.0 ) ;
} else if ( gl _VertexID % 6 == 3 ) {
pos = v + pixel * vec2 ( 1.0 ) ;
} else if ( gl _VertexID % 6 == 4 ) {
pos = v + pixel * vec2 ( - 1.0 , 1.0 ) ;
} else if ( gl _VertexID % 6 == 5 ) {
pos = v + pixel * vec2 ( 1.0 , - 1.0 ) ;
}
vec2 screen02 = pos ;
screen02 . y = 2.0 - screen02 . y ;
v _fadeout = u _fadeout ;
gl _Position = vec4 ( screen02 - 1.0 , 0.0 , 1.0 ) ;
}
` ;
const dots _fs _src = ` #version 300 es
precision highp float ;
in float v _fadeout ;
layout ( location = 0 ) out vec4 FragColor ;
void main ( ) {
vec3 color = vec3 ( 0.5 ) ;
FragColor = vec4 ( color * v _fadeout , v _fadeout ) ;
}
` ;
function init _webgl ( state , context ) {
context . canvas = document . querySelector ( '#c' ) ;
context . gl = context . canvas . getContext ( 'webgl2' , {
'preserveDrawingBuffer' : true ,
'desynchronized' : true ,
'antialias' : false ,
} ) ;
const gl = context . gl ;
gl . enable ( gl . BLEND ) ;
gl . blendFunc ( gl . ONE , gl . ONE _MINUS _SRC _ALPHA ) ;
gl . enable ( gl . DEPTH _TEST ) ;
gl . depthFunc ( gl . GEQUAL ) ;
context . gpu _timer _ext = gl . getExtension ( 'EXT_disjoint_timer_query_webgl2' ) ;
if ( context . gpu _timer _ext === null ) {
context . gpu _timer _ext = gl . getExtension ( 'EXT_disjoint_timer_query' ) ;
}
const quad _vs = create _shader ( gl , gl . VERTEX _SHADER , tquad _vs _src ) ;
const quad _fs = create _shader ( gl , gl . FRAGMENT _SHADER , tquad _fs _src ) ;
const sdf _vs = create _shader ( gl , gl . VERTEX _SHADER , sdf _vs _src ) ;
const sdf _fs = create _shader ( gl , gl . FRAGMENT _SHADER , sdf _fs _src ) ;
const opaque _vs = create _shader ( gl , gl . VERTEX _SHADER , opaque _vs _src ) ;
const nop _fs = create _shader ( gl , gl . FRAGMENT _SHADER , nop _fs _src ) ;
const simple _vs = create _shader ( gl , gl . VERTEX _SHADER , simple _vs _src ) ;
const simple _fs = create _shader ( gl , gl . FRAGMENT _SHADER , simple _fs _src ) ;
const dots _vs = create _shader ( gl , gl . VERTEX _SHADER , dots _vs _src ) ;
const dots _fs = create _shader ( gl , gl . FRAGMENT _SHADER , dots _fs _src ) ;
const grid _vs = create _shader ( gl , gl . VERTEX _SHADER , grid _vs _src ) ;
context . programs [ 'image' ] = create _program ( gl , quad _vs , quad _fs ) ;
context . programs [ 'debug' ] = create _program ( gl , simple _vs , simple _fs ) ;
context . programs [ 'sdf' ] = {
'opaque' : create _program ( gl , opaque _vs , nop _fs ) ,
'main' : create _program ( gl , sdf _vs , sdf _fs ) ,
} ;
context . programs [ 'pattern' ] = {
'dots' : create _program ( gl , dots _vs , dots _fs ) ,
'grid' : create _program ( gl , grid _vs , dots _fs ) ,
} ;
context . locations [ 'image' ] = {
'a_pos' : gl . getAttribLocation ( context . programs [ 'image' ] , 'a_pos' ) ,
'u_res' : gl . getUniformLocation ( context . programs [ 'image' ] , 'u_res' ) ,
'u_scale' : gl . getUniformLocation ( context . programs [ 'image' ] , 'u_scale' ) ,
'u_translation' : gl . getUniformLocation ( context . programs [ 'image' ] , 'u_translation' ) ,
'u_texture' : gl . getUniformLocation ( context . programs [ 'image' ] , 'u_texture' ) ,
} ;
context . locations [ 'debug' ] = {
'a_pos' : gl . getAttribLocation ( context . programs [ 'debug' ] , 'a_pos' ) ,
'u_res' : gl . getUniformLocation ( context . programs [ 'debug' ] , 'u_res' ) ,
'u_scale' : gl . getUniformLocation ( context . programs [ 'debug' ] , 'u_scale' ) ,
'u_translation' : gl . getUniformLocation ( context . programs [ 'debug' ] , 'u_translation' ) ,
} ;
context . locations [ 'sdf' ] = {
'opaque' : {
'a_pos' : gl . getAttribLocation ( context . programs [ 'sdf' ] . opaque , 'a_pos' ) ,
'a_line' : gl . getAttribLocation ( context . programs [ 'sdf' ] . opaque , 'a_line' ) ,
'a_stroke_id' : gl . getAttribLocation ( context . programs [ 'sdf' ] . opaque , 'a_stroke_id' ) ,
'u_res' : gl . getUniformLocation ( context . programs [ 'sdf' ] . opaque , 'u_res' ) ,
'u_scale' : gl . getUniformLocation ( context . programs [ 'sdf' ] . opaque , 'u_scale' ) ,
'u_translation' : gl . getUniformLocation ( context . programs [ 'sdf' ] . opaque , 'u_translation' ) ,
'u_stroke_count' : gl . getUniformLocation ( context . programs [ 'sdf' ] . opaque , 'u_stroke_count' ) ,
} ,
'main' : {
'a_a' : gl . getAttribLocation ( context . programs [ 'sdf' ] . main , 'a_a' ) ,
'a_b' : gl . getAttribLocation ( context . programs [ 'sdf' ] . main , 'a_b' ) ,
'a_stroke_id' : gl . getAttribLocation ( context . programs [ 'sdf' ] . main , 'a_stroke_id' ) ,
'a_pressure' : gl . getAttribLocation ( context . programs [ 'sdf' ] . main , 'a_pressure' ) ,
'u_res' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_res' ) ,
'u_scale' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_scale' ) ,
'u_translation' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_translation' ) ,
'u_debug_mode' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_debug_mode' ) ,
'u_stroke_count' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_stroke_count' ) ,
'u_stroke_data' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_stroke_data' ) ,
'u_stroke_texture_size' : gl . getUniformLocation ( context . programs [ 'sdf' ] . main , 'u_stroke_texture_size' ) ,
}
} ;
context . locations [ 'pattern' ] = {
'dots' : {
'a_xy' : gl . getAttribLocation ( context . programs [ 'pattern' ] . dots , 'a_xy' ) ,
'a_center' : gl . getAttribLocation ( context . programs [ 'pattern' ] . dots , 'a_center' ) ,
'u_res' : gl . getUniformLocation ( context . programs [ 'pattern' ] . dots , 'u_res' ) ,
'u_scale' : gl . getUniformLocation ( context . programs [ 'pattern' ] . dots , 'u_scale' ) ,
'u_translation' : gl . getUniformLocation ( context . programs [ 'pattern' ] . dots , 'u_translation' ) ,
'u_fadeout' : gl . getUniformLocation ( context . programs [ 'pattern' ] . dots , 'u_fadeout' ) ,
} ,
'grid' : {
'a_data' : gl . getAttribLocation ( context . programs [ 'pattern' ] . grid , 'a_data' ) ,
'u_res' : gl . getUniformLocation ( context . programs [ 'pattern' ] . grid , 'u_res' ) ,
'u_scale' : gl . getUniformLocation ( context . programs [ 'pattern' ] . grid , 'u_scale' ) ,
'u_translation' : gl . getUniformLocation ( context . programs [ 'pattern' ] . grid , 'u_translation' ) ,
'u_fadeout' : gl . getUniformLocation ( context . programs [ 'pattern' ] . grid , 'u_fadeout' ) ,
}
} ;
context . buffers [ 'debug' ] = {
'b_packed' : gl . createBuffer ( ) ,
} ;
context . buffers [ 'image' ] = {
'b_quads' : gl . createBuffer ( ) ,
} ;
context . buffers [ 'sdf' ] = {
'b_instance' : gl . createBuffer ( ) ,
'b_dynamic_instance' : gl . createBuffer ( ) ,
} ;
context . buffers [ 'pattern' ] = {
'b_instance_dot' : gl . createBuffer ( ) ,
'b_instance_grid' : gl . createBuffer ( ) ,
'b_dot' : gl . createBuffer ( ) ,
} ;
context . textures = {
'stroke_data' : gl . createTexture ( ) ,
'dynamic_stroke_data' : gl . createTexture ( ) ,
} ;
gl . bindTexture ( gl . TEXTURE _2D , context . textures [ 'stroke_data' ] ) ;
gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _MIN _FILTER , gl . NEAREST ) ;
gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _MAG _FILTER , gl . NEAREST ) ;
gl . texImage2D ( gl . TEXTURE _2D , 0 , gl . RGBA16UI , config . stroke _texture _size , config . stroke _texture _size , 0 , gl . RGBA _INTEGER , gl . UNSIGNED _SHORT , new Uint16Array ( config . stroke _texture _size * config . stroke _texture _size * 4 ) ) ; // fill the whole texture once with zeroes to kill a warning about a partial upload
gl . bindTexture ( gl . TEXTURE _2D , context . textures [ 'dynamic_stroke_data' ] ) ;
gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _MIN _FILTER , gl . NEAREST ) ;
gl . texParameteri ( gl . TEXTURE _2D , gl . TEXTURE _MAG _FILTER , gl . NEAREST ) ;
gl . texImage2D ( gl . TEXTURE _2D , 0 , gl . RGBA16UI , config . dynamic _stroke _texture _size , config . dynamic _stroke _texture _size , 0 , gl . RGBA _INTEGER , gl . UNSIGNED _SHORT , new Uint16Array ( config . dynamic _stroke _texture _size * config . dynamic _stroke _texture _size * 4 ) ) ; // fill the whole texture once with zeroes to kill a warning about a partial upload
const resize _canvas = ( entries ) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries [ 0 ] ;
let width ;
let height ;
if ( entry . devicePixelContentBoxSize ) {
width = entry . devicePixelContentBoxSize [ 0 ] . inlineSize ;
height = entry . devicePixelContentBoxSize [ 0 ] . blockSize ;
} else if ( entry . contentBoxSize ) {
// fallback for Safari that will not always be correct
width = Math . round ( entry . contentBoxSize [ 0 ] . inlineSize * devicePixelRatio ) ;
height = Math . round ( entry . contentBoxSize [ 0 ] . blockSize * devicePixelRatio ) ;
}
context . canvas . width = width ;
context . canvas . height = height ;
schedule _draw ( state , context ) ;
}
const resize _observer = new ResizeObserver ( resize _canvas ) ;
resize _observer . observe ( context . canvas ) ;
}
function create _shader ( gl , type , source ) {
const shader = gl . createShader ( type ) ;
gl . shaderSource ( shader , source ) ;
gl . compileShader ( shader ) ;
if ( gl . getShaderParameter ( shader , gl . COMPILE _STATUS ) ) {
return shader ;
}
console . error ( type , ':' , gl . getShaderInfoLog ( shader ) ) ;
gl . deleteShader ( shader ) ;
}
function create _program ( gl , vs , fs ) {
const program = gl . createProgram ( ) ;
gl . attachShader ( program , vs ) ;
gl . attachShader ( program , fs ) ;
gl . linkProgram ( program ) ;
if ( gl . getProgramParameter ( program , gl . LINK _STATUS ) ) {
return program ;
}
console . error ( 'link:' , gl . getProgramInfoLog ( program ) ) ;
gl . deleteProgram ( program ) ;
}