1// Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved.2//3// Permission is hereby granted, free of charge, to any person obtaining a copy4// of this software and associated documentation files (the "Software"), to deal5// in the Software without restriction, including without limitation the rights6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell7// copies of the Software, and to permit persons to whom the Software is8// furnished to do so, subject to the following conditions:9// The above copyright notice and this permission notice shall be included in10// all copies or substantial portions of the Software.11//12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR13// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,14// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE15// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER16// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN18// THE SOFTWARE.20#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput22struct CASUniforms {23 sharpness: f32,24};26@group(0) @binding(0) var screenTexture: texture_2d<f32>;27@group(0) @binding(1) var samp: sampler;28@group(0) @binding(2) var<uniform> uniforms: CASUniforms;30// This is set at the limit of providing unnatural results for sharpening.31const FSR_RCAS_LIMIT = 0.1875;32// -4.0 instead of -1.0 to avoid issues with MSAA.33const peakC = vec2<f32>(10.0, -40.0);35// Robust Contrast Adaptive Sharpening (RCAS)36// Based on the following implementation:37// https://github.com/GPUOpen-Effects/FidelityFX-FSR2/blob/ea97a113b0f9cadf519fbcff315cc539915a3acd/src/ffx-fsr2-api/shaders/ffx_fsr1.h#L67238// RCAS is based on the following logic.39// RCAS uses a 5 tap filter in a cross pattern (same as CAS),40// W b41// W 1 W for taps d e f 42// W h43// Where 'W' is the negative lobe weight.44// output = (W*(b+d+f+h)+e)/(4*W+1)45// RCAS solves for 'W' by seeing where the signal might clip out of the {0 to 1} input range,46// 0 == (W*(b+d+f+h)+e)/(4*W+1) -> W = -e/(b+d+f+h)47// 1 == (W*(b+d+f+h)+e)/(4*W+1) -> W = (1-e)/(b+d+f+h-4)48// Then chooses the 'W' which results in no clipping, limits 'W', and multiplies by the 'sharp' amount.49// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues.50// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps.51// As well as switching from 'e' to either the minimum or maximum (depending on side), to help in energy conservation.52// This stabilizes RCAS.53// RCAS does a simple highpass which is normalized against the local contrast then shaped,54// 0.2555// 0.25 -1 0.2556// 0.2557// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges.58// The CAS node runs after tonemapping, so the input will be in the range of 0 to 1.59@fragment60fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {61 // Algorithm uses minimal 3x3 pixel neighborhood.62 // b63 // d e f64 // h65 let b = textureSample(screenTexture, samp, in.uv, vec2<i32>(0, -1)).rgb;66 let d = textureSample(screenTexture, samp, in.uv, vec2<i32>(-1, 0)).rgb;67 // We need the alpha value of the pixel we're working on for the output68 let e = textureSample(screenTexture, samp, in.uv).rgba;69 let f = textureSample(screenTexture, samp, in.uv, vec2<i32>(1, 0)).rgb;70 let h = textureSample(screenTexture, samp, in.uv, vec2<i32>(0, 1)).rgb;71 // Min and max of ring.72 let mn4 = min(min(b, d), min(f, h));73 let mx4 = max(max(b, d), max(f, h));74 // Limiters75 // 4.0 to avoid issues with MSAA.76 let hitMin = mn4 / (4.0 * mx4);77 let hitMax = (peakC.x - mx4) / (peakC.y + 4.0 * mn4);78 let lobeRGB = max(-hitMin, hitMax);79 var lobe = max(-FSR_RCAS_LIMIT, min(0.0, max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)))) * uniforms.sharpness;80#ifdef RCAS_DENOISE81 // Luma times 2.82 let bL = b.b * 0.5 + (b.r * 0.5 + b.g);83 let dL = d.b * 0.5 + (d.r * 0.5 + d.g);84 let eL = e.b * 0.5 + (e.r * 0.5 + e.g);85 let fL = f.b * 0.5 + (f.r * 0.5 + f.g);86 let hL = h.b * 0.5 + (h.r * 0.5 + h.g);87 // Noise detection.88 var noise = 0.25 * bL + 0.25 * dL + 0.25 * fL + 0.25 * hL - eL;;89 noise = saturate(abs(noise) / (max(max(bL, dL), max(fL, hL)) - min(min(bL, dL), min(fL, hL))));90 noise = 1.0 - 0.5 * noise;91 // Apply noise removal.92 lobe *= noise;93#endif94 return vec4<f32>((lobe * b + lobe * d + lobe * f + lobe * h + e.rgb) / (4.0 * lobe + 1.0), e.w);95}