Josh Posted November 26, 2016 Share Posted November 26, 2016 Not nearly working, although the code looks right to me. Vert: #version 400 uniform mat4 projectionmatrix; uniform mat4 drawmatrix; uniform vec2 offset; uniform vec2 position[4]; in vec3 vertex_position; void main(void) { gl_Position = projectionmatrix * (drawmatrix * vec4(position[gl_VertexID]+offset, 0.0, 1.0)); } Frag: #version 400 uniform sampler2DMS texture0;//depth uniform sampler2D texture1;//color uniform sampler2DMS texture2;//normal uniform bool isbackbuffer; uniform vec2 buffersize; uniform vec2 camerarange; uniform float camerazoom; uniform mat3 camerainversenormalmatrix; out vec4 fragData0; float depthToPosition(in float depth, in vec2 depthrange) { return depthrange.x / (depthrange.y - depth * (depthrange.y - depthrange.x)) * depthrange.y; } float LinePointDistance(in vec3 v, in vec3 w, in vec3 p) { // Return minimum distance between line segment vw and point p vec3 d = w-v; float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt if (l2 == 0.0) return distance(p, v); // v == w case // Consider the line extending the segment, parameterized as v + t (w - v). // We find projection of point p onto the line. // It falls where t = [(p-v) . (w-v)] / |w-v|^2 // We clamp t from [0,1] to handle points outside the segment vw. float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2)); vec3 projection = v + t * (w - v); // Projection falls on the segment return distance(p, projection); } void main(void) { //---------------------------------------------------------------------- //Calculate screen texcoord //---------------------------------------------------------------------- vec2 coord = gl_FragCoord.xy / buffersize; if (isbackbuffer) coord.y = 1.0 - coord.y; ivec2 icoord = ivec2(gl_FragCoord.xy); if (isbackbuffer) icoord.y = int(buffersize.y) - icoord.y; //Get the original color vec4 c = texture(texture1,coord); //---------------------------------------------------------------------- //Calculate screen position and vector //---------------------------------------------------------------------- float depth = texelFetch(texture0,icoord,0).x; vec3 screencoord = vec3(((gl_FragCoord.x/buffersize.x)-0.5) * 2.0 * (buffersize.x/buffersize.y),((-gl_FragCoord.y/buffersize.y)+0.5) * 2.0,depthToPosition(depth,camerarange)); screencoord.x *= screencoord.z / camerazoom; screencoord.y *= -screencoord.z / camerazoom; vec3 screennormal = normalize(screencoord); if (!isbackbuffer) screencoord.y *= -1.0; //Get the normal at this pixel vec3 pixelnormal = normalize(texelFetch(texture2,icoord,0).xyz * 2.0 - 1.0); //Raytrace settings const int maxSteps = 1000; float stepSize = 1.0 / buffersize.x * 1.0; const float hitThreshold = 0.5; //2D Vector for stepping along texture vec3 dir = vec3(normalize(pixelnormal.xy),pixelnormal.z); dir.z *= dir.x / pixelnormal.x; vec4 reflection = vec4(0.0f); for (int i=0; i<maxSteps; ++i) { //Calculate new tex coord coord -= dir.xy * stepSize; if (coord.x<0.0f || coord.y<0.0f || coord.x>1.0f || coord.y>1.0f) break; icoord = ivec2(coord); //Get this pixel's screen position depth = texelFetch(texture0,icoord,0).x; vec3 screencoord1 = vec3(((coord.x)-0.5) * 2.0 * (buffersize.x/buffersize.y),((-coord.y)+0.5) * 2.0,depthToPosition(depth,camerarange)); screencoord1.x *= screencoord1.z / camerazoom; screencoord1.y *= -screencoord1.z / camerazoom; //Test the distance between this pixel's position and the ray to see if it hits this point if (LinePointDistance(screencoord,screencoord+dir,screencoord1)<hitThreshold) { //Get this point's normal vec3 pixelnormal1 = normalize(texelFetch(texture2,icoord,0).xyz * 2.0 - 1.0); //Compare to see if the pixels face each other if (dot(pixelnormal,pixelnormal1) < 0.0f) { //Look up this pixel's color reflection = texture(texture1,coord); break; } } } fragData0 = c + reflection; } 2 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
shadmar Posted November 26, 2016 Share Posted November 26, 2016 Igors and mine fixed and using normal.a roughness map channel: shader sslrenv.zip 3 Quote HP Omen - 16GB - i7 - Nvidia GTX 1060 6GB Link to comment Share on other sites More sharing options...
DooMAGE Posted November 26, 2016 Share Posted November 26, 2016 Will this work with AMD cards too? =] Quote My Leadwerks games! https://ragingmages.itch.io/ Link to comment Share on other sites More sharing options...
Josh Posted November 26, 2016 Author Share Posted November 26, 2016 The work you guys did is great. I want to eliminate the remaining errors, figure out a better way to blend in the visible area, and try to get it to work together with the GI cubemaps. When the reflection is available, the SSLR reflection should be used, and when it's not the GI should take over, on a per-pixel basis. 2 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted November 26, 2016 Author Share Posted November 26, 2016 If you comment out this line you get a much better result the sides of walls: normalView.z *= roughness * 2.0f; Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted November 26, 2016 Author Share Posted November 26, 2016 Check it out! I added a test to see if the normal of the picked pixel faces the reflection vector. This ensures that only surfaces that are actually facing the surfaces can be reflected. Notice the bottom of that edge on the machine does not reflect, because there is no reflection information to use. I also made it so the routine checks the distance between the collided point to see if the point actually lies along the raytrace ray. This eliminates all those bad reflections and ensures only actual reflections get displayed. /* //--------------------------------------------------------------------------- -- SSLR (Screen-Space Local Reflections) by Igor Katrich and Shadmar 26/03/2015 -- Email: igorbgz@outlook.com //---------------------------------------------------------------------------*/ #version 400 uniform sampler2DMS texture0; uniform sampler2D texture1; uniform sampler2DMS texture2; uniform sampler2DMS texture3; uniform bool isbackbuffer; uniform vec2 buffersize; uniform mat4 projectioncameramatrix; uniform vec3 cameraposition; uniform mat3 cameranormalmatrix; uniform mat3 camerainversenormalmatrix; //User variable's #define reflectionfalloff 10.0f #define raylength 1.1f #define maxstep 10 #define edgefadefactor 0.95f #define hitThreshold 0.1 out vec4 fragData0; vec4 getPosition(in vec2 texCoord, out float z) { float x = texCoord.s * 2.0f - 1.0f; float y = texCoord.t * 2.0f - 1.0f; z = texelFetch(texture0, ivec2(texCoord*buffersize),0).r; vec4 posProj = vec4(x,y,z,1.0f); vec4 posView = inverse(projectioncameramatrix) * posProj; posView /= posView.w; return posView; } float LinePointDistance(in vec3 v, in vec3 w, in vec3 p) { // Return minimum distance between line segment vw and point p vec3 d = w-v; float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt if (l2 == 0.0) return distance(p, v); // v == w case //float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2)); float t = dot(p - v, w - v) / l2; vec3 projection = v + t * (w - v); // Projection falls on the segment return distance(p, projection); } void main(void) { vec2 icoord = vec2(gl_FragCoord.xy/buffersize); if (isbackbuffer) icoord.y = 1.0f - icoord.y; //Get screen color vec4 color = texture(texture1,icoord); //Get normal + alpha channel. vec4 n=texelFetch(texture2, ivec2(icoord*buffersize),0); vec3 normalView = normalize(n.xyz * 2.0f - 1.0f); //Get roughness from gbuffer (normal.a) int materialflags = int(n.a*255.0+0.5); int roughness=1; if ((32 & materialflags)!=0) roughness += 4; if ((64 & materialflags)!=0) roughness += 2; roughness=1; //Get specmap from gbuffer float specularity = texelFetch(texture3, ivec2(icoord*buffersize),0).a; specularity=1.0; //only compute if we hvae specularity if (specularity > 0.0f) { //Get position and out depth (z) float z; vec3 posView = getPosition(icoord,z).xyz; //if (localposView.z<0.0f) break; //normal distort by rougness //normalView.z *= roughness * 2.0f; //Reflect vector vec4 reflectedColor = color; vec3 reflected = normalize(reflect(normalize(posView-cameraposition), normalView)); float rayLength = raylength; vec4 T = vec4(0.0f); vec3 newPos; //Raytrace for (int i = 0; i < maxstep; i++) { newPos = posView + reflected * rayLength; T = projectioncameramatrix * vec4(newPos, 1.0f); T.xy = vec2(0.5f) + 0.5f * T.xy / T.w; T.z /= T.w; if (abs(z - T.z) < 1.0f && T.x <= 1.0f && T.x >= 0.0f && T.y <= 1.0f && T.y >= 0.0f) { float depth; newPos = getPosition(T.xy,depth).xyz; rayLength = length(posView - newPos); //Check distance of this pixel to the reflection ray. If it's close enough we count it as a hit. if (LinePointDistance(posView,posView+reflected,newPos) < hitThreshold) { //Get the pixel at this normal vec4 n1=texelFetch(texture2, ivec2(T.xy*buffersize),0); vec3 normalView1 = normalize(n1.xyz * 2.0f - 1.0f); //Make sure the pixel faces the reflection vector if (dot(reflected,normalView1)<0.0f) { float m = max(1.0f-T.y,0.0f); m = max(1.0f-T.x,m); m += roughness * 0.1f; vec4 rcol=texture(texture1,T.xy); reflectedColor = mix(rcol,color,clamp(m,0.0f,1.0f)); //We hit the pixel, so we're done, right? break; } } } else { break;//exit because we're out of the texture } } //Fading to screen edges vec2 fadeToScreenEdge = vec2(1.0f); fadeToScreenEdge.x = distance(icoord.x , 1.0f); fadeToScreenEdge.x *= distance(icoord.x, 0.0f) * 4.0f; fadeToScreenEdge.y = distance(icoord.y, 1.0f); fadeToScreenEdge.y *= distance(icoord.y, 0.0f) * 4.0f; float fresnel = reflectionfalloff*(1.0f-(pow(dot(normalize(posView-cameraposition), normalize(normalView)), 2.0f))); color = mix(color, reflectedColor,clamp(fresnel*pow(fadeToScreenEdge.x * fadeToScreenEdge.y,edgefadefactor)*(specularity),0.0f,1.0f)); } fragData0 = color; } 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted November 27, 2016 Author Share Posted November 27, 2016 Now to remove those ugly edges, we also fade the reflection out based on the reflected pixel's coordinate as well. This fixes a lot of ugly lines: /* //--------------------------------------------------------------------------- -- SSLR (Screen-Space Local Reflections) by Igor Katrich and Shadmar 26/03/2015 -- Email: igorbgz@outlook.com //---------------------------------------------------------------------------*/ #version 400 uniform sampler2DMS texture0; uniform sampler2D texture1; uniform sampler2DMS texture2; uniform sampler2DMS texture3; uniform bool isbackbuffer; uniform vec2 buffersize; uniform mat4 projectioncameramatrix; uniform vec3 cameraposition; uniform mat3 cameranormalmatrix; uniform mat3 camerainversenormalmatrix; //User variable's #define reflectionfalloff 10.0f #define raylength 1.1f #define maxstep 10 #define edgefadefactor 0.95f #define hitThreshold 0.1 out vec4 fragData0; vec4 getPosition(in vec2 texCoord, out float z) { float x = texCoord.s * 2.0f - 1.0f; float y = texCoord.t * 2.0f - 1.0f; z = texelFetch(texture0, ivec2(texCoord*buffersize),0).r; vec4 posProj = vec4(x,y,z,1.0f); vec4 posView = inverse(projectioncameramatrix) * posProj; posView /= posView.w; return posView; } float LinePointDistance(in vec3 v, in vec3 w, in vec3 p) { // Return minimum distance between line segment vw and point p vec3 d = w-v; float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt if (l2 == 0.0) return distance(p, v); // v == w case //float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2)); float t = dot(p - v, w - v) / l2; vec3 projection = v + t * (w - v); // Projection falls on the segment return distance(p, projection); } void main(void) { vec2 icoord = vec2(gl_FragCoord.xy/buffersize); if (isbackbuffer) icoord.y = 1.0f - icoord.y; //Get screen color vec4 color = texture(texture1,icoord); //Get normal + alpha channel. vec4 n=texelFetch(texture2, ivec2(icoord*buffersize),0); vec3 normalView = normalize(n.xyz * 2.0f - 1.0f); //Get roughness from gbuffer (normal.a) int materialflags = int(n.a*255.0+0.5); int roughness=1; //if ((32 & materialflags)!=0) roughness += 4; //if ((64 & materialflags)!=0) roughness += 2; //Get specmap from gbuffer float specularity = texelFetch(texture3, ivec2(icoord*buffersize),0).a; //only compute if we hvae specularity if (specularity > 0.0f) { //Get position and out depth (z) float z; vec3 posView = getPosition(icoord,z).xyz; //Reflect vector vec4 reflectedColor = color; vec3 reflected = normalize(reflect(normalize(posView-cameraposition), normalView)); float rayLength = raylength; vec4 T = vec4(0.0f); vec3 newPos; //Raytrace for (int i = 0; i < maxstep; i++) { newPos = posView + reflected * rayLength; T = projectioncameramatrix * vec4(newPos, 1.0f); T.xy = vec2(0.5f) + 0.5f * T.xy / T.w; T.z /= T.w; if (abs(z - T.z) < 1.0f && T.x <= 1.0f && T.x >= 0.0f && T.y <= 1.0f && T.y >= 0.0f) { float depth; newPos = getPosition(T.xy,depth).xyz; rayLength = length(posView - newPos); //Check distance of this pixel to the reflection ray. If it's close enough we count it as a hit. if (LinePointDistance(posView,posView+reflected,newPos) < hitThreshold) { //Get the pixel at this normal vec4 n1=texelFetch(texture2, ivec2(T.xy*buffersize),0); vec3 normalView1 = normalize(n1.xyz * 2.0f - 1.0f); //Make sure the pixel faces the reflection vector if (dot(reflected,normalView1)<0.0f) { float m = max(1.0f-T.y,0.0f); m = max(1.0f-T.x,m); m += roughness * 0.1f; vec4 rcol=texture(texture1,T.xy); reflectedColor = mix(rcol,color,clamp(m,0.0f,1.0f)); //Fading to screen edges vec2 fadeToScreenEdge = vec2(1.0f); fadeToScreenEdge.x = distance(icoord.x , 1.0f); fadeToScreenEdge.x *= distance(icoord.x, 0.0f) * 4.0f; fadeToScreenEdge.y = distance(icoord.y, 1.0f); fadeToScreenEdge.y *= distance(icoord.y, 0.0f) * 4.0f; //Also fade by the reflected pixel's coordinate to eliminate ugly lines fadeToScreenEdge.x *= distance(T.x , 1.0f); fadeToScreenEdge.x *= distance(T.x, 0.0f) * 4.0f; fadeToScreenEdge.y *= distance(T.y, 1.0f); fadeToScreenEdge.y *= distance(T.y, 0.0f) * 4.0f; float fresnel = reflectionfalloff*(1.0f-(pow(dot(normalize(posView-cameraposition), normalize(normalView)), 2.0f))); color = mix(color, reflectedColor,clamp(fresnel*pow(fadeToScreenEdge.x * fadeToScreenEdge.y,edgefadefactor)*(specularity),0.0f,1.0f)); //We hit the pixel, so we're done, right? break; } } } else { break;//exit because we're out of the texture } } } fragData0 = color; } 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted November 27, 2016 Author Share Posted November 27, 2016 When you combine this with a normal mapped surface it does a good job of hiding the imperfections and it looks FANTASTIC!!! 2 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
DooMAGE Posted November 27, 2016 Share Posted November 27, 2016 Looks good indeed. 4 Quote My Leadwerks games! https://ragingmages.itch.io/ Link to comment Share on other sites More sharing options...
Josh Posted November 27, 2016 Author Share Posted November 27, 2016 There's actually no need to fade out near the screen edge. You just need to fade out when the reflected pixel is near the screen edge: //Fading to screen edges vec2 fadeToScreenEdge = vec2(1.0f); //fadeToScreenEdge.x = distance(icoord.x , 1.0f); //fadeToScreenEdge.x *= distance(icoord.x, 0.0f) * 4.0f; //fadeToScreenEdge.y = distance(icoord.y, 1.0f); //fadeToScreenEdge.y *= distance(icoord.y, 0.0f) * 4.0f; //Also fade by the reflected pixel's coordinate to eliminate ugly lines fadeToScreenEdge.x *= distance(T.x , 1.0f); fadeToScreenEdge.x *= distance(T.x, 0.0f) * 4.0f; fadeToScreenEdge.y *= distance(T.y, 1.0f); fadeToScreenEdge.y *= distance(T.y, 0.0f) * 4.0f; Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
reepblue Posted November 27, 2016 Share Posted November 27, 2016 Really cool. Maybe if polished enough, it can be a standard scene option? This way the user doesn't need to worry about having this shader on top of the stack like the old one. A tick-box option for this would be great. 2 Quote Cyclone - Ultra Game System - Component Preprocessor - Tex2TGA - Darkness Awaits Template (Leadwerks) If you like my work, consider supporting me on Patreon! Link to comment Share on other sites More sharing options...
Josh Posted November 27, 2016 Author Share Posted November 27, 2016 This interacts with the GI cubemapping so eventually it should be built into the engine and not treated as a post-effect. I need to make it write into the stencil buffer or something to prevent pixels from receiving static GI lighting. I bet we could do some pretty nice water that doesn't require a second render of everything. That would make scenes with water not really any more demanding than without. I still do not understanding how this code is providing such accurate high-res results with so few samples! 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted November 27, 2016 Author Share Posted November 27, 2016 Improved the reflection blending and the edge fade: /* //--------------------------------------------------------------------------- -- SSLR (Screen-Space Local Reflections) by Igor Katrich and Shadmar 26/03/2015 -- Email: igorbgz@outlook.com //---------------------------------------------------------------------------*/ #version 400 uniform sampler2DMS texture0; uniform sampler2D texture1; uniform sampler2DMS texture2; uniform sampler2DMS texture3; uniform bool isbackbuffer; uniform vec2 buffersize; uniform mat4 projectioncameramatrix; uniform vec3 cameraposition; uniform mat3 cameranormalmatrix; uniform mat3 camerainversenormalmatrix; //User variables #define reflectionfalloff 10.0f #define raylength 1.1f #define maxstep 10 #define edgefadefactor 0.95f #define hitThreshold 0.1 out vec4 fragData0; vec4 getPosition(in vec2 texCoord, out float z) { float x = texCoord.s * 2.0f - 1.0f; float y = texCoord.t * 2.0f - 1.0f; z = texelFetch(texture0, ivec2(texCoord*buffersize),0).r; vec4 posProj = vec4(x,y,z,1.0f); vec4 posView = inverse(projectioncameramatrix) * posProj; posView /= posView.w; return posView; } float LinePointDistance(in vec3 v, in vec3 w, in vec3 p) { // Return minimum distance between line segment vw and point p vec3 d = w-v; float l2 = d.x*d.x+d.y*d.y+d.z*d.z; // i.e. |w-v|^2 - avoid a sqrt if (l2 == 0.0) return distance(p, v); // v == w case //float t = max(0.0f, min(1.0f, dot(p - v, w - v) / l2)); float t = dot(p - v, w - v) / l2; vec3 projection = v + t * (w - v); // Projection falls on the segment return distance(p, projection); } void main(void) { vec2 icoord = vec2(gl_FragCoord.xy/buffersize); if (isbackbuffer) icoord.y = 1.0f - icoord.y; //Get screen color vec4 color = texture(texture1,icoord); //Get normal + alpha channel. vec4 n=texelFetch(texture2, ivec2(icoord*buffersize),0); vec3 normalView = normalize(n.xyz * 2.0f - 1.0f); //Get roughness from gbuffer (normal.a) int materialflags = int(n.a*255.0+0.5); int roughness=1; //if ((32 & materialflags)!=0) roughness += 4; //if ((64 & materialflags)!=0) roughness += 2; //Get specmap from gbuffer float specularity = texelFetch(texture3, ivec2(icoord*buffersize),0).a; //only compute if we hvae specularity if (specularity > 0.0f) { //Get position and out depth (z) float z; vec3 posView = getPosition(icoord,z).xyz; //Reflect vector vec4 reflectedColor = color; vec3 reflected = normalize(reflect(normalize(posView-cameraposition), normalView)); float rayLength = raylength; vec4 T = vec4(0.0f); vec3 newPos; //Raytrace for (int i = 0; i < maxstep; i++) { newPos = posView + reflected * rayLength; T = projectioncameramatrix * vec4(newPos, 1.0f); T.xy = vec2(0.5f) + 0.5f * T.xy / T.w; T.z /= T.w; if (abs(z - T.z) < 1.0f && T.x <= 1.0f && T.x >= 0.0f && T.y <= 1.0f && T.y >= 0.0f) { float depth; newPos = getPosition(T.xy,depth).xyz; rayLength = length(posView - newPos); //Check distance of this pixel to the reflection ray. If it's close enough we count it as a hit. if (LinePointDistance(posView,posView+reflected,newPos) < hitThreshold) { //Get the pixel at this normal vec4 n1=texelFetch(texture2, ivec2(T.xy*buffersize),0); vec3 normalView1 = normalize(n1.xyz * 2.0f - 1.0f); //Make sure the pixel faces the reflection vector if (dot(reflected,normalView1)<0.0f) { /*float m = max(1.0f-T.y,0.0f); m = max(1.0f-T.x,m); m += roughness * 0.1f; */ float m = 0.5; vec4 rcol=texture(texture1,T.xy); //reflectedColor = mix(rcol,color,clamp(m,0.0f,1.0f)); reflectedColor = rcol + color; //Fading to screen edges vec2 fadeToScreenEdge = vec2(1.0f); float edgedistance[2]; edgedistance[1] = 0.5; edgedistance[0] = edgedistance[1] * (buffersize.y / buffersize.x); if (T.x<edgedistance[0]) { fadeToScreenEdge.x = T.x / edgedistance[0]; } else if (T.x > 1.0 - edgedistance[0]) { fadeToScreenEdge.x = 1.0 - ((T.x - (1.0 - edgedistance[0])) / edgedistance[0]); } if (T.y<edgedistance[1]) { fadeToScreenEdge.y = T.y / edgedistance[1]; } else if (T.y>1.0-edgedistance[1]) { fadeToScreenEdge.y = 1.0 - (T.y - (1.0-edgedistance[1])) / edgedistance[1]; } float fresnel = reflectionfalloff * (1.0f-(pow(dot(normalize(posView-cameraposition), normalize(normalView)), 2.0f))); fresnel = clamp(fresnel,0.0f,1.0f); color = mix(color, reflectedColor,clamp(fresnel * fadeToScreenEdge.x * fadeToScreenEdge.y * specularity, 0.0f, 1.0f)); //We hit the pixel, so we're done, right? break; } } } else { break;//exit because we're out of the texture } } } fragData0 = color; } 2 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
shadmar Posted November 27, 2016 Share Posted November 27, 2016 Works very well Quote HP Omen - 16GB - i7 - Nvidia GTX 1060 6GB Link to comment Share on other sites More sharing options...
Josh Posted December 17, 2016 Author Share Posted December 17, 2016 Here is my final version. ssr.zip 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.