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 ;
uniform float u _fixed _pixel _width ;
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 ;
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 ;
uvec4 stroke _data = texelFetch ( u _stroke _data , ivec2 ( stroke _data _x , stroke _data _y ) , 0 ) ;
float radius = float ( stroke _data . w ) ;
if ( u _fixed _pixel _width > 0.0 ) {
radius = u _fixed _pixel _width / u _scale . x ;
}
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 * apron ;
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 dba = dot ( ba , ba ) ;
float dist ;
if ( dba > 0.0 ) {
float h = clamp ( dot ( pa , ba ) / dba , 0.0 , 1.0 ) ;
dist = length ( v _texcoord - ( a + ba * h ) ) - mix ( v _thickness . x , v _thickness . y , h ) ;
} else {
// Special case for when we are drawing a single point. Just a circle SDF
dist = length ( v _texcoord - a . xy - v _thickness . x ) ;
}
float fade = 0.5 * length ( fwidth ( v _texcoord ) ) ;
float alpha = 1.0 - smoothstep ( - fade , fade , dist ) ;
//if (alpha > 0.5) alpha = 0.5;
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 ;
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 ) ;
}
screen02 . y = 2.0 - screen02 . y ;
vec2 screen11 = screen02 - 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 ;
uniform int u _solid ;
uniform vec4 u _color ;
layout ( location = 0 ) out vec4 FragColor ;
void main ( ) {
if ( u _solid == 0 ) {
FragColor = texture ( u _texture , v _texcoord ) ;
} else {
FragColor = u _color ;
}
}
` ;
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 ) ;
}
` ;
//
const iquad _vs _src = ` #version 300 es
in vec2 a _topleft ; // per-instance
in vec2 a _bottomright ; // per-instance
uniform vec2 u _scale ;
uniform vec2 u _res ;
uniform vec2 u _translation ;
out vec3 v _color ;
void main ( ) {
vec2 pos ;
int vertex _index = gl _VertexID % 6 ;
if ( vertex _index == 0 ) {
// top left
pos = a _topleft ;
} else if ( vertex _index == 1 || vertex _index == 5 ) {
// top right
pos = vec2 ( a _bottomright . x , a _topleft . y ) ;
} else if ( vertex _index == 2 || vertex _index == 4 ) {
// bottom left
pos = vec2 ( a _topleft . x , a _bottomright . y ) ;
} else {
// bottom right
pos = a _bottomright ;
}
v _color = vec3 (
float ( int ( a _topleft . x ) * 908125 % 255 ) / 255.0 ,
float ( int ( a _topleft . y ) * 257722 % 255 ) / 255.0 ,
float ( int ( a _bottomright . y ) * 826586 % 255 ) / 255.0
) ;
vec2 screen01 = ( pos * u _scale + u _translation ) / u _res ;
vec2 screen02 = screen01 * 2.0 ;
screen02 . y = 2.0 - screen02 . y ;
vec2 screen11 = screen02 - 1.0 ;
gl _Position = vec4 ( screen11 , 0.0 , 1.0 ) ;
}
` ;
const iquad _fs _src = ` #version 300 es
precision highp float ;
layout ( location = 0 ) out vec4 FragColor ;
in vec3 v _color ;
void main ( ) {
FragColor = vec4 ( v _color , 0.5 ) ;
}
` ;
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.blendEquation(gl.MAX);
//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 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 ) ;
const iquad _vs = create _shader ( gl , gl . VERTEX _SHADER , iquad _vs _src ) ;
const iquad _fs = create _shader ( gl , gl . FRAGMENT _SHADER , iquad _fs _src ) ;
context . programs = {
'image' : create _program ( gl , quad _vs , quad _fs ) ,
'main' : create _program ( gl , sdf _vs , sdf _fs ) ,
'dots' : create _program ( gl , dots _vs , dots _fs ) ,
'grid' : create _program ( gl , grid _vs , dots _fs ) ,
'iquad' : create _program ( gl , iquad _vs , iquad _fs ) ,
} ;
context . buffers = {
'b_images' : gl . createBuffer ( ) ,
'b_strokes_static' : gl . createBuffer ( ) ,
'b_strokes_dynamic' : gl . createBuffer ( ) ,
'b_instance_dot' : gl . createBuffer ( ) ,
'b_instance_grid' : gl . createBuffer ( ) ,
'b_dot' : gl . createBuffer ( ) ,
'b_hud' : gl . createBuffer ( ) ,
'b_iquads' : gl . createBuffer ( ) ,
} ;
context . textures = {
'stroke_data' : gl . createTexture ( ) ,
'dynamic_stroke_data' : gl . createTexture ( ) ,
'ui' : 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
gl . bindTexture ( gl . TEXTURE _2D , context . textures [ 'ui' ] ) ;
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 . ui _texture _size , config . ui _texture _size , 0 , gl . RGBA _INTEGER , gl . UNSIGNED _SHORT , new Uint16Array ( config . ui _texture _size * config . ui _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 ) ) {
// src: tiny-sdf
// https://github.com/mapbox/tiny-sdf
const wrapper = { program } ;
const num _attrs = gl . getProgramParameter ( program , gl . ACTIVE _ATTRIBUTES ) ;
const num _uniforms = gl . getProgramParameter ( program , gl . ACTIVE _UNIFORMS ) ;
wrapper . locations = { } ;
for ( let i = 0 ; i < num _attrs ; i ++ ) {
const attribute = gl . getActiveAttrib ( program , i ) ;
wrapper . locations [ attribute . name ] = gl . getAttribLocation ( program , attribute . name ) ;
}
for ( let i = 0 ; i < num _uniforms ; i ++ ) {
const uniform = gl . getActiveUniform ( program , i ) ;
wrapper . locations [ uniform . name ] = gl . getUniformLocation ( program , uniform . name ) ;
}
return wrapper ;
}
console . error ( 'link:' , gl . getProgramInfoLog ( program ) ) ;
gl . deleteProgram ( program ) ;
}