#version 420 #extension GL_ARB_texture_gather : enable #extension GL_ARB_separate_shader_objects : enable // shader d2a97b2fb99411a5 // CRT filter - Just for fun, probably won't be fixing any issues.. layout(binding = 0) uniform sampler2D textureUnitPS0;// Tex0 addr 0xf4481800 res 768x384x1 dim 1 tm: 4 format 001a compSel: 0 1 2 3 mipView: 0x0 (num 0x1) sliceView: 0x0 (num 0x1) Sampler0 ClampX/Y/Z: 2 2 2 border: 1 layout(location = 0) in vec4 passParameterSem0; layout(location = 0) out vec4 passPixelColor0; uniform vec2 uf_fragCoordScale; // // PUBLIC DOMAIN CRT STYLED SCAN-LINE SHADER // // by Timothy Lottes // // This is more along the style of a really good CGA arcade monitor. // With RGB inputs instead of NTSC. // The shadow mask example has the mask rotated 90 degrees for less chromatic aberration. // // Left it unoptimized to show the theory behind the algorithm. // // It is an example what I personally would want as a display option for pixel art games. // Please take and use, change, or whatever. // //old contrasty, or just copy paste clarity const float gamma = 0.95; // 1.0 is neutral const float exposure = 1.2; // 1.0 is neutral, first lessen to avoid truncation prob around .25 for radeon. const float vibrance = 0.175; // 0.0 is neutral const float crushContrast = 0.01; // 0.0 is neutral. loss of shadow detail //const float postExposure = 1.16; // 1.0 is neutral, then slightly raise exposure back up. vec3 contrasty(vec3 colour) { vec3 fColour = (colour.xyz); fColour = clamp(exposure * fColour, 0.0, 1.0); fColour = pow(fColour, vec3(1.0 / gamma)); float luminance = fColour.r*0.299 + fColour.g*0.587 + fColour.b*0.114; float mn = min(min(fColour.r, fColour.g), fColour.b); float mx = max(max(fColour.r, fColour.g), fColour.b); float sat = (1.0 - (mx - mn)) * (1.0 - mx) * luminance * 5.0; vec3 lightness = vec3((mn + mx) / 2.0); // vibrance fColour = mix(fColour, mix(fColour, lightness, -vibrance), sat); fColour = max(vec3(0.0), fColour - vec3(crushContrast)); return fColour; } #define RGBA(r, g, b, a) vec4(float(r)/255.0, float(g)/255.0, float(b)/255.0, float(a)/255.0) const vec3 kBackgroundColor = RGBA(0x00, 0x60, 0xb8, 0xff).rgb; // medium-blue sky //const vec3 kBackgroundColor = RGBA(0xff, 0x00, 0xff, 0xff).rgb; // test magenta //const vec3 kBackgroundColor = RGBA(0x50, 0x50, 0x50, 0xff).rgb; // Emulated input resolution. #if 1 // Fix resolution to set amount. //768x384x1 // Note: 256x224 is the most common resolution of the SNES, and that of Super Mario World. vec2 res = vec2( textureSize(textureUnitPS0, 0).x / 1.0, textureSize(textureUnitPS0, 0).y / 2.0 ); #else // Optimize for resize. vec2 res = textureSize(textureUnitPS0, 0) / 6.0; #endif // Hardness of scanline. // -8.0 = soft // -16.0 = medium float sHardScan = -8.0; // Hardness of pixels in scanline. // -2.0 = soft // -4.0 = hard const float kHardPix = -2.0; // Display warp. // 0.0 = none // 1.0 / 8.0 = extreme const vec2 kWarp = vec2(1.0 / 64.0, 1.0 / 48.0); //const vec2 kWarp = vec2(0); // Amount of shadow mask. float kMaskDark = 2.0; float kMaskLight = 0.5; //------------------------------------------------------------------------ /* // sRGB to Linear. // Assuing using sRGB typed textures this should not be needed. float toLinear1(float c) { return (c <= 0.04045) ? (c / 12.92) : pow((c + 0.055) / 1.055, 2.4); } vec3 toLinear(vec3 c) { return vec3(toLinear1(c.r), toLinear1(c.g), toLinear1(c.b)); } // Linear to sRGB. // Assuing using sRGB typed textures this should not be needed. float toSrgb1(float c) { return(c < 0.0031308 ? (c * 12.92) : (1.055 * pow(c, 0.41666) - 0.055)); } vec3 toSrgb(vec3 c) { return vec3(toSrgb1(c.r), toSrgb1(c.g), toSrgb1(c.b)); } */ // Nearest emulated sample given floating point position and texel offset. // Also zero's off screen. vec4 fetch(vec2 pos, vec2 off) { pos = floor(pos * res + off) / res; if (max(abs(pos.x - 0.5), abs(pos.y - 0.5)) > 0.5) return vec4(vec3(0.0), 0.0); //vec4 sampledColor = texture(textureUnitPS0, pos.xy, -16.0); vec4 sampledColor = texture(textureUnitPS0, pos.xy,0); sampledColor = vec4( (sampledColor.rgb * sampledColor.a) + (kBackgroundColor * (1.0 - sampledColor.a)), 1.0 ); return vec4( //toLinear(sampledColor.rgb), //sampledColor.a sampledColor.rgba ); } // Distance in emulated pixels to nearest texel. vec2 dist(vec2 pos) { pos = pos * res; return -((pos - floor(pos)) - vec2(0.5)); } // 1D Gaussian. float gaus(float pos, float scale) { return exp2(scale * pos * pos); } // 3-tap Gaussian filter along horz line. vec3 horz3(vec2 pos, float off) { vec3 b = fetch(pos, vec2(-1.0, off)).rgb; vec3 c = fetch(pos, vec2(0.0, off)).rgb; vec3 d = fetch(pos, vec2(+1.0, off)).rgb; float dst = dist(pos).x; // Convert distance to weight. float scale = kHardPix; float wb = gaus(dst - 1.0, scale); float wc = gaus(dst + 0.0, scale); float wd = gaus(dst + 1.0, scale); // Return filtered sample. return (b * wb + c * wc + d * wd) / (wb + wc + wd); } // 5-tap Gaussian filter along horz line. vec3 horz5(vec2 pos, float off) { vec3 a = fetch(pos, vec2(-2.0, off)).rgb; vec3 b = fetch(pos, vec2(-1.0, off)).rgb; vec3 c = fetch(pos, vec2(0.0, off)).rgb; vec3 d = fetch(pos, vec2(+1.0, off)).rgb; vec3 e = fetch(pos, vec2(+2.0, off)).rgb; float dst = dist(pos).x; // Convert distance to weight. float scale = kHardPix; float wa = gaus(dst - 2.0, scale); float wb = gaus(dst - 1.0, scale); float wc = gaus(dst + 0.0, scale); float wd = gaus(dst + 1.0, scale); float we = gaus(dst + 2.0, scale); // Return filtered sample. return (a * wa + b * wb + c * wc + d * wd + e * we) / (wa + wb + wc + wd + we); } // Return scanline weight. float scan(vec2 pos, float off) { float dst = dist(pos).y; return gaus(dst + off, sHardScan); } // Allow nearest three lines to effect pixel. vec3 tri(vec2 pos) { vec3 a = horz3(pos, -1.0); vec3 b = horz5(pos, 0.0); vec3 c = horz3(pos, +1.0); float wa = scan(pos, -1.0); float wb = scan(pos, 0.0); float wc = scan(pos, +1.0); return a * wa + b * wb + c * wc; } // Distortion of scanlines, and end of screen alpha. vec2 warp(vec2 pos) { pos = pos * 2.0 - 1.0; pos *= vec2( 1.0 + (pos.y * pos.y) * kWarp.x, 1.0 + (pos.x * pos.x) * kWarp.y ); return pos * 0.5 + 0.5; } // Shadow mask. vec3 mask(vec2 pos) { pos.x += pos.y * 3.0; vec3 mask = vec3(kMaskDark, kMaskDark, kMaskDark); pos.x = fract(pos.x / 6.0); if (pos.x < 0.333) mask.r = kMaskLight; else if (pos.x < 0.666) mask.g = kMaskLight; else mask.b = kMaskLight; return mask; } // Draw dividing bars. float bar(float pos, float bar) { pos -= bar; return (pos * pos < 4.0) ? 0.0 : 1.0; } float rand(vec2 co) { return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); } int clampFI32(int v) { if( v == 0x7FFFFFFF ) return floatBitsToInt(1.0); else if( v == 0xFFFFFFFF ) return floatBitsToInt(0.0); return floatBitsToInt(clamp(intBitsToFloat(v), 0.0, 1.0)); } float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; } void main() { vec4 R0f = vec4(0.0); float backupReg0f, backupReg1f, backupReg2f, backupReg3f, backupReg4f; vec4 PV0f = vec4(0.0), PV1f = vec4(0.0); float PS0f = 0.0, PS1f = 0.0; vec4 tempf = vec4(0.0); float tempResultf; int tempResulti; ivec4 ARi = ivec4(0); bool predResult = true; vec3 cubeMapSTM; int cubeMapFaceId; R0f = passParameterSem0; //R0f.xyzw = (texture(textureUnitPS0, R0f.xy).xyzw); // export //passPixelColor0 = vec4(R0f.x, R0f.y, R0f.z, R0f.w); vec2 pos = warp(gl_FragCoord.xy / textureSize(textureUnitPS0, 0));//iResolution.xy); vec4 unmodifiedColor = fetch(pos, vec2(0)); // Unmodified. if (passPixelColor0.x > textureSize(textureUnitPS0, 0).x * 0.333) { passPixelColor0.rgb = contrasty(unmodifiedColor.rgb); } else { if (passPixelColor0.x > textureSize(textureUnitPS0, 0).x * 0.666) { sHardScan = -12.0; kMaskDark = kMaskLight = 1.0; } passPixelColor0.rgb = tri(pos) * mask(gl_FragCoord.xy); } //passPixelColor0.rgb *= bar(gl_FragCoord.x, textureSize(textureUnitPS0, 0).x * 0.333) * bar(gl_FragCoord.x, textureSize(textureUnitPS0, 0).x * 0.666); //passPixelColor0 = vec4( //toSrgb(passPixelColor0.rgb), //1.0 //); passPixelColor0 = vec4(passPixelColor0.rgb, 1.0); }