![]() |
#1 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Hey everyone!
I'd like to share an experiment I've been working on for the past week or so. It's a VST meant to do essentially what ShaderToy does, but inside REAPER. Right now, it's extremely buggy. Like, it's not even funny how buggy it is. It will make REAPER crash, it will empty all of your 401k contributions in the process, and it will be ogling your mother while doing so. For now, no audio is present and the output resolution is hardcoded to 1024x768. In the future, I'll probably add an option to make it process audio the same way, even though the roundtrip latency from RAM to GPU and back are probably going to kill all of the possible performance gains, especially at low buffer sizes. The REAPER APIs for processing video are not super well documented so it's likely I'll be stopping here a few times to pester justin and schwa about silly things I'm doing that don't make sense. You can find its source code and some binaries here, but you've already been warned. Bonus: use this shader to render a snail at snail pace in REAPER Code:
// Copyright Inigo Quilez, 2015 - https://iquilezles.org/ // I am the sole copyright owner of this Work. // You cannot host, display, distribute or share this Work neither // as it is or altered, here on Shadertoy or anywhere else, in any // form including physical and digital. You cannot use this Work in any // commercial or non-commercial product, website or project. You cannot // sell this Work and you cannot mint an NFTs of it or train a neural // network with it without permission. I share this Work for educational // purposes, and you can link to it, through an URL, proper attribution // and unmodified screenshot, as part of your educational material. If // these conditions are too restrictive please contact me and we'll // definitely work it out. // You can buy a metal print of this shader here: // https://www.redbubble.com/i/metal-print/Snail-by-InigoQuilez/39845499.0JXQP // antialiasing - make AA 2, meaning 4x AA, if you have a fast machine #define AA 2 #define USE_TEXTURES 1 #define ZERO (min(int(iTime*30),0)) // https://iquilezles.org/articles/distfunctions float sdSphere( in vec3 p, in vec4 s ) { return length(p-s.xyz) - s.w; } // https://iquilezles.org/articles/distfunctions float sdEllipsoid( in vec3 p, in vec3 c, in vec3 r ) { #if 0 return (length( (p-c)/r ) - 1.0) * min(min(r.x,r.y),r.z); #else float k0 = length((p-c)/r); float k1 = length((p-c)/(r*r)); return k0*(k0-1.0)/k1; #endif } // https://iquilezles.org/articles/distfunctions float sdCircle( in vec2 p, in vec2 c, in float r ) { return length(p-c) - r; } // https://iquilezles.org/articles/distfunctions float sdTorus( vec3 p, vec2 t ) { return length( vec2(length(p.xz)-t.x,p.y) )-t.y; } // https://iquilezles.org/articles/distfunctions float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) { vec3 pa = p-a, ba = b-a; float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); return length( pa - ba*h ) - r; } // https://iquilezles.org/articles/distfunctions vec2 udSegment( vec3 p, vec3 a, vec3 b ) { vec3 pa = p-a, ba = b-a; float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); return vec2( length( pa - ba*h ), h ); } // http://research.microsoft.com/en-us/um/people/hoppe/ravg.pdf float det( vec2 a, vec2 b ) { return a.x*b.y-b.x*a.y; } vec3 getClosest( vec2 b0, vec2 b1, vec2 b2 ) { float a = det(b0,b2); float b = 2.0*det(b1,b0); float d = 2.0*det(b2,b1); float f = b*d - a*a; vec2 d21 = b2-b1; vec2 d10 = b1-b0; vec2 d20 = b2-b0; vec2 gf = 2.0*(b*d21+d*d10+a*d20); gf = vec2(gf.y,-gf.x); vec2 pp = -f*gf/dot(gf,gf); vec2 d0p = b0-pp; float ap = det(d0p,d20); float bp = 2.0*det(d10,d0p); float t = clamp( (ap+bp)/(2.0*a+b+d), 0.0 ,1.0 ); return vec3( mix(mix(b0,b1,t), mix(b1,b2,t),t), t ); } vec4 sdBezier( vec3 a, vec3 b, vec3 c, vec3 p ) { vec3 w = normalize( cross( c-b, a-b ) ); vec3 u = normalize( c-b ); vec3 v = ( cross( w, u ) ); vec2 a2 = vec2( dot(a-b,u), dot(a-b,v) ); vec2 b2 = vec2( 0.0 ); vec2 c2 = vec2( dot(c-b,u), dot(c-b,v) ); vec3 p3 = vec3( dot(p-b,u), dot(p-b,v), dot(p-b,w) ); vec3 cp = getClosest( a2-p3.xy, b2-p3.xy, c2-p3.xy ); return vec4( sqrt(dot(cp.xy,cp.xy)+p3.z*p3.z), cp.z, length(cp.xy), p3.z ); } // https://iquilezles.org/articles/smin float smin( float a, float b, float k ) { float h = max(k-abs(a-b),0.0); return min(a, b) - h*h*0.25/k; } // https://iquilezles.org/articles/smin float smax( float a, float b, float k ) { float h = max(k-abs(a-b),0.0); return max(a, b) + h*h*0.25/k; } // https://iquilezles.org/articles/smin vec3 smax( vec3 a, vec3 b, float k ) { vec3 h = max(k-abs(a-b),0.0); return max(a, b) + h*h*0.25/k; } //--------------------------------------------------------------------------- float hash1( float n ) { return fract(sin(n)*43758.5453123); } vec3 forwardSF( float i, float n ) { const float PI = 3.141592653589793238; const float PHI = 1.618033988749894848; float phi = 2.0*PI*fract(i/PHI); float zi = 1.0 - (2.0*i+1.0)/n; float sinTheta = sqrt( 1.0 - zi*zi); return vec3( cos(phi)*sinTheta, sin(phi)*sinTheta, zi); } //--------------------------------------------------------------------------- const float pi = 3.1415927; //--------------------------------------------------------------------------- float mapShell( in vec3 p, out vec4 matInfo ) { p -= vec3(0.05,0.12,-0.09); vec3 q = mat3(-0.6333234236, -0.7332753384, 0.2474039592, 0.7738444477, -0.6034162289, 0.1924931824, 0.0081370606, 0.3133626215, 0.9495986813) * p; const float b = 0.1759; float r = length( q.xy ); float t = atan( q.y, q.x ); // https://swiftcoder.wordpress.com/2010/06/21/logarithmic-spiral-distance-field/ float np = (log( r)/b-t)/(2.0*pi); float nm = (log(0.11)/b-t)/(2.0*pi); float n = min(np,nm); float ni = floor( n ); float r1 = exp( b * (t + 2.0*pi*ni)); float r2 = r1 * 3.019863; //------- float h1 = q.z + 1.5*r1 - 0.5; float d1 = sqrt((r1-r)*(r1-r)+h1*h1) - r1; float h2 = q.z + 1.5*r2 - 0.5; float d2 = sqrt((r2-r)*(r2-r)+h2*h2) - r2; float d, dx, dy; if( d1<d2 ) { d = d1; dx=r1-r; dy=h1; } else { d = d2; dx=r2-r; dy=h2; } float di = 0.0; d += 0.002*di; matInfo = vec4(dx,dy,r/0.4,t/pi); vec3 s = q; q = q - vec3(0.34,-0.1,0.03); q.xy = mat2(0.8,0.6,-0.6,0.8)*q.xy; d = smin( d, sdTorus( q, vec2(0.28,0.05) ), 0.06); d = smax( d, -sdEllipsoid(q,vec3(0.0,0.0,0.0),vec3(0.24,0.36,0.24) ), 0.03 ); d = smax( d, -sdEllipsoid(s,vec3(0.52,-0.0,0.0),vec3(0.42,0.23,0.5) ), 0.05 ); return d; } #if USE_TEXTURES==0 float voronoi( in vec2 x, float w, float s ) { vec2 n = floor( x ); vec2 f = fract( x ); vec2 m = vec2( 8.0, 0.0 ); for( int j=-1; j<=1; j++ ) for( int i=-1; i<=1; i++ ) { vec2 g = vec2( float(i),float(j) ); vec3 r = hash3( n + g ); float d = length(g - f + r.xy); float c = s*(0.1+0.9*r.z); d = min(d*1.25,1.0); // do the smooth min for distances and color float h = smoothstep( -1.0, 1.0, (m.x-d)/w ); m = mix( m, vec2(d,c), h ) - h*(1.0-h)*w/(1.0+3.0*w); } return m.y*(1.0-m.x); } #endif float text1( in vec2 p ) { #if USE_TEXTURES==1 return 0.0; #else float f = max( voronoi( 7.5*p, 0.3, 1.00 ), voronoi( 13.0*p, 0.3, 0.75 ) ); return 0.5*smoothstep(0.0,1.0,f); #endif } vec2 mapSnail( vec3 p, out vec4 matInfo ) { const vec3 head = vec3(-0.76,0.6,-0.3); vec3 q = p - head; // body vec4 b1 = sdBezier( vec3(-0.13,-0.65,0.0), vec3(0.24,0.9+0.1,0.0), head+vec3(0.04,0.01,0.0), p ); float d1 = b1.x; d1 -= smoothstep(0.0,0.2,b1.y)*(0.16 - 0.07*smoothstep(0.5,1.0,b1.y)); b1 = sdBezier( vec3(-0.085,0.0,0.0), vec3(-0.1,0.9-0.05,0.0), head+vec3(0.06,-0.08,0.0), p ); float d2 = b1.x; d2 -= 0.1 - 0.06*b1.y; d1 = smin( d1, d2, 0.03 ); matInfo.xyz = b1.yzw; d2 = sdSphere( q, vec4(0.0,-0.06,0.0,0.085) ); d1 = smin( d1, d2, 0.03 ); d1 = smin( d1, sdSphere(p,vec4(0.05,0.52,0.0,0.13)), 0.07 ); q.xz = mat2(0.8,0.6,-0.6,0.8)*q.xz; vec3 sq = vec3( q.xy, abs(q.z) ); // top antenas vec3 af = 0.05*sin(0.5*iTime+vec3(0.0,1.0,3.0) + vec3(2.0,1.0,0.0)*sign(q.z) ); vec4 b2 = sdBezier( vec3(0.0), vec3(-0.1,0.2,0.2), vec3(-0.3,0.2,0.3)+af, sq ); float d3 = b2.x; d3 -= 0.03 - 0.025*b2.y; d1 = smin( d1, d3, 0.04 ); d3 = sdSphere( sq, vec4(-0.3,0.2,0.3,0.016) + vec4(af,0.0) ); d1 = smin( d1, d3, 0.01 ); // bottom antenas vec3 bf = 0.02*sin(0.3*iTime+vec3(4.0,1.0,2.0) + vec3(3.0,0.0,1.0)*sign(q.z) ); vec2 b3 = udSegment( sq, vec3(0.06,-0.05,0.0), vec3(-0.04,-0.2,0.18)+bf ); d3 = b3.x; d3 -= 0.025 - 0.02*b3.y; d1 = smin( d1, d3, 0.06 ); d3 = sdSphere( sq, vec4(-0.04,-0.2,0.18,0.008)+vec4(bf,0.0) ); d1 = smin( d1, d3, 0.02 ); // bottom vec3 pp = p-vec3(-0.17,0.15,0.0); float co = 0.988771078; float si = 0.149438132; pp.xy = mat2(co,-si,si,co)*pp.xy; d1 = smin( d1, sdEllipsoid( pp, vec3(0.0,0.0,0.0), vec3(0.084,0.3,0.15) ), 0.05 ); d1 = smax( d1, -sdEllipsoid( pp, vec3(-0.08,-0.0,0.0), vec3(0.06,0.55,0.1) ), 0.02 ); // disp float dis = text1(5.0*p.xy); float dx = 0.5 + 0.5*(1.0-smoothstep(0.5,1.0,b1.y)); d1 -= 0.005*dis*dx*0.5; return vec2(d1,1.0); } float mapDrop( in vec3 p ) { p -= vec3(-0.26,0.25,-0.02); p.x -= 2.5*p.y*p.y; return sdCapsule( p, vec3(0.0,-0.06,0.0), vec3(0.014,0.06,0.0), 0.037 ); } float mapLeaf( in vec3 p ) { p -= vec3(-1.8,0.6,-0.75); p = mat3(0.671212, 0.366685, -0.644218, -0.479426, 0.877583, 0.000000, 0.565354, 0.308854, 0.764842)*p; p.y += 0.2*exp2(-abs(2.9*p.z) ); float ph = 0.25*50.0*p.x - 0.25*75.0*abs(p.z); float rr = sin( ph ); rr = rr*rr; rr = rr*rr; p.y += 0.005*rr; float r = clamp((p.x+2.0)/4.0,0.0,1.0); r = 0.0001 + r*(1.0-r)*(1.0-r)*6.0; rr = sin( ph*2.0 ); rr = rr*rr; rr *= 0.5+0.5*sin( p.x*12.0 ); float ri = 0.035*rr; float d = sdEllipsoid( p, vec3(0.0), vec3(2.0,0.25*r,r+ri) ); float d2 = p.y-0.02; return smax( d, -d2, 0.02 ); } vec2 mapOpaque( vec3 p, out vec4 matInfo ) { matInfo = vec4(0.0); // leaf vec2 res = vec2( mapLeaf( p ), 4.0); // stem vec4 b3 = sdBezier( vec3(-0.15,-1.5,0.0), vec3(-0.1,0.5,0.0), vec3(-0.6,1.5,0.0), p ); float d3 = b3.x - 0.04 + 0.02*b3.y; if( d3<res.x ) res = vec2(d3,3.0); // snail float boundingVolume = sdCapsule(p, vec3(0.0), vec3(-0.6,0.7,0.0), 0.55); if( boundingVolume<res.x ) { vec2 tmp = mapSnail( p, matInfo ); if( tmp.x<res.x ) { res = tmp; } // shell float bb = length( p-vec3(0.25,0.3,-0.1) )-0.6; if( bb<res.x ) { vec4 tmpMatInfo; float d4 = mapShell( p, tmpMatInfo ); if( d4<res.x ) { res = vec2(d4,2.0); matInfo = tmpMatInfo; } } } return res; } // https://iquilezles.org/articles/normalsSDF vec3 calcNormalOpaque( in vec3 pos, in float eps ) { vec4 kk; #if 0 vec2 e = vec2(1.0,-1.0)*0.5773*eps; return normalize( e.xyy*mapOpaque( pos + e.xyy, kk ).x + e.yyx*mapOpaque( pos + e.yyx, kk ).x + e.yxy*mapOpaque( pos + e.yxy, kk ).x + e.xxx*mapOpaque( pos + e.xxx, kk ).x ); #else // inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times vec3 n = vec3(0.0); for( int i=ZERO; i<4; i++ ) { vec3 e = 0.5773*(2.0*vec3((((i+3)>>1)&1),((i>>1)&1),(i&1))-1.0); n += e*mapOpaque(pos+eps*e,kk).x; } return normalize(n); #endif } //========================================================================= float mapLeafWaterDrops( in vec3 p ) { p -= vec3(-1.8,0.6,-0.75); vec3 s = p; p = mat3(0.671212, 0.366685, -0.644218, -0.479426, 0.877583, 0.000000, 0.565354, 0.308854, 0.764842)*p; vec3 q = p; p.y += 0.2*exp(-abs(2.0*p.z) ); float r = clamp((p.x+2.0)/4.0,0.0,1.0); r = r*(1.0-r)*(1.0-r)*6.0; float d1 = sdEllipsoid( q, vec3(0.5,0.0,0.2), 1.0*vec3(0.15,0.13,0.15) ); float d2 = sdEllipsoid( q, vec3(0.8,-0.07,-0.15), 0.5*vec3(0.15,0.13,0.15) ); float d3 = sdEllipsoid( s, vec3(0.76,-0.8,0.6), 0.5*vec3(0.15,0.2,0.15) ); float d4 = sdEllipsoid( q, vec3(-0.5,0.09,-0.2), vec3(0.04,0.03,0.04) ); d3 = max( d3, p.y-0.01); return min( min(d1,d4), min(d2,d3) ); } vec2 mapTransparent( vec3 p, out vec4 matInfo ) { matInfo = vec4(0.0); float d5 = mapDrop( p ); vec2 res = vec2(d5,4.0); float d6 = mapLeafWaterDrops( p ); res.x = min( res.x, d6 ); return res; } // https://iquilezles.org/articles/normalsSDF vec3 calcNormalTransparent( in vec3 pos, in float eps ) { vec4 kk; vec2 e = vec2(1.0,-1.0)*0.5773*eps; return normalize( e.xyy*mapTransparent( pos + e.xyy, kk ).x + e.yyx*mapTransparent( pos + e.yyx, kk ).x + e.yxy*mapTransparent( pos + e.yxy, kk ).x + e.xxx*mapTransparent( pos + e.xxx, kk ).x ); } //========================================================================= float calcAO( in vec3 pos, in vec3 nor ) { vec4 kk; float ao = 0.0; for( int i=ZERO; i<32; i++ ) { vec3 ap = forwardSF( float(i), 32.0 ); float h = hash1(float(i)); ap *= sign( dot(ap,nor) ) * h*0.1; ao += clamp( mapOpaque( pos + nor*0.01 + ap, kk ).x*3.0, 0.0, 1.0 ); } ao /= 32.0; return clamp( ao*6.0, 0.0, 1.0 ); } float calcSSS( in vec3 pos, in vec3 nor ) { vec4 kk; float occ = 0.0; for( int i=ZERO; i<8; i++ ) { float h = 0.002 + 0.11*float(i)/7.0; vec3 dir = normalize( sin( float(i)*13.0 + vec3(0.0,2.1,4.2) ) ); dir *= sign(dot(dir,nor)); occ += (h-mapOpaque(pos-h*dir, kk).x); } occ = clamp( 1.0 - 11.0*occ/8.0, 0.0, 1.0 ); return occ*occ; } // https://iquilezles.org/articles/rmshadows float calcSoftShadow( in vec3 ro, in vec3 rd, float k ) { vec4 kk; float res = 1.0; float t = 0.01; for( int i=ZERO; i<32; i++ ) { float h = mapOpaque(ro + rd*t, kk ).x; res = min( res, smoothstep(0.0,1.0,k*h/t) ); t += clamp( h, 0.04, 0.1 ); if( res<0.01 ) break; } return clamp(res,0.0,1.0); } const vec3 sunDir = normalize( vec3(0.2,0.1,0.02) ); vec3 shadeOpaque( in vec3 ro, in vec3 rd, in float t, in float m, in vec4 matInfo ) { float eps = 0.002; vec3 pos = ro + t*rd; vec3 nor = calcNormalOpaque( pos, eps ); vec3 mateD = vec3(0.0); vec3 mateS = vec3(0.0); vec2 mateK = vec2(0.0); vec3 mateE = vec3(0.0); float focc = 1.0; float fsha = 1.0; if( m<1.5 ) // snail body { float dis = text1( 5.0*pos.xy ); float be = sdEllipsoid( pos, vec3(-0.3,-0.5,-0.1), vec3(0.2,1.0,0.5) ); be = 1.0-smoothstep( -0.01, 0.01, be ); float ff = abs(matInfo.x-0.20); mateS = 6.0*mix( 0.7*vec3(2.0,1.2,0.2), vec3(2.5,1.8,0.9), ff ); mateS += 2.0*dis; mateS *= 1.5; mateS *= 1.0 + 0.5*ff*ff; mateS *= 1.0-0.5*be; mateD = vec3(1.0,0.8,0.4); mateD *= dis; mateD *= 0.015; mateD += vec3(0.8,0.4,0.3)*0.15*be; mateK = vec2( 60.0, 0.7 + 2.0*dis ); float f = clamp( dot( -rd, nor ), 0.0, 1.0 ); f = 1.0-pow( f, 8.0 ); f = 1.0 - (1.0-f)*(1.0-0.0); mateS *= vec3(0.5,0.1,0.0) + f*vec3(0.5,0.9,1.0); float b = 1.0-smoothstep( 0.25,0.55,abs(pos.y)); focc = 0.2 + 0.8*smoothstep( 0.0, 0.15, sdSphere(pos,vec4(0.05,0.52,0.0,0.13)) ); } else if( m<2.5 ) // shell { mateK = vec2(0.0); float tip = 1.0-smoothstep(0.05,0.4, length(pos-vec3(0.17,0.2,0.35)) ); mateD = mix( 0.7*vec3(0.2,0.21,0.22), 0.2*vec3(0.15,0.1,0.0), tip ); vec2 uv = vec2( .5*atan(matInfo.x,matInfo.y)/3.1416, 1.5*matInfo.w ); float ff = 2.0*matInfo.w+matInfo.z; float ral = 0.4 + 0.4*(0.26*sin(ff*6.283185* 1.0+0.0)+ 0.23*sin(ff*6.283185* 3.0+2.0)+ 0.20*sin(ff*6.283185* 8.0+1.0)+ 0.17*sin(ff*6.283185*17.0+3.0)+ 0.14*sin(ff*6.283185*25.0+2.0)); mateD *= 0.25 + 0.75*ral; float pa = smoothstep(-0.2,0.2, 0.3+sin(2.0+40.0*uv.x + 3.0*sin(11.0*uv.x)) ); float bar = mix(pa,1.0,smoothstep(0.7,1.0,tip)); bar *= (matInfo.z<0.6) ? 1.0 : smoothstep( 0.17, 0.21, abs(matInfo.w) ); mateD *= vec3(0.06,0.03,0.0)+vec3(0.94,0.97,1.0)*bar; mateK = vec2( 64.0, 0.2 ); mateS = 1.5*vec3(1.0,0.65,0.6) * (1.0-tip);//*0.5; } else if( m<3.5 ) // plant { mateD = vec3(0.05,0.1,0.0)*0.2; mateS = vec3(0.1,0.2,0.02)*25.0; mateK = vec2(5.0,1.0); float fre = clamp(1.0+dot(nor,rd), 0.0, 1.0 ); mateD += 0.2*fre*vec3(1.0,0.5,0.1); vec3 te = vec3(0.0); mateS *= 0.5 + 1.5*te; mateE = 0.5*vec3(0.1,0.1,0.03)*(0.2+0.8*te.x); } else //if( m<4.5 ) // leaf { vec3 p = pos - vec3(-1.8,0.6,-0.75); vec3 s = p; p = mat3(0.671212, 0.366685, -0.644218, -0.479426, 0.877583, 0.000000, 0.565354, 0.308854, 0.764842)*p; vec3 q = p; p.y += 0.2*exp(-abs(2.0*p.z) ); float v = smoothstep( 0.01, 0.02, abs(p.z)); float rr = sin( 4.0*0.25*50.0*p.x - 4.0*0.25*75.0*abs(p.z) ); vec3 te = vec3(0.0); float r = clamp((p.x+2.0)/4.0,0.0,1.0); r = r*(1.0-r)*(1.0-r)*6.0; float ff = length(p.xz/vec2(2.0,r)); mateD = mix( vec3(0.07,0.1,0.0), vec3(0.05,0.2,0.01)*0.25, v ); mateD = mix( mateD, vec3(0.16,0.2,0.01)*0.25, ff ); mateD *= 1.0 + 0.25*te; mateD *= 0.8; mateS = vec3(0.15,0.2,0.02)*0.8; mateS *= 1.0 + 0.2*rr; mateS *= 0.8; mateK = vec2(64.0,0.25); //--------------------- nor.xz += v*0.15*(-1.0+2.0*vec2(0.0)); nor = normalize( nor ); float d1 = sdEllipsoid( q, vec3( 0.5-0.07, 0.0, 0.20), 1.0*vec3(1.4*0.15,0.13,0.15) ); float d2 = sdEllipsoid( q, vec3( 0.8-0.05,-0.07,-0.15), 0.5*vec3(1.3*0.15,0.13,0.15) ); float d4 = sdEllipsoid( q, vec3(-0.5-0.07, 0.09,-0.20), 1.0*vec3(1.4*0.04,0.03,0.04) ); float dd = min(d1,min(d2,d4)); fsha = 0.05 + 0.95*smoothstep(0.0,0.05,dd); d1 = abs( sdCircle( q.xz, vec2( 0.5, 0.20), 1.0*0.15 )); d2 = abs( sdCircle( q.xz, vec2( 0.8,-0.15), 0.5*0.15 )); d4 = abs( sdCircle( q.xz, vec2(-0.5,-0.20), 1.0*0.04 )); dd = min(d1,min(d2,d4)); focc *= 0.55 + 0.45*smoothstep(0.0,0.08,dd); d1 = distance( q.xz, vec2( 0.5-0.07, 0.20) ); d2 = distance( q.xz, vec2( 0.8-0.03,-0.15) ); fsha += (1.0-smoothstep(0.0,0.10,d1))*1.5; fsha += (1.0-smoothstep(0.0,0.05,d2))*1.5; } vec3 hal = normalize( sunDir-rd ); float fre = clamp(1.0+dot(nor,rd), 0.0, 1.0 ); float occ = calcAO( pos, nor )*focc; float sss = calcSSS( pos, nor ); sss = sss*occ + fre*occ + (0.5+0.5*fre)*pow(abs(matInfo.x-0.2),1.0)*occ; float dif1 = clamp( dot(nor,sunDir), 0.0, 1.0 ); dif1 *= fsha; float sha = 1.0; if( dif1>0.0001 ) sha=calcSoftShadow( pos, sunDir, 20.0 ); dif1 *= sha; float spe1 = clamp( dot(nor,hal), 0.0, 1.0 ); float bou = clamp( 0.3-0.7*nor.y, 0.0, 1.0 ); // illumination vec3 col = vec3(0.0); col += 7.0*vec3(1.7,1.2,0.6)*dif1*2.0; // sun col += 4.0*vec3(0.2,1.2,1.6)*occ*(0.5+0.5*nor.y); // sky col += 1.8*vec3(0.1,2.0,0.1)*bou*occ; // bounce col *= mateD; col += 0.4*sss*(vec3(0.15,0.1,0.05)+vec3(0.85,0.9,0.95)*dif1)*(0.05+0.95*occ)*mateS; // sss col = pow(col,vec3(0.6,0.8,1.0)); col += vec3(1.0,1.0,1.0)*0.2*pow( spe1, 1.0+mateK.x )*dif1*(0.04+0.96*pow(fre,4.0))*mateK.x*mateK.y; // sun lobe1 col += vec3(1.0,1.0,1.0)*0.1*pow( spe1, 1.0+mateK.x/3.0 )*dif1*(0.1+0.9*pow(fre,4.0))*mateK.x*mateK.y; // sun lobe2 col += 0.1*vec3(1.0,max(1.5-0.7*col.y,0.0),2.0)*occ*occ*smoothstep( 0.0, 0.3, reflect( rd, nor ).y )*mateK.x*mateK.y*(0.04+0.96*pow(fre,5.0)); // sky col += mateE; return col; } vec3 shadeTransparent( in vec3 ro, in vec3 rd, in float t, in float m, in vec4 matInfo, in vec3 col, in float depth ) { vec3 oriCol = col; float dz = depth - t; float ao = clamp(dz*50.0,0.0,1.0); vec3 pos = ro + t*rd; vec3 nor = calcNormalTransparent( pos, 0.002 ); float fre = clamp( 1.0 + dot( rd, nor ), 0.0, 1.0 ); vec3 hal = normalize( sunDir-rd ); vec3 ref = reflect( -rd, nor ); float spe1 = clamp( dot(nor,hal), 0.0, 1.0 ); float spe2 = clamp( dot(ref,sunDir), 0.0, 1.0 ); float ds = 1.6 - col.y; col *= mix( vec3(0.0,0.0,0.0), vec3(0.4,0.6,0.4), ao ); col += ds*1.5*vec3(1.0,0.9,0.8)*pow( spe1, 80.0 ); col += ds*0.2*vec3(0.9,1.0,1.0)*smoothstep(0.4,0.8,fre); col += ds*0.9*vec3(0.6,0.7,1.0)*smoothstep( -0.5, 0.5, -reflect( rd, nor ).y )*smoothstep(0.2,0.4,fre); col += ds*0.5*vec3(1.0,0.9,0.8)*pow( spe2, 80.0 ); col += ds*0.5*vec3(1.0,0.9,0.8)*pow( spe2, 16.0 ); #if USE_TEXTURES==1 col += vec3(0.8,1.0,0.8)*0.5*smoothstep(0.3,0.6,text1( 0.8*nor.xy ))*(0.1+0.9*fre*fre); #else col += vec3(0.8,1.0,0.8)*0.65*smoothstep(0.3,0.6,text1( 0.7*nor.xy ))*(0.1+0.9*fre*fre); #endif // hide aliasing a bit return mix( col, oriCol, smoothstep(0.6,1.0,fre) ); } //-------------------------------------------- vec2 intersectOpaque( in vec3 ro, in vec3 rd, const float mindist, const float maxdist, out vec4 matInfo ) { vec2 res = vec2(-1.0); float t = mindist; for( int i=ZERO; i<128; i++ ) { vec3 p = ro + t*rd; vec2 h = mapOpaque( p, matInfo ); res = vec2(t,h.y); if( h.x<(0.001*t) || t>maxdist ) break; t += h.x*0.9; } return res; } vec2 intersectTransparent( in vec3 ro, in vec3 rd, const float mindist, const float maxdist, out vec4 matInfo ) { vec2 res = vec2(-1.0); float t = mindist; for( int i=ZERO; i<64; i++ ) { vec3 p = ro + t*rd; vec2 h = mapTransparent( p, matInfo ); res = vec2(t,h.y); if( h.x<(0.001*t) || t>maxdist ) break; t += h.x; } return res; } vec3 background( in vec3 d ) { // cheap cubemap vec3 n = abs(d); vec2 uv = (n.x>n.y && n.x>n.z) ? d.yz/d.x: (n.y>n.x && n.y>n.z) ? d.zx/d.y: d.xy/d.z; // fancy blur vec3 col = vec3( 0.0 ); for( int i=ZERO; i<200; i++ ) { float h = float(i)/200.0; float an = 31.0*6.2831*h; vec2 of = vec2( cos(an), sin(an) ) * h; vec3 tmp = vec3(0.0); col = smax( col, tmp, 0.5 ); } return pow(col,vec3(3.5,3.0,6.0))*0.2; } vec3 render( in vec3 ro, in vec3 rd, in vec2 q ) { //----------------------------- vec3 col = background( rd ); //----------------------------- float mindist = 1.0; float maxdist = 4.0; vec4 matInfo; vec2 tm = intersectOpaque( ro, rd, mindist, maxdist, matInfo ); if( tm.y>-0.5 && tm.x < maxdist ) { col = shadeOpaque( ro, rd, tm.x, tm.y, matInfo ); maxdist = tm.x; } //----------------------------- tm = intersectTransparent( ro, rd, mindist, maxdist, matInfo ); if( tm.y>-0.5 && tm.x < maxdist ) { col = shadeTransparent( ro, rd, tm.x, tm.y, matInfo, col, maxdist ); } //----------------------------- float sun = clamp(dot(rd,sunDir),0.0,1.0); col += 1.0*vec3(1.5,0.8,0.7)*pow(sun,4.0); //----------------------------- col = pow( col, vec3(0.45) ); col = vec3(1.05,1.0,1.0)*col*(0.7+0.3*col*max(3.0-2.0*col,0.0)) + vec3(0.0,0.0,0.04); col *= 0.3 + 0.7*pow(16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y),0.1); return clamp( col, 0.0, 1.0 ); } mat3 setCamera( in vec3 ro, in vec3 rt ) { vec3 w = normalize(ro-rt); float m = sqrt(1.0-w.y*w.y); return mat3( w.z, 0.0, -w.x, 0.0, m*m, -w.z*w.y, w.x*m, w.y*m, w.z*m ); } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { #if AA<2 vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y; vec2 q = fragCoord/iResolution.xy; float an = 1.87 - 0.04*(1.0-cos(0.5*iTime)); vec3 ro = vec3(-0.4,0.2,0.0) + 2.2*vec3(cos(an),0.0,sin(an)); vec3 ta = vec3(-0.6,0.2,0.0); mat3 ca = setCamera( ro, ta ); vec3 rd = normalize( ca * vec3(p,-2.8) ); vec3 col = render( ro, rd, q ); #else vec3 col = vec3(0.0); for( int m=ZERO; m<AA; m++ ) for( int n=ZERO; n<AA; n++ ) { vec2 rr = vec2(float(m),float(n))/float(AA); vec2 p = (2.0*(fragCoord+rr)-iResolution.xy)/iResolution.y; float an = 1.87 - 0.04*(1.0-cos(0.5*iTime)); vec2 q = (fragCoord+rr)/iResolution.xy; vec3 ro = vec3(-0.4,0.2,0.0) + 2.2*vec3(cos(an),0.0,sin(an)); vec3 ta = vec3(-0.6,0.2,0.0); mat3 ca = setCamera( ro, ta ); vec3 rd = normalize( ca * vec3(p,-2.8) ); col += render( ro, rd, q ); } col /= float(AA*AA); #endif fragColor = vec4( col, 1.0 ); } |
![]() |
![]() |
![]() |
#2 |
Human being with feelings
Join Date: Mar 2021
Posts: 410
|
![]()
Very interesting!
Is it meant to be a playground (eg video only appears in VST window) or to produce exportable video like a video processor? Thanks for sharing ![]() |
![]() |
![]() |
![]() |
#3 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
It produces video data, it's actually meant to work sort of like the existing video processor, but using GLSL shaders instead of EEL2 scripts.
Reading the first input channel is actually supposed to work through the iChannel sampler2D uniform, but for some reason right now the plugin becomes choppy whenever an input channel is present. i.e. Code:
void mainImage(out vec4 fragColor, in vec2 fragCoord) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord / iResolution.xy; uv = vec2(uv.x, 1.0 - uv.y); // Flip y coordinate because OpenGL just neeeeds to be different // Time varying pixel color vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0, 2, 4)); // Output to screen fragColor = vec4(col, 1.0) * (1.0 - iWet) + texture(iChannel, uv) * iWet; } |
![]() |
![]() |
![]() |
#4 |
Human being with feelings
Join Date: Mar 2021
Posts: 410
|
![]()
That's very impressive! Thanks again for sharing
![]() |
![]() |
![]() |
![]() |
#5 |
Human being with feelings
Join Date: Feb 2015
Location: Turkey
Posts: 159
|
![]()
Reaper used to support Winamp's visualizations until v4. This just felt like a full circle completed
![]() Great work!
__________________
Audio Director at Taleworlds Entertainment --- Articulation Manager feature request - Mastodon/@Ugurcan - @UgurcanFX |
![]() |
![]() |
![]() |
#6 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Ended up migrating to Vulkan instead of OpenGL in the interest of driver compatibility and ease of use in a multithreaded environment. It turned out to be quite effort...
Still plenty of crashes left, but still good progress I believe ![]() https://github.com/frabert/ogler/releases/tag/v0.2.0 |
![]() |
![]() |
![]() |
#7 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Version 0.4.0 is out!
Compared to version 0.2.0 we have substantial stability and performance improvements, also custom parameters can be declared in a specific GLSL uniform block and can be automated. Available here: https://github.com/frabert/ogler/releases/tag/v0.4.0 ![]() |
![]() |
![]() |
![]() |
#8 |
Human being with feelings
Join Date: Dec 2012
Posts: 7,205
|
![]()
I'm sorry I haven't dropped in sooner, but I am excited for this. No idea where to even start. Way above my paygrade, but I really want to finger it out.
__________________
Lorenzo's Tractor is Everywhere --- Ash's Tube --- Join the Partnership for a Drum Free Amerika |
![]() |
![]() |
![]() |
#9 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Hey ashcat! I'm planning on writing some sort of introduction to GLSL shader sometime, but if you find any ShaderToy tutorial on the net you like, you can probably apply most of that to ogler as well
![]() |
![]() |
![]() |
![]() |
#10 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Version 0.5.0 is out!
Lots of improvements in this one:
![]() |
![]() |
![]() |
![]() |
#11 |
Human being with feelings
Join Date: Dec 2012
Posts: 7,205
|
![]()
Can it see and use audio samples?
__________________
Lorenzo's Tractor is Everywhere --- Ash's Tube --- Join the Partnership for a Drum Free Amerika |
![]() |
![]() |
![]() |
#12 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Unfortunately, not directly.* What it can do, though, is read automation: so if you wanted to e.g. change the height of that pyramid based on the loudness of the incoming audio, you could instead use the JSFX Loudness Meter to write an automation track with that info, and then copy the resulting envelope to the parameter controlling the height of the pyramid.
No fancy FFT effects on the shaders though, for now ![]() * Why can't it access the audio data? The gist of the issue is that audio and video processing happen at essentially completely separate times, so there is no guarantee REAPER will send the right audio data before processing a specific frame. Also, the block size at which audio processing happens is completely independent of framerate, so that won't match either |
![]() |
![]() |
![]() |
#13 |
Human being with feelings
Join Date: Dec 2012
Posts: 7,205
|
![]()
Yeah I was hoping you’d found a way around that. We use a buffer in gmem to do it in VP, can your thing access that?
__________________
Lorenzo's Tractor is Everywhere --- Ash's Tube --- Join the Partnership for a Drum Free Amerika |
![]() |
![]() |
![]() |
#14 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
That's a great idea, it's going to be in the next version
![]() EDIT: to be clear, it's going to be possible to read from gmem, not write to it. Shader communication is strictly one-way for our purposes. Last edited by frabert; 05-01-2023 at 03:21 PM. Reason: clarification |
![]() |
![]() |
![]() |
#15 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Version 0.6.0 is out!
Not a lot of new features, but gmem is now available to be read from the shaders, under the namespace "ogler". Also the previous generated frame can be used. ![]() https://github.com/frabert/ogler/releases/tag/v0.6.0 Last edited by frabert; 05-05-2023 at 03:13 PM. Reason: Added link to release |
![]() |
![]() |
![]() |
#16 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Version 0.7.0 is out!
No new features, but should be more stable, and there are a few improvements to the editor. https://github.com/frabert/ogler/releases/tag/v0.7.0 I also wrote a simple tutorial for how to write a shader, feedback is welcome! https://github.com/frabert/ogler/blo...cs/Tutorial.md |
![]() |
![]() |
![]() |
#17 |
Human being with feelings
Join Date: May 2020
Location: Spain
Posts: 24
|
![]()
This is impressive, thank you so much frabert! So many possibilites!
At this time, sadly I cant make it to work (copied the snail example but it crashed (I have a AMD integrated gpu, in case it has something to do)). But Im very excited about this! Some quick questions: - Is it possible to take a video as some kind of input? So, lets say track 1 has a normal video, track 2 is a video with noise. Would it be possible to use the noise from track 2 to use it as the sampler2D in texture() and add some kind of distortion to track 1? - Is it possible to change (automate) shader parameters? (like, automating normal parameters in reaper) Again, great work. Thank you so much. To be honest, if this becomes more stable.... it will so powerful for creating lot of different cool effects! It will make reaper video 1000 times better (which is hard, because is already awesome!). |
![]() |
![]() |
![]() |
#18 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Hey Suzuka!
Trying the snail example with an integrated GPU is kind of expected to crash... As I understand it, AMD drivers (and I believe NVIDIA too) will automatically terminate any computation that takes more than 15 seconds to complete. I will try running the snail example again later and see if it works on my end. As for inputs: yes this is possible! See the Reference for all available uniforms/inputs. In particular, Code:
iChannel Code:
input_track You could totally generate some noise using a VideoProcessor script before ogler, and let Ogler use that like a regular texture. EDIT: just tried the snail example. It works on my machine (using an AMD RX570), it's very slow and had to turn the resolution down to 320x240 in order to work smoothly, and put AA to 1 Last edited by frabert; 05-18-2023 at 08:35 AM. Reason: mispelling |
![]() |
![]() |
![]() |
#19 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Just tried the displacement texture trick: works like a charm!
You can put ogler on a folder track, then use subtracks as iChannels |
![]() |
![]() |
![]() |
#20 | |
Human being with feelings
Join Date: May 2020
Location: Spain
Posts: 24
|
![]() Quote:
Its kinda sad that this probably won't get the "love" it deserves, since maybe most Reaper users are not familiar with shaders. But for me (and anyone familiar), this (& you) is/are a genius! Thank you so much for your response, and again for this wonderful tool. Much appreciated, I'm speechless. This is so good that IMHO cockos should pay you and integrate it by default. First of all, it makes Reaper's video to literally have any possible effect out there! And also opens a big door for experimentation. I have always been curious for a video editor that accepted shaders. And here you are. This is wonderful. That way I can automate their RGB knobs as normal, and then in the shader just read the texture/image generated and use each color component to automate anything I want. This is so cool! EDIT: Wait, just read the full topic and you already covered it in a easier way! (#7) Screenshot to your gif (it was hard to see on the preview): ![]() Nevermind, sorry! Just read it in the manual: Code:
OGLER_PARAMS { float my_param; float my_other_param; }; I'm impressed/hyped x10 now (this is much better and handy than the "color/hack thing") ![]() Last edited by Suzuka; 05-19-2023 at 03:23 AM. |
|
![]() |
![]() |
![]() |
#21 |
Banned
Join Date: Apr 2022
Posts: 635
|
![]()
@frabert - wow,thanks so much!
been waiting for this to come along for some time now....and here you are = brilliant.🌟 there are some abo fabo shaders to be compiled,rea_worked and rea_imagined. perfect comes in neat little packages these days. well done 💛 |
![]() |
![]() |
![]() |
#22 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Thanks for the kind words Suzuka and Reapology, I'm happy this is interesting for other people as well
![]() |
![]() |
![]() |
![]() |
#23 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Version 0.8.0 is out!
No new major features, but with this release ogler now uses the CLAP API instead of VST (this solves a legal conundrum with me basically bootlegging their VST2 SDK) which should make things easier going forward. Due to REAPER API limitations that should be fixed shortly (thanks Justin!), this version is only compatible with REAPER 6.79 x64 https://github.com/frabert/ogler/releases/tag/v0.8.0 |
![]() |
![]() |
![]() |
#24 |
Human being with feelings
Join Date: May 2020
Location: Spain
Posts: 24
|
![]()
Thank you! This pluggin is so awesome that, in the case you never update it ever again, I would neither update Reaper ever again just for having this working! 😁
|
![]() |
![]() |
![]() |
#25 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
No need for staying behind with REAPER updates
![]() Release 0.9.0 is available now, and it works from REAPER 6.80 onwards. Apart from not having the ugly hack anymore, now the shaders use the resolution in the project's settings if not otherwise specified. https://github.com/frabert/ogler/releases/tag/v0.9.0 In the meantime, I'm working on migrating the GUI to use imgui instead, which should make it easier to port ogler to other operating systems in the future, but there are still a bunch of bugs I need to figure out. |
![]() |
![]() |
![]() |
#26 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Long time no see!
I updated ogler's GUI to use Sciter -- it should make it easier to add features in the future. I also made a bunch of bugfixes. https://github.com/frabert/ogler/releases/tag/v0.10.0 |
![]() |
![]() |
![]() |
#27 |
Human being with feelings
Join Date: May 2020
Location: Spain
Posts: 24
|
![]()
...to say -again- how freaking awesome this is!
Thank you so much for the updates!! @frabert have you thought about adding it to reapack? (asking from ignorance, not sure if it would be possible, since it is a vst/clap (I've never seen a VST in reapack, I know is meant for scripts, jsfx, etc)). But in case is doable, it would be very handy, and maybe more people will discover it more eassily. For me this is an absolutely gem. ![]() Edit: Solved. I think it was crashing because I didn't had any value at "Preferred video size" in Project Settings. After setting and value and then adding Ogler, no more crashes (0.10.0 in Reaper 6.83). ![]() Edit 2: The new interface looks awesome! Last edited by Suzuka; 10-26-2023 at 12:00 PM. |
![]() |
![]() |
![]() |
#28 | ||
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]() Quote:
Quote:
I'm glad you like it, unfortunately I have not been able to dedicate much time to it lately, but I'll try and get back to it soon enough |
||
![]() |
![]() |
![]() |
#29 | |
Human being with feelings
Join Date: May 2015
Location: Québec, Canada
Posts: 4,683
|
![]() Quote:
https://github.com/cfillion/reapack/...ment-643410870
__________________
💖 Donate (PayPal) | Sponsor (GitHub) | The Endless Journey (Keyboard Ensemble) ReaPack, a package manager for REAPER | SWS pre-releases Developer tools: Lua profiler [new!] | Interactive ReaScript | ReaPack Editor | ReaImGui Last edited by cfillion; 10-27-2023 at 02:56 AM. Reason: s/VSTs/FX plugins/ |
|
![]() |
![]() |
![]() |
#30 | |
Human being with feelings
Join Date: Aug 2010
Location: Germany
Posts: 2,023
|
![]() Quote:
Getting nostalgic when I hear Sciter ![]() BTW, what cfillion mentioned about installing REAPER-specific VSTs into the UserPlugins/FX folder (e.g. via ReaPack), it works very well. It also removes potential problems that other DAWs attempt to load it and makes portable REAPER installations more self-contained. Until recently, this was only working for VST plug-ins but I asked Justin if he could support CLAP plug-ins as well. He added that possibility right away: https://forum.cockos.com/showpost.ph...46&postcount=1 "+ CLAP: automatically scan reaper_resource_path/UserPlugins/FX for .clap plug-ins" |
|
![]() |
![]() |
![]() |
#31 | ||
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]() Quote:
Quote:
Sciter is an embeddable HTML and Javascript engine that I'm using for the GUI, while Scintilla is the text editor component used by ogler and SciTE. SciTE does not use Sciter though... That's not confusing at all haha |
||
![]() |
![]() |
![]() |
#32 | |
Human being with feelings
Join Date: May 2020
Location: Spain
Posts: 24
|
![]() Quote:
![]() And is not only that I like it, is that objectively (IMHO) is one of the best pluggins I can imagine for a video editor. I'm not aware of other video editors that support direct shader code (maybe there are some, but I haven't found them). This literally turns reaper video editor into a monster (in a good way)! Is not that I want to sound annoying, but I do like to recognize when a -freaking awesome- good work is done! |
|
![]() |
![]() |
![]() |
#33 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Ogler is on ReaPack now
![]() |
![]() |
![]() |
![]() |
#34 |
Human being with feelings
Join Date: Feb 2021
Posts: 426
|
![]() |
![]() |
![]() |
![]() |
#35 |
Human being with feelings
Join Date: Aug 2020
Location: Greece
Posts: 43
|
![]() |
![]() |
![]() |
![]() |
#36 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Well the good news is that publishing on Reapack brought in more bug reports!
The bad news is that they are bug reports ![]() I'm sorry, for now the thing I know for sure is that this does not seem to be an issue with your setup but a bug in ogler. |
![]() |
![]() |
![]() |
#38 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
Does REAPER give any indication as to what happens? Any info on crash memory location? Alternatively (and ideally) would you be able to provide me with a minidump?
BTW, I just released version 0.11.0 on GitHub. Could you check if the crash is happening with the new version as well? https://github.com/frabert/ogler/releases/tag/v0.11.0 Last edited by frabert; 11-10-2023 at 03:22 AM. |
![]() |
![]() |
![]() |
#39 |
Human being with feelings
Join Date: Aug 2020
Location: Greece
Posts: 43
|
![]() |
![]() |
![]() |
![]() |
#40 |
Human being with feelings
Join Date: May 2020
Posts: 39
|
![]()
I created a ReaPack repository for unstable ogler releases, please use this in the future:
https://bertolaccini.dev/ogler/unstable.xml I released version 0.12.0 in an attempt to at least diagnose what the crashes everyone has been experiencing are caused by. |
![]() |
![]() |
![]() |
Thread Tools | |
Display Modes | |
|
|