From 11f66dc30656b9e1aaeff1b3c69cafb9c69bea30 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Sat, 27 Nov 2021 08:32:11 +0100 Subject: [PATCH] examples: Add Sub-Pixel Morphological Anti-Aliasing This requires a bit more complex setup compared to what you'd otherwise see, but it does work. Adapted from: https://github.com/iryoku/smaa --- data/examples/shaders/filter/smaa.effect | 1605 +++++++++++++++++ data/examples/shaders/filter/smaa/areatex.png | Bin 0 -> 46617 bytes .../shaders/filter/smaa/searchtex.png | Bin 0 -> 153 bytes 3 files changed, 1605 insertions(+) create mode 100644 data/examples/shaders/filter/smaa.effect create mode 100644 data/examples/shaders/filter/smaa/areatex.png create mode 100644 data/examples/shaders/filter/smaa/searchtex.png diff --git a/data/examples/shaders/filter/smaa.effect b/data/examples/shaders/filter/smaa.effect new file mode 100644 index 0000000..bfe2b31 --- /dev/null +++ b/data/examples/shaders/filter/smaa.effect @@ -0,0 +1,1605 @@ +// SMAA from https://github.com/iryoku/smaa +// Adjusted for StreamFX by Michael Fabian 'Xaymar' Dirks +// +// Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) +// Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) +// Copyright (C) 2013 Belen Masia (bmasia@unizar.es) +// Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) +// Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. As clarification, there is no +// requirement that the copyright notice and permission be included in binary +// distributions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// How to use: +// 1. Find something you want to Anti-Alias, preferably without transparency. +// 2. Create two different Source Mirrors for what you found in step 1. +// We'll call them "A" and "B" from now on, but you might call them anything. +// 3. To the Source "A": +// 1. Add a Shader filter pointing at this file, we'll call it "SMAA Pass 1". +// 2. In "SMAA Pass 1", set the Technique to either "LumaEdgeDetection" or "ColorEdgeDetection". +// 3. In "SMAA Pass 1", adjust the threshold for edge detection to your liking. The default works for most cases. +// 4. To the Source "A": +// 1. Add a Shader filter pointing at this file, we'll call it "SMAA Pass 2". +// 2. In "SMAA Pass 2", set the Technique to "BlendingWeightCalculation" +// 3. In "SMAA Pass 2", set "Area Texture" to the included "areatex.png". +// 4. In "SMAA Pass 2", set "Search Texture" to the included "searchtex.png". +// 5. In "SMAA Pass 2", adjust configuration values as necessary. +// 5. To the Source "B": +// 1. Add a Shader filter pointing at this file, we'll call it "SMAA Pass 3". +// 2. In "SMAA Pass 3", set "Previous Pass" to the Source "A". +// 6. Done! You now have SMAA in OBS. + +// ================================================================================ // +// Beware, reader! Beyond this point lie dragons and magic! +// ================================================================================ // + +#define IS_FILTER +#include "../base.effect" + +uniform float _100_Threshold< + string name = "Threshold"; + string field_type = "slider"; + float minimum = 0.; + float maximum = 50.; + float step = .01; + float scale = .01; +> = 10.; + +uniform int _110_MaxSearchSteps< + string name = "Search Steps"; + string field_type = "slider"; + int minimum = 0; + int maximum = 112; + int step = 1; +> = 16; + +uniform int _120_MaxDiagonalSearchSteps< + string name = "Diagonal Search Steps"; + string field_type = "slider"; + int minimum = 0; + int maximum = 20; + int step = 1; +> = 8; + +uniform float _130_CornerRounding< + string name = "Corner Rounding"; + string suffix = " %"; + string field_type = "slider"; + float minimum = 0.; + float maximum = 100.; + float step = .01; + float scale = 1.; +> = 25.; + +uniform texture2d _200_PreviousPass< + string name = "Previous Pass"; + string field_type = "input"; +>; + +uniform texture2d _999_AreaTexture< + // bool visible = false; + string name = "Area Texture"; + string field_type = "input"; + string enum_0 = "./smaa/areatex.png"; + string enum_0_name = "Area Texture"; +>; + +uniform texture2d _999_SearchTexture< + // bool visible = false; + string name = "Search Texture"; + string field_type = "input"; + string enum_0 = "./smaa/searchtex.png"; + string enum_0_name = "Search Texture"; +>; + +#define SMAA_RT_METRICS float4(ViewSize.zw, ViewSize.xy) +#define SMAA_THRESHOLD _100_Threshold +#define SMAA_MAX_SEARCH_STEPS _110_MaxSearchSteps +#define SMAA_MAX_SEARCH_STEPS_DIAG _120_MaxDiagonalSearchSteps +#define SMAA_CORNER_ROUNDING _130_CornerRounding + +sampler_state LinearSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; +sampler_state PointSampler { + Filter = Point; + AddressU = Clamp; + AddressV = Clamp; +}; + +#define SMAATexture2D(tex) texture2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) tex.SampleLevel(LinearSampler, coord, 0) +#define SMAASampleLevelZeroPoint(tex, coord) tex.SampleLevel(PointSampler, coord, 0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) tex.SampleLevel(LinearSampler, coord, 0, offset) +#define SMAASample(tex, coord) tex.Sample(LinearSampler, coord) +#define SMAASamplePoint(tex, coord) tex.Sample(PointSampler, coord) +#define SMAASampleOffset(tex, coord, offset) tex.Sample(LinearSampler, coord, offset) + +#ifdef GS_DEVICE_OPENGL +#define bool2 bvec2 +#define bool3 bvec3 +#define bool4 bvec4 +#define mad(a, b, c) (a * b + c) +#endif + +#define SMAA_FLATTEN +#define SMAA_BRANCH + +// ================================================================================ // +// SMAA Code +// ================================================================================ // +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +/** + * _______ ___ ___ ___ ___ + * / || \/ | / \ / \ + * | (---- | \ / | / ^ \ / ^ \ + * \ \ | |\/| | / /_\ \ / /_\ \ + * ----) | | | | | / _____ \ / _____ \ + * |_______/ |__| |__| /__/ \__\ /__/ \__\ + * + * E N H A N C E D + * S U B P I X E L M O R P H O L O G I C A L A N T I A L I A S I N G + * + * http://www.iryoku.com/smaa/ + * + * Hi, welcome aboard! + * + * Here you'll find instructions to get the shader up and running as fast as + * possible. + * + * IMPORTANTE NOTICE: when updating, remember to update both this file and the + * precomputed textures! They may change from version to version. + * + * The shader has three passes, chained together as follows: + * + * |input|------------------� + * v | + * [ SMAA*EdgeDetection ] | + * v | + * |edgesTex| | + * v | + * [ SMAABlendingWeightCalculation ] | + * v | + * |blendTex| | + * v | + * [ SMAANeighborhoodBlending ] <------� + * v + * |output| + * + * Note that each [pass] has its own vertex and pixel shader. Remember to use + * oversized triangles instead of quads to avoid overshading along the + * diagonal. + * + * You've three edge detection methods to choose from: luma, color or depth. + * They represent different quality/performance and anti-aliasing/sharpness + * tradeoffs, so our recommendation is for you to choose the one that best + * suits your particular scenario: + * + * - Depth edge detection is usually the fastest but it may miss some edges. + * + * - Luma edge detection is usually more expensive than depth edge detection, + * but catches visible edges that depth edge detection can miss. + * + * - Color edge detection is usually the most expensive one but catches + * chroma-only edges. + * + * For quickstarters: just use luma edge detection. + * + * The general advice is to not rush the integration process and ensure each + * step is done correctly (don't try to integrate SMAA T2x with predicated edge + * detection from the start!). Ok then, let's go! + * + * 1. The first step is to create two RGBA temporal render targets for holding + * |edgesTex| and |blendTex|. + * + * In DX10 or DX11, you can use a RG render target for the edges texture. + * In the case of NVIDIA GPUs, using RG render targets seems to actually be + * slower. + * + * On the Xbox 360, you can use the same render target for resolving both + * |edgesTex| and |blendTex|, as they aren't needed simultaneously. + * + * 2. Both temporal render targets |edgesTex| and |blendTex| must be cleared + * each frame. Do not forget to clear the alpha channel! + * + * 3. The next step is loading the two supporting precalculated textures, + * 'areaTex' and 'searchTex'. You'll find them in the 'Textures' folder as + * C++ headers, and also as regular DDS files. They'll be needed for the + * 'SMAABlendingWeightCalculation' pass. + * + * If you use the C++ headers, be sure to load them in the format specified + * inside of them. + * + * You can also compress 'areaTex' and 'searchTex' using BC5 and BC4 + * respectively, if you have that option in your content processor pipeline. + * When compressing then, you get a non-perceptible quality decrease, and a + * marginal performance increase. + * + * 4. All samplers must be set to linear filtering and clamp. + * + * After you get the technique working, remember that 64-bit inputs have + * half-rate linear filtering on GCN. + * + * If SMAA is applied to 64-bit color buffers, switching to point filtering + * when accesing them will increase the performance. Search for + * 'SMAASamplePoint' to see which textures may benefit from point + * filtering, and where (which is basically the color input in the edge + * detection and resolve passes). + * + * 5. All texture reads and buffer writes must be non-sRGB, with the exception + * of the input read and the output write in + * 'SMAANeighborhoodBlending' (and only in this pass!). If sRGB reads in + * this last pass are not possible, the technique will work anyway, but + * will perform antialiasing in gamma space. + * + * IMPORTANT: for best results the input read for the color/luma edge + * detection should *NOT* be sRGB. + * + * 6. Before including SMAA.h you'll have to setup the render target metrics, + * the target and any optional configuration defines. Optionally you can + * use a preset. + * + * You have the following targets available: + * SMAA_HLSL_3 + * SMAA_HLSL_4 + * SMAA_HLSL_4_1 + * SMAA_GLSL_3 * + * SMAA_GLSL_4 * + * + * * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below). + * + * And four presets: + * SMAA_PRESET_LOW (%60 of the quality) + * SMAA_PRESET_MEDIUM (%80 of the quality) + * SMAA_PRESET_HIGH (%95 of the quality) + * SMAA_PRESET_ULTRA (%99 of the quality) + * + * For example: + * #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0) + * #define SMAA_HLSL_4 + * #define SMAA_PRESET_HIGH + * #include "SMAA.h" + * + * Note that SMAA_RT_METRICS doesn't need to be a macro, it can be a + * uniform variable. The code is designed to minimize the impact of not + * using a constant value, but it is still better to hardcode it. + * + * Depending on how you encoded 'areaTex' and 'searchTex', you may have to + * add (and customize) the following defines before including SMAA.h: + * #define SMAA_AREATEX_SELECT(sample) sample.rg + * #define SMAA_SEARCHTEX_SELECT(sample) sample.r + * + * If your engine is already using porting macros, you can define + * SMAA_CUSTOM_SL, and define the porting functions by yourself. + * + * 7. Then, you'll have to setup the passes as indicated in the scheme above. + * You can take a look into SMAA.fx, to see how we did it for our demo. + * Checkout the function wrappers, you may want to copy-paste them! + * + * 8. It's recommended to validate the produced |edgesTex| and |blendTex|. + * You can use a screenshot from your engine to compare the |edgesTex| + * and |blendTex| produced inside of the engine with the results obtained + * with the reference demo. + * + * 9. After you get the last pass to work, it's time to optimize. You'll have + * to initialize a stencil buffer in the first pass (discard is already in + * the code), then mask execution by using it the second pass. The last + * pass should be executed in all pixels. + * + * + * After this point you can choose to enable predicated thresholding, + * temporal supersampling and motion blur integration: + * + * a) If you want to use predicated thresholding, take a look into + * SMAA_PREDICATION; you'll need to pass an extra texture in the edge + * detection pass. + * + * b) If you want to enable temporal supersampling (SMAA T2x): + * + * 1. The first step is to render using subpixel jitters. I won't go into + * detail, but it's as simple as moving each vertex position in the + * vertex shader, you can check how we do it in our DX10 demo. + * + * 2. Then, you must setup the temporal resolve. You may want to take a look + * into SMAAResolve for resolving 2x modes. After you get it working, you'll + * probably see ghosting everywhere. But fear not, you can enable the + * CryENGINE temporal reprojection by setting the SMAA_REPROJECTION macro. + * Check out SMAA_DECODE_VELOCITY if your velocity buffer is encoded. + * + * 3. The next step is to apply SMAA to each subpixel jittered frame, just as + * done for 1x. + * + * 4. At this point you should already have something usable, but for best + * results the proper area textures must be set depending on current jitter. + * For this, the parameter 'subsampleIndices' of + * 'SMAABlendingWeightCalculationPS' must be set as follows, for our T2x + * mode: + * + * @SUBSAMPLE_INDICES + * + * | S# | Camera Jitter | subsampleIndices | + * +----+------------------+---------------------+ + * | 0 | ( 0.25, -0.25) | float4(1, 1, 1, 0) | + * | 1 | (-0.25, 0.25) | float4(2, 2, 2, 0) | + * + * These jitter positions assume a bottom-to-top y axis. S# stands for the + * sample number. + * + * More information about temporal supersampling here: + * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf + * + * c) If you want to enable spatial multisampling (SMAA S2x): + * + * 1. The scene must be rendered using MSAA 2x. The MSAA 2x buffer must be + * created with: + * - DX10: see below (*) + * - DX10.1: D3D10_STANDARD_MULTISAMPLE_PATTERN or + * - DX11: D3D11_STANDARD_MULTISAMPLE_PATTERN + * + * This allows to ensure that the subsample order matches the table in + * @SUBSAMPLE_INDICES. + * + * (*) In the case of DX10, we refer the reader to: + * - SMAA::detectMSAAOrder and + * - SMAA::msaaReorder + * + * These functions allow to match the standard multisample patterns by + * detecting the subsample order for a specific GPU, and reordering + * them appropriately. + * + * 2. A shader must be run to output each subsample into a separate buffer + * (DX10 is required). You can use SMAASeparate for this purpose, or just do + * it in an existing pass (for example, in the tone mapping pass, which has + * the advantage of feeding tone mapped subsamples to SMAA, which will yield + * better results). + * + * 3. The full SMAA 1x pipeline must be run for each separated buffer, storing + * the results in the final buffer. The second run should alpha blend with + * the existing final buffer using a blending factor of 0.5. + * 'subsampleIndices' must be adjusted as in the SMAA T2x case (see point + * b). + * + * d) If you want to enable temporal supersampling on top of SMAA S2x + * (which actually is SMAA 4x): + * + * 1. SMAA 4x consists on temporally jittering SMAA S2x, so the first step is + * to calculate SMAA S2x for current frame. In this case, 'subsampleIndices' + * must be set as follows: + * + * | F# | S# | Camera Jitter | Net Jitter | subsampleIndices | + * +----+----+--------------------+-------------------+----------------------+ + * | 0 | 0 | ( 0.125, 0.125) | ( 0.375, -0.125) | float4(5, 3, 1, 3) | + * | 0 | 1 | ( 0.125, 0.125) | (-0.125, 0.375) | float4(4, 6, 2, 3) | + * +----+----+--------------------+-------------------+----------------------+ + * | 1 | 2 | (-0.125, -0.125) | ( 0.125, -0.375) | float4(3, 5, 1, 4) | + * | 1 | 3 | (-0.125, -0.125) | (-0.375, 0.125) | float4(6, 4, 2, 4) | + * + * These jitter positions assume a bottom-to-top y axis. F# stands for the + * frame number. S# stands for the sample number. + * + * 2. After calculating SMAA S2x for current frame (with the new subsample + * indices), previous frame must be reprojected as in SMAA T2x mode (see + * point b). + * + * e) If motion blur is used, you may want to do the edge detection pass + * together with motion blur. This has two advantages: + * + * 1. Pixels under heavy motion can be omitted from the edge detection process. + * For these pixels we can just store "no edge", as motion blur will take + * care of them. + * 2. The center pixel tap is reused. + * + * Note that in this case depth testing should be used instead of stenciling, + * as we have to write all the pixels in the motion blur pass. + * + * That's it! + */ + +//----------------------------------------------------------------------------- +// SMAA Presets + +/** + * Note that if you use one of these presets, the following configuration + * macros will be ignored if set in the "Configurable Defines" section. + */ + +// #if defined(SMAA_PRESET_LOW) +// #define SMAA_THRESHOLD 0.15 +// #define SMAA_MAX_SEARCH_STEPS 4 +// #define SMAA_DISABLE_DIAG_DETECTION +// #define SMAA_DISABLE_CORNER_DETECTION +// #elif defined(SMAA_PRESET_MEDIUM) +// #define SMAA_THRESHOLD 0.1 +// #define SMAA_MAX_SEARCH_STEPS 8 +// #define SMAA_DISABLE_DIAG_DETECTION +// #define SMAA_DISABLE_CORNER_DETECTION +// #elif defined(SMAA_PRESET_HIGH) +// #define SMAA_THRESHOLD 0.1 +// #define SMAA_MAX_SEARCH_STEPS 16 +// #define SMAA_MAX_SEARCH_STEPS_DIAG 8 +// #define SMAA_CORNER_ROUNDING 25 +// #elif defined(SMAA_PRESET_ULTRA) +// #define SMAA_THRESHOLD 0.05 +// #define SMAA_MAX_SEARCH_STEPS 32 +// #define SMAA_MAX_SEARCH_STEPS_DIAG 16 +// #define SMAA_CORNER_ROUNDING 25 +// #endif + +//----------------------------------------------------------------------------- +// Configurable Defines + +/** + * SMAA_THRESHOLD specifies the threshold or sensitivity to edges. + * Lowering this value you will be able to detect more edges at the expense of + * performance. + * + * Range: [0, 0.5] + * 0.1 is a reasonable value, and allows to catch most visible edges. + * 0.05 is a rather overkill value, that allows to catch 'em all. + * + * If temporal supersampling is used, 0.2 could be a reasonable value, as low + * contrast edges are properly filtered by just 2x. + */ +// #ifndef SMAA_THRESHOLD +// #define SMAA_THRESHOLD 0.1 +// #endif + +/** + * SMAA_DEPTH_THRESHOLD specifies the threshold for depth edge detection. + * + * Range: depends on the depth range of the scene. + */ +#ifndef SMAA_DEPTH_THRESHOLD +#define SMAA_DEPTH_THRESHOLD float(0.1 * SMAA_THRESHOLD) +#endif + +/** + * SMAA_MAX_SEARCH_STEPS specifies the maximum steps performed in the + * horizontal/vertical pattern searches, at each side of the pixel. + * + * In number of pixels, it's actually the double. So the maximum line length + * perfectly handled by, for example 16, is 64 (by perfectly, we meant that + * longer lines won't look as good, but still antialiased). + * + * Range: [0, 112] + */ +// #ifndef SMAA_MAX_SEARCH_STEPS +// #define SMAA_MAX_SEARCH_STEPS 16 +// #endif + +/** + * SMAA_MAX_SEARCH_STEPS_DIAG specifies the maximum steps performed in the + * diagonal pattern searches, at each side of the pixel. In this case we jump + * one pixel at time, instead of two. + * + * Range: [0, 20] + * + * On high-end machines it is cheap (between a 0.8x and 0.9x slower for 16 + * steps), but it can have a significant impact on older machines. + * + * Define SMAA_DISABLE_DIAG_DETECTION to disable diagonal processing. + */ +// #ifndef SMAA_MAX_SEARCH_STEPS_DIAG +// #define SMAA_MAX_SEARCH_STEPS_DIAG 8 +// #endif + +/** + * SMAA_CORNER_ROUNDING specifies how much sharp corners will be rounded. + * + * Range: [0, 100] + * + * Define SMAA_DISABLE_CORNER_DETECTION to disable corner processing. + */ +// #ifndef SMAA_CORNER_ROUNDING +// #define SMAA_CORNER_ROUNDING 25 +// #endif + +/** + * If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times + * bigger contrast than current edge, current edge will be discarded. + * + * This allows to eliminate spurious crossing edges, and is based on the fact + * that, if there is too much contrast in a direction, that will hide + * perceptually contrast in the other neighbors. + */ +#ifndef SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#endif + +/** + * Predicated thresholding allows to better preserve texture details and to + * improve performance, by decreasing the number of detected edges using an + * additional buffer like the light accumulation buffer, object ids or even the + * depth buffer (the depth buffer usage may be limited to indoor or short range + * scenes). + * + * It locally decreases the luma or color threshold if an edge is found in an + * additional buffer (so the global threshold can be higher). + * + * This method was developed by Playstation EDGE MLAA team, and used in + * Killzone 3, by using the light accumulation buffer. More information here: + * http://iryoku.com/aacourse/downloads/06-MLAA-on-PS3.pptx + */ +// #ifndef SMAA_PREDICATION +// #define SMAA_PREDICATION 0 +// #endif + +/** + * Threshold to be used in the additional predication buffer. + * + * Range: depends on the input, so you'll have to find the magic number that + * works for you. + */ +// #ifndef SMAA_PREDICATION_THRESHOLD +// #define SMAA_PREDICATION_THRESHOLD 0.01 +// #endif + +/** + * How much to scale the global threshold used for luma or color edge + * detection when using predication. + * + * Range: [1, 5] + */ +// #ifndef SMAA_PREDICATION_SCALE +// #define SMAA_PREDICATION_SCALE 2.0 +// #endif + +/** + * How much to locally decrease the threshold. + * + * Range: [0, 1] + */ +// #ifndef SMAA_PREDICATION_STRENGTH +// #define SMAA_PREDICATION_STRENGTH 0.4 +// #endif + +/** + * Temporal reprojection allows to remove ghosting artifacts when using + * temporal supersampling. We use the CryEngine 3 method which also introduces + * velocity weighting. This feature is of extreme importance for totally + * removing ghosting. More information here: + * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf + * + * Note that you'll need to setup a velocity buffer for enabling reprojection. + * For static geometry, saving the previous depth buffer is a viable + * alternative. + */ +// #ifndef SMAA_REPROJECTION +// #define SMAA_REPROJECTION 0 +// #endif + +/** + * SMAA_REPROJECTION_WEIGHT_SCALE controls the velocity weighting. It allows to + * remove ghosting trails behind the moving object, which are not removed by + * just using reprojection. Using low values will exhibit ghosting, while using + * high values will disable temporal supersampling under motion. + * + * Behind the scenes, velocity weighting removes temporal supersampling when + * the velocity of the subsamples differs (meaning they are different objects). + * + * Range: [0, 80] + */ +// #ifndef SMAA_REPROJECTION_WEIGHT_SCALE +// #define SMAA_REPROJECTION_WEIGHT_SCALE 30.0 +// #endif + +/** + * On some compilers, discard cannot be used in vertex shaders. Thus, they need + * to be compiled separately. + */ +#ifndef SMAA_INCLUDE_VS +#define SMAA_INCLUDE_VS 1 +#endif +#ifndef SMAA_INCLUDE_PS +#define SMAA_INCLUDE_PS 1 +#endif + +//----------------------------------------------------------------------------- +// Texture Access Defines + +#ifndef SMAA_AREATEX_SELECT +#ifdef SMAA_HLSL_3 +#define SMAA_AREATEX_SELECT(sample) sample.ra +#else +#define SMAA_AREATEX_SELECT(sample) sample.rg +#endif +#endif + +#ifndef SMAA_SEARCHTEX_SELECT +#define SMAA_SEARCHTEX_SELECT(sample) sample.r +#endif + +#ifndef SMAA_DECODE_VELOCITY +#define SMAA_DECODE_VELOCITY(sample) sample.rg +#endif + +//----------------------------------------------------------------------------- +// Non-Configurable Defines + +#define SMAA_AREATEX_MAX_DISTANCE 16 +#define SMAA_AREATEX_MAX_DISTANCE_DIAG 20 +#define SMAA_AREATEX_PIXEL_SIZE float2(1.0 / float2(160.0, 560.0)) +#define SMAA_AREATEX_SUBTEX_SIZE float(1.0 / 7.0) +#define SMAA_SEARCHTEX_SIZE float2(66.0, 33.0) +#define SMAA_SEARCHTEX_PACKED_SIZE float2(64.0, 16.0) +#define SMAA_CORNER_ROUNDING_NORM float(float(SMAA_CORNER_ROUNDING) / 100.0) + +//----------------------------------------------------------------------------- +// Porting Functions + +// #if defined(SMAA_HLSL_3) +// #define SMAATexture2D(tex) sampler2D tex +// #define SMAATexturePass2D(tex) tex +// #define SMAASampleLevelZero(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) +// #define SMAASampleLevelZeroPoint(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) +// #define SMAASampleLevelZeroOffset(tex, coord, offset) tex2Dlod(tex, float4(coord + offset * SMAA_RT_METRICS.xy, 0.0, 0.0)) +// #define SMAASample(tex, coord) tex2D(tex, coord) +// #define SMAASamplePoint(tex, coord) tex2D(tex, coord) +// #define SMAASampleOffset(tex, coord, offset) tex2D(tex, coord + offset * SMAA_RT_METRICS.xy) +// #define SMAA_FLATTEN [flatten] +// #define SMAA_BRANCH [branch] +// #endif +// #if defined(SMAA_HLSL_4) || defined(SMAA_HLSL_4_1) +// SamplerState LinearSampler { Filter = MIN_MAG_LINEAR_MIP_POINT; AddressU = Clamp; AddressV = Clamp; }; +// SamplerState PointSampler { Filter = MIN_MAG_MIP_POINT; AddressU = Clamp; AddressV = Clamp; }; +// #define SMAATexture2D(tex) Texture2D tex +// #define SMAATexturePass2D(tex) tex +// #define SMAASampleLevelZero(tex, coord) tex.SampleLevel(LinearSampler, coord, 0) +// #define SMAASampleLevelZeroPoint(tex, coord) tex.SampleLevel(PointSampler, coord, 0) +// #define SMAASampleLevelZeroOffset(tex, coord, offset) tex.SampleLevel(LinearSampler, coord, 0, offset) +// #define SMAASample(tex, coord) tex.Sample(LinearSampler, coord) +// #define SMAASamplePoint(tex, coord) tex.Sample(PointSampler, coord) +// #define SMAASampleOffset(tex, coord, offset) tex.Sample(LinearSampler, coord, offset) +// #define SMAA_FLATTEN [flatten] +// #define SMAA_BRANCH [branch] +// #define SMAATexture2DMS2(tex) Texture2DMS tex +// #define SMAALoad(tex, pos, sample) tex.Load(pos, sample) +// #if defined(SMAA_HLSL_4_1) +// #define SMAAGather(tex, coord) tex.Gather(LinearSampler, coord, 0) +// #endif +// #endif +// #if defined(SMAA_GLSL_3) || defined(SMAA_GLSL_4) +// #define SMAATexture2D(tex) sampler2D tex +// #define SMAATexturePass2D(tex) tex +// #define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) +// #define SMAASampleLevelZeroPoint(tex, coord) textureLod(tex, coord, 0.0) +// #define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) +// #define SMAASample(tex, coord) texture(tex, coord) +// #define SMAASamplePoint(tex, coord) texture(tex, coord) +// #define SMAASampleOffset(tex, coord, offset) texture(tex, coord, offset) +// #define SMAA_FLATTEN +// #define SMAA_BRANCH +// #define lerp(a, b, t) mix(a, b, t) +// #define saturate(a) clamp(a, 0.0, 1.0) +// #if defined(SMAA_GLSL_4) +// #define mad(a, b, c) fma(a, b, c) +// #define SMAAGather(tex, coord) textureGather(tex, coord) +// #else +// #define mad(a, b, c) (a * b + c) +// #endif +// #define float2 vec2 +// #define float3 vec3 +// #define float4 vec4 +// #define int2 ivec2 +// #define int3 ivec3 +// #define int4 ivec4 +// #define bool2 bvec2 +// #define bool3 bvec3 +// #define bool4 bvec4 +// #endif + +// #if !defined(SMAA_HLSL_3) && !defined(SMAA_HLSL_4) && !defined(SMAA_HLSL_4_1) && !defined(SMAA_GLSL_3) && !defined(SMAA_GLSL_4) && !defined(SMAA_CUSTOM_SL) +// #error you must define the shading language: SMAA_HLSL_*, SMAA_GLSL_* or SMAA_CUSTOM_SL +// #endif + +//----------------------------------------------------------------------------- +// Misc functions + +/** + * Gathers current pixel, and the top-left neighbors. + */ +float3 SMAAGatherNeighbours(float2 texcoord, + float4 offset0, float4 offset1, float4 offset2, + SMAATexture2D(tex)) { + #ifdef SMAAGather + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; + #else + float P = SMAASamplePoint(tex, texcoord).r; + float Pleft = SMAASamplePoint(tex, offset0.xy).r; + float Ptop = SMAASamplePoint(tex, offset0.zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset0, float4 offset1, float4 offset2, + SMAATexture2D(predicationTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(predicationTex)); + float2 delta = abs(neighbours.xx - neighbours.yz); + float2 edges = step(SMAA_PREDICATION_THRESHOLD, delta); + return SMAA_PREDICATION_SCALE * SMAA_THRESHOLD * (1.0 - SMAA_PREDICATION_STRENGTH * edges); +} + +/** + * Conditional move: + */ +void SMAAMovc2(bool2 cond, inout float2 variable, float2 value) { + SMAA_FLATTEN if (cond.x) variable.x = value.x; + SMAA_FLATTEN if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bool4 cond, inout float4 variable, float4 value) { + SMAAMovc2(cond.xy, variable.xy, value.xy); + SMAAMovc2(cond.zw, variable.zw, value.zw); +} + + +#ifdef SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset0, float4 offset1, float4 offset2) { + offset0 = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); + offset1 = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); + offset2 = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, -2.0), texcoord.xyxy); +} + +/** + * Blend Weight Calculation Vertex Shader + */ +void SMAABlendingWeightCalculationVS(float2 texcoord, + out float2 pixcoord, + out float4 offset0, float4 offset1, float4 offset2) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset0 = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); + offset1 = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, -0.25, -0.125, 1.25), texcoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + offset2 = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset0.xz, offset1.yw)); +} + +/** + * Neighborhood Blending Vertex Shader + */ +void SMAANeighborhoodBlendingVS(float2 texcoord, + out float4 offset) { + offset = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); +} +#endif // SMAA_INCLUDE_VS + +#ifdef SMAA_INCLUDE_PS +//----------------------------------------------------------------------------- +// Edge Detection Pixel Shaders (First Pass) + +/** + * Luma Edge Detection + * + * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAALumaEdgeDetectionPS(float2 texcoord, + float4 offset0, float4 offset1, float4 offset2, + SMAATexture2D(colorTex) + #ifdef SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #ifdef SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, SMAATexturePass2D(predicationTex)); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate lumas: + float3 weights = float3(0.2126, 0.7152, 0.0722); + float L = dot(SMAASamplePoint(colorTex, texcoord).rgb, weights); + + float Lleft = dot(SMAASamplePoint(colorTex, offset0.xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset0.zw).rgb, weights); + + // We do the usual threshold: + float4 delta; + delta.xy = abs(L - float2(Lleft, Ltop)); + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + discard; + + // Calculate right and bottom deltas: + float Lright = dot(SMAASamplePoint(colorTex, offset1.xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset1.zw).rgb, weights); + delta.zw = abs(L - float2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float Lleftleft = dot(SMAASamplePoint(colorTex, offset2.xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset2.zw).rgb, weights); + delta.zw = abs(float2(Lleft, Ltop) - float2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Color Edge Detection + * + * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAAColorEdgeDetectionPS(float2 texcoord, + float4 offset0, float4 offset1, float4 offset2, + SMAATexture2D(colorTex) + #ifdef SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #ifdef SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, predicationTex); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate color deltas: + float4 delta; + float3 C = SMAASamplePoint(colorTex, texcoord).rgb; + + float3 Cleft = SMAASamplePoint(colorTex, offset0.xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset0.zw).rgb; + t = abs(C - Ctop); + delta.y = max(max(t.r, t.g), t.b); + + // We do the usual threshold: + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + discard; + + // Calculate right and bottom deltas: + float3 Cright = SMAASamplePoint(colorTex, offset1.xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset1.zw).rgb; + t = abs(C - Cbottom); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float3 Cleftleft = SMAASamplePoint(colorTex, offset2.xy).rgb; + t = abs(C - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset2.zw).rgb; + t = abs(C - Ctoptop); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Depth Edge Detection + */ +float2 SMAADepthEdgeDetectionPS(float2 texcoord, + float4 offset0, float4 offset1, float4 offset2, + SMAATexture2D(depthTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(depthTex)); + float2 delta = abs(neighbours.xx - float2(neighbours.y, neighbours.z)); + float2 edges = step(SMAA_DEPTH_THRESHOLD, delta); + + if (dot(edges, float2(1.0, 1.0)) == 0.0) + discard; + + return edges; +} + +//----------------------------------------------------------------------------- +// Diagonal Search Functions + +#ifndef SMAA_DISABLE_DIAG_DETECTION + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess2(float2 e) { + // Bilinear access for fetching 'e' have a 0.25 offset, and we are + // interested in the R and G edges: + // + // +---G---+-------+ + // | x o R x | + // +-------+-------+ + // + // Then, if one of these edge is enabled: + // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 + // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 + // + // This function will unpack the values (mad + mul + round): + // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 + e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); + return round(e); +} + +float4 SMAADecodeDiagBilinearAccess(float4 e) { + e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); + return round(e); +} + +/** + * These functions allows to perform diagonal pattern searches. + */ +float2 SMAASearchDiag1(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +float2 SMAASearchDiag2(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + + // @SearchDiag2Optimization + // Fetch both edges at once using bilinear filtering: + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + e = SMAADecodeDiagBilinearAccess2(e); + + // Non-optimized version: + // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; + // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; + + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +/** + * Similar to SMAAArea, this calculates the area corresponding to a certain + * diagonal distance and crossing edges 'e'. + */ +float2 SMAAAreaDiag(SMAATexture2D(areaTex), float2 dist, float2 e, float offset) { + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Diagonal areas are on the second half of the texture: + texcoord.x += 0.5; + + // Move to proper place, according to the subpixel offset: + texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +/** + * This searches for diagonal patterns and returns the corresponding weights. + */ +float2 SMAACalculateDiagWeights(SMAATexture2D(edgesTex), SMAATexture2D(areaTex), float2 texcoord, float2 e, float4 subsampleIndices) { + float2 weights = float2(0.0, 0.0); + + // Search for the line ends: + float4 d; + float2 end; + if (e.r > 0.0) { + d.xz = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, 1.0), end); + d.x += float(end.y > 0.9); + } else + d.xz = float2(0.0, 0.0); + d.yw = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, -1.0), end); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).rg; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).rg; + c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); + + // Non-optimized version: + // float4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + // float4 c; + // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, 0)).r; + // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).g; + // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, -1)).r; + + // Merge crossing edges at each side into a single value: + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc2(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.z); + } + + // Search for the line ends: + d.xz = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, -1.0), end); + if (SMAASampleLevelZeroOffset(edgesTex, texcoord, int2(1, 0)).r > 0.0) { + d.yw = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, 1.0), end); + d.y += float(end.y > 0.9); + } else + d.yw = float2(0.0, 0.0); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, -1)).r; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).gr; + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc2(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.w).gr; + } + + return weights; +} +#endif + +//----------------------------------------------------------------------------- +// Horizontal/Vertical Search Functions + +/** + * This allows to determine how much length should we add in the last step + * of the searches. It takes the bilinearly interpolated edge (see + * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and + * crossing edges are active. + */ +float SMAASearchLength(SMAATexture2D(searchTex), float2 e, float offset) { + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally: + float2 scale = SMAA_SEARCHTEX_SIZE * float2(0.5, -1.0); + float2 bias = SMAA_SEARCHTEX_SIZE * float2(offset, 1.0); + + // Scale and bias to access texel centers: + scale += float2(-1.0, 1.0); + bias += float2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords: + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, mad(scale, e, bias))); +} + +/** + * Horizontal/vertical search functions for the 2nd pass. + */ +float SMAASearchXLeft(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + float2 e = float2(0.0, 1.0); + while (texcoord.x > end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0), 3.25); + return mad(SMAA_RT_METRICS.x, offset, texcoord.x); + + // Non-optimized version: + // We correct the previous (-0.25, -0.125) offset we applied: + // texcoord.x += 0.25 * SMAA_RT_METRICS.x; + + // The searches are bias by 1, so adjust the coords accordingly: + // texcoord.x += SMAA_RT_METRICS.x; + + // Disambiguate the length added by the last step: + // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step + // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); + // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchXRight(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(0.0, 1.0); + while (texcoord.x < end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchYUp(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y > end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.0), 3.25); + return mad(SMAA_RT_METRICS.y, offset, texcoord.y); +} + +float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y < end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); +} + +/** + * Ok, we have the distance and both crossing edges. So, what are the areas + * at each side of current edge? + */ +float2 SMAAArea(SMAATexture2D(areaTex), float2 dist, float e1, float e2, float offset) { + // Rounding prevents precision errors of bilinear filtering: + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * float2(e1, e2)), dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Move to proper place, according to the subpixel offset: + texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +//----------------------------------------------------------------------------- +// Corner Detection Functions + +void SMAADetectHorizontalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #ifndef SMAA_DISABLE_CORNER_DETECTION + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, 1)).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, 1)).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, -2)).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, -2)).r; + + weights *= saturate(factor); + #endif +} + +void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #ifndef SMAA_DISABLE_CORNER_DETECTION + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2( 1, 0)).g; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2( 1, 1)).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, 1)).g; + + weights *= saturate(factor); + #endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +float4 SMAABlendingWeightCalculationPS(float2 texcoord, + float2 pixcoord, + float4 offset0, float4 offset1, float4 offset2, + SMAATexture2D(edgesTex), + SMAATexture2D(areaTex), + SMAATexture2D(searchTex), + float4 subsampleIndices) { // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. + float4 weights = float4(0.0, 0.0, 0.0, 0.0); + + float2 e = SMAASample(edgesTex, texcoord).rg; + + SMAA_BRANCH + if (e.g > 0.0) { // Edge at north + #ifndef SMAA_DISABLE_DIAG_DETECTION + // Diagonals have both north and west edges, so searching for them in + // one of the boundaries is enough. + weights.rg = SMAACalculateDiagWeights(SMAATexturePass2D(edgesTex), SMAATexturePass2D(areaTex), texcoord, e, subsampleIndices); + + // We give priority to diagonals, so if we find a diagonal we skip + // horizontal/vertical processing. + SMAA_BRANCH + if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 + #endif + + float2 d; + + // Find the distance to the left: + float3 coords; + coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset0.xy, offset2.x); + coords.y = offset1.y; // offset1.y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).r; + + // Find the distance to the right: + coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset0.zw, offset2.y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r; + + // Ok, we know how this pattern looks like, now it is time for getting + // the actual area: + weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y); + + // Fix corners: + coords.y = texcoord.y; + SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d); + + #ifndef SMAA_DISABLE_DIAG_DETECTION + } else + e.r = 0.0; // Skip vertical processing. + #endif + } + + SMAA_BRANCH + if (e.r > 0.0) { // Edge at west + float2 d; + + // Find the distance to the top: + float3 coords; + coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset1.xy, offset2.z); + coords.x = offset0.x; // offset1.x = texcoord.x - 0.25 * SMAA_RT_METRICS.x; + d.x = coords.y; + + // Fetch the top crossing edges: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g; + + // Find the distance to the bottom: + coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset1.zw, offset2.w); + d.y = coords.z; + + // We want the distances to be in pixel units: + d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g; + + // Get the area for this direction: + weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x); + + // Fix corners: + coords.x = texcoord.x; + SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d); + } + + return weights; +} + +//----------------------------------------------------------------------------- +// Neighborhood Blending Pixel Shader (Third Pass) + +float4 SMAANeighborhoodBlendingPS(float2 texcoord, + float4 offset, + SMAATexture2D(colorTex), + SMAATexture2D(blendTex) + #ifdef SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + // Fetch the blending weights for current pixel: + float4 a; + a.x = SMAASample(blendTex, offset.xy).a; // Right + a.y = SMAASample(blendTex, offset.zw).g; // Top + a.wz = SMAASample(blendTex, texcoord).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + SMAA_BRANCH + if (dot(a, float4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { + float4 color = SMAASampleLevelZero(colorTex, texcoord); + + #ifdef SMAA_REPROJECTION + float2 velocity = SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, texcoord)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } else { + bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) + + // Calculate the blending offsets: + float4 blendingOffset = float4(0.0, a.y, 0.0, a.w); + float2 blendingWeight = a.yw; + SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); + SMAAMovc2(bool2(h, h), blendingWeight, a.xz); + blendingWeight /= dot(blendingWeight, float2(1.0, 1.0)); + + // Calculate the texture coordinates: + float4 blendingCoord = mad(blendingOffset, float4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texcoord.xyxy); + + // We exploit bilinear filtering to mix current pixel with the chosen + // neighbor: + float4 color = blendingWeight.x * SMAASampleLevelZero(colorTex, blendingCoord.xy); + color += blendingWeight.y * SMAASampleLevelZero(colorTex, blendingCoord.zw); + + #ifdef SMAA_REPROJECTION + // Antialias velocity for proper reprojection in a later stage: + float2 velocity = blendingWeight.x * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.xy)); + velocity += blendingWeight.y * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.zw)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } +} + +//----------------------------------------------------------------------------- +// Temporal Resolve Pixel Shader (Optional Pass) + +float4 SMAAResolvePS(float2 texcoord, + SMAATexture2D(currentColorTex), + SMAATexture2D(previousColorTex) + #ifdef SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #ifdef SMAA_REPROJECTION + // Velocity is assumed to be calculated for motion blur, so we need to + // inverse it for reprojection: + float2 velocity = -SMAA_DECODE_VELOCITY(SMAASamplePoint(velocityTex, texcoord).rg); + + // Fetch current pixel: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + + // Reproject current coordinates and fetch previous pixel: + float4 previous = SMAASamplePoint(previousColorTex, texcoord + velocity); + + // Attenuate the previous pixel if the velocity is different: + float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; + float weight = 0.5 * saturate(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE); + + // Blend the pixels according to the calculated weight: + return lerp(current, previous, weight); + #else + // Just blend the pixels: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + float4 previous = SMAASamplePoint(previousColorTex, texcoord); + return lerp(current, previous, 0.5); + #endif +} + +//----------------------------------------------------------------------------- +// Separate Multisamples Pixel Shader (Optional Pass) + +#ifdef SMAALoad +void SMAASeparatePS(float4 position, + float2 texcoord, + out float4 target0, + out float4 target1, + SMAATexture2DMS2(colorTexMS)) { + int2 pos = int2(position.xy); + target0 = SMAALoad(colorTexMS, pos, 0); + target1 = SMAALoad(colorTexMS, pos, 1); +} +#endif + +//----------------------------------------------------------------------------- +#endif // SMAA_INCLUDE_PS + + +// ================================================================================ // +// StreamFX code +// ================================================================================ // +float4 LumaEdgeDetectionPS(VertexInformation vtx) : TARGET { + float4 offset0, offset1, offset2; + offset0 = mad(ViewSize.zwzw, float4(-1.0, 0.0, 0.0, -1.0), vtx.texcoord0.xyxy); + offset1 = mad(ViewSize.zwzw, float4( 1.0, 0.0, 0.0, 1.0), vtx.texcoord0.xyxy); + offset2 = mad(ViewSize.zwzw, float4(-2.0, 0.0, 0.0, -2.0), vtx.texcoord0.xyxy); + + float2 edge = SMAALumaEdgeDetectionPS( + vtx.texcoord0.xy, + offset0, + offset1, + offset2, + InputA); + return float4(edge.r, edge.g, 0., 1.); +} +technique LumaEdgeDetection +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = LumaEdgeDetectionPS(vtx); + } +} + +float4 ColorEdgeDetectionPS(VertexInformation vtx) : TARGET { + float4 offset0, offset1, offset2; + offset0 = mad(ViewSize.zwzw, float4(-1.0, 0.0, 0.0, -1.0), vtx.texcoord0.xyxy); + offset1 = mad(ViewSize.zwzw, float4( 1.0, 0.0, 0.0, 1.0), vtx.texcoord0.xyxy); + offset2 = mad(ViewSize.zwzw, float4(-2.0, 0.0, 0.0, -2.0), vtx.texcoord0.xyxy); + + float2 edge = SMAAColorEdgeDetectionPS( + vtx.texcoord0.xy, + offset0, + offset1, + offset2, + InputA); + return float4(edge.r, edge.g, 0., 1.); +} +technique ColorEdgeDetectionPS +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = ColorEdgeDetectionPS(vtx); + } +} + + +float4 BlendingWeightCalculationPS(VertexInformation vtx) : TARGET { + float4 subsampleIndices = float4(0., 0., 0., 0.); + float4 offset0, offset1, offset2; + offset0 = mad(ViewSize.zwzw, float4(-0.25, -0.125, 1.25, -0.125), vtx.texcoord0.xyxy); + offset1 = mad(ViewSize.zwzw, float4(-0.125, -0.25, -0.125, 1.25), vtx.texcoord0.xyxy); + offset2 = mad(ViewSize.zzww, float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), float4(offset0.xz, offset1.yw)); + + return SMAABlendingWeightCalculationPS( + vtx.texcoord0.xy, + vtx.texcoord0.xy * ViewSize.xy, + offset0, + offset1, + offset2, + InputA, + _999_AreaTexture, + _999_SearchTexture, + subsampleIndices); +} +technique BlendingWeightCalculation +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = BlendingWeightCalculationPS(vtx); + } +} + +float4 NeighborhoodBlendingPS(VertexInformation vtx) : TARGET { + float4 offset = mad(ViewSize.zwzw, float4( 1.0, 0.0, 0.0, 1.0), vtx.texcoord0.xyxy); + return SMAANeighborhoodBlendingPS( + vtx.texcoord0.xy, + offset, + InputA, + _200_PreviousPass); +} +technique NeighborhoodBlending +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = NeighborhoodBlendingPS(vtx); + } +} diff --git a/data/examples/shaders/filter/smaa/areatex.png b/data/examples/shaders/filter/smaa/areatex.png new file mode 100644 index 0000000000000000000000000000000000000000..a05aee8d2c7eb2d48181f49babe90f48bacef471 GIT binary patch literal 46617 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