From 5e9d7d63a1e9c8c66aab22f1902bf2cb49a41ffe Mon Sep 17 00:00:00 2001 From: Areloch Date: Fri, 16 Sep 2022 16:16:12 -0500 Subject: [PATCH] Adjusts handling for httpObject class so that if curl flag is off, files are not included and references are disabled to avoid compilation issues Adds SMAA anti aliasing Adjusts AA option setting to properly work and facilitate SMAA Added defaults for AA to facilitate SMAA Updated brightness and HDR values on Example Level to comply more closely to the Editor default to give better default results Fixed issue where ShapeEditor wasn't initialized properly when we'd open it from the Asset Browser --- Engine/source/platform/platformNet.cpp | 9 + .../game/core/gui/scripts/canvas.tscript | 31 +- .../game/core/postFX/scripts/SMAA/SMAA.h | 1373 +++++++++++++++++ .../postFX/scripts/SMAA/SMAAPostFX.asset.taml | 6 + .../postFX/scripts/SMAA/SMAAPostFX.tscript | 180 +++ .../scripts/SMAA/SMAA_BlendWeight_P.hlsl | 47 + .../scripts/SMAA/SMAA_BlendWeight_V.hlsl | 54 + .../core/postFX/scripts/SMAA/SMAA_Edge_P.hlsl | 50 + .../core/postFX/scripts/SMAA/SMAA_Edge_V.hlsl | 52 + .../SMAA/SMAA_Neighbor_H_Blending_P.hlsl | 43 + .../SMAA/SMAA_Neighbor_H_Blending_V.hlsl | 53 + .../core/postFX/scripts/SMAA/SMAA_Params.hlsl | 16 + .../postFX/scripts/SMAA/Textures/AreaTex.dds | Bin 0 -> 268928 bytes .../scripts/SMAA/Textures/AreaTexDX9.dds | Bin 0 -> 179328 bytes .../SMAA/Textures/AreaTexDX9_image.asset.taml | 3 + .../SMAA/Textures/AreaTex_image.asset.taml | 3 + .../scripts/SMAA/Textures/SearchTex.dds | Bin 0 -> 1152 bytes .../SMAA/Textures/SearchTex_image.asset.taml | 3 + .../core/postFX/scripts/SMAA/gl/SMAA.glsl | 1144 ++++++++++++++ .../scripts/SMAA/gl/SMAA_BlendWeight_P.glsl | 47 + .../scripts/SMAA/gl/SMAA_BlendWeight_V.glsl | 59 + .../postFX/scripts/SMAA/gl/SMAA_Edge_P.glsl | 52 + .../postFX/scripts/SMAA/gl/SMAA_Edge_V.glsl | 55 + .../SMAA/gl/SMAA_Neighbor_H_Blending_P.glsl | 46 + .../SMAA/gl/SMAA_Neighbor_H_Blending_V.glsl | 55 + .../postFX/scripts/SMAA/gl/SMAA_Params.glsl | 21 + .../rendering/scripts/graphicsOptions.tscript | 5 + .../levels/ExampleLevel.asset.taml | 1 + .../ExampleModule/levels/ExampleLevel.mis | 2 +- .../levels/ExampleLevel.postfxpreset.tscript | 23 +- .../game/data/UI/guis/optionsMenu.tscript | 13 +- Templates/BaseGame/game/data/defaults.tscript | 4 +- .../scripts/assetTypes/shape.tscript | 1 + .../scripts/assetTypes/shapeAnimation.tscript | 1 + Tools/CMake/modules/module_curl.cmake | 3 +- Tools/CMake/torque3d.cmake | 4 +- 36 files changed, 3436 insertions(+), 23 deletions(-) create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA.h create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.asset.taml create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.tscript create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_P.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_V.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_P.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_V.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_P.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_V.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Params.hlsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex.dds create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTexDX9.dds create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTexDX9_image.asset.taml create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex_image.asset.taml create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/SearchTex.dds create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/SearchTex_image.asset.taml create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_P.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_V.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_P.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_V.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_P.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_V.glsl create mode 100644 Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Params.glsl diff --git a/Engine/source/platform/platformNet.cpp b/Engine/source/platform/platformNet.cpp index 4ef06b7f1..09547892f 100644 --- a/Engine/source/platform/platformNet.cpp +++ b/Engine/source/platform/platformNet.cpp @@ -25,7 +25,10 @@ #include "core/strings/stringFunctions.h" #include "core/util/hashFunction.h" #include "console/consoleTypes.h" + +#ifdef TORQUE_NET_CURL #include "app/net/httpObject.h" +#endif // jamesu - debug DNS //#define TORQUE_DEBUG_LOOKUPS @@ -559,7 +562,9 @@ bool Net::init() smConnectionReceive = new ConnectionReceiveEvent(); smPacketReceive = new PacketReceiveEvent(); +#ifdef TORQUE_NET_CURL HTTPObject::init(); +#endif Process::notify(&Net::process, PROCESS_NET_ORDER); @@ -570,7 +575,9 @@ void Net::shutdown() { Process::remove(&Net::process); +#ifdef TORQUE_NET_CURL HTTPObject::shutdown(); +#endif while (gPolledSockets.size() > 0) { @@ -1103,8 +1110,10 @@ void Net::process() processListenSocket(PlatformNetState::udpSocket); processListenSocket(PlatformNetState::udp6Socket); +#ifdef TORQUE_NET_CURL // process HTTPObject HTTPObject::process(); +#endif // process the polled sockets. This blob of code performs functions // similar to WinsockProc in winNet.cc diff --git a/Templates/BaseGame/game/core/gui/scripts/canvas.tscript b/Templates/BaseGame/game/core/gui/scripts/canvas.tscript index 6567819fe..79988556d 100644 --- a/Templates/BaseGame/game/core/gui/scripts/canvas.tscript +++ b/Templates/BaseGame/game/core/gui/scripts/canvas.tscript @@ -110,6 +110,7 @@ function configureCanvas() %resY = $pref::Video::Resolution.y; %bpp = $pref::Video::BitDepth; %rate = $pref::Video::RefreshRate; + %aaMode = $pref::video::AAMode; %aa = $pref::Video::AA; %fs = ($pref::Video::deviceMode == 2); @@ -118,7 +119,7 @@ function configureCanvas() "--Screen Mode : " @ %fsLabel NL "--Bits Per Pixel : " @ %bpp NL "--Refresh Rate : " @ %rate NL - "--FXAA Level : " @ %aa NL + "--Anit-Aliasing Type : " @ %aaMode NL "--------------"); // Actually set the new video mode @@ -140,10 +141,30 @@ function configureCanvas() // AA piggybacks on the AA setting in $pref::Video::mode. // We need to parse the setting between AA modes, and then it's level - // It's formatted as AATypexAALevel - // So, FXAAx4 or MLAAx2 - if ( isObject( FXAAPostFX ) ) - FXAAPostFX.Enabled = ( %aa > 0 ) ? true : false; + + if(isObject( FXAAPostFX )) + { + if ( startsWith(%aaMode, "FXAA") ) + { + FXAAPostFX.Enabled = true; + } + else + { + FXAAPostFX.Enabled = false; + } + } + + if(isObject( SMAAPostFX )) + { + if ( startsWith(%aaMode, "SMAA") ) + { + SMAAPostFX.Enabled = true; + } + else + { + SMAAPostFX.Enabled = false; + } + } } function GuiCanvas::modeStrToPrefs(%this, %modeStr) diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA.h b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA.h new file mode 100644 index 000000000..c992ec679 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA.h @@ -0,0 +1,1373 @@ +/** + * 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 + +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#define SMAA_PREDICATION_THRESHOLD 0.01 +#define SMAA_PREDICATION_SCALE 2.0 +#define SMAA_PREDICATION_STRENGTH 0.4 + +#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 + +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#define SMAA_PREDICATION_THRESHOLD 0.01 +#define SMAA_PREDICATION_SCALE 2.0 +#define SMAA_PREDICATION_STRENGTH 0.4 + +#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 (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 +#if defined(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 (1.0 / float2(160.0, 560.0)) +#define SMAA_AREATEX_SUBTEX_SIZE (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(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 offset[3], + 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, offset[0].xy).r; + float Ptop = SMAASamplePoint(tex, offset[0].zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset[3], + 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 SMAAMovc(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) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + + +#if SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset[3]) { + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); + offset[2] = 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 offset[3]) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); + offset[1] = 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: + offset[2] = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset[0].xz, offset[1].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 + +#if 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 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if 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, offset[0].xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset[0].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, offset[1].xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].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, offset[2].xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].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 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if 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, offset[0].xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset[0].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, offset[1].xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset[1].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, offset[2].xy).rgb; + t = abs(Cleft - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; + t = abs(Ctop - 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 offset[3], + 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 + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess(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 = SMAADecodeDiagBilinearAccess(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: + SMAAMovc(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: + SMAAMovc(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) { + #if !defined(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) { + #if !defined(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 offset[3], + 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 + #if !defined(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), offset[0].xy, offset[2].x); + coords.y = offset[1].y; // offset[1].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), offset[0].zw, offset[2].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); + + #if !defined(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), offset[1].xy, offset[2].z); + coords.x = offset[0].x; // offset[1].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), offset[1].zw, offset[2].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) + #if 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); + + #if 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)); + SMAAMovc(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); + + #if 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) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #if 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 diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.asset.taml b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.asset.taml new file mode 100644 index 000000000..62329e90d --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.asset.taml @@ -0,0 +1,6 @@ + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.tscript b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.tscript new file mode 100644 index 000000000..2e863dc3e --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAAPostFX.tscript @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- + +// "Jorge Jimenez, Subpixel Morphological Antialiasing | SMAA" +// http://www.iryoku.com/smaa/ + + +singleton GFXStateBlockData( SMAA_Edge_D_StateBlock : PFX_DefaultStateBlock ) +{ + stencilDefined = true; + stencilEnable = true; + stencilPassOp = GFXStencilOpReplace; + stencilFunc = GFXCmpAlways; + stencilRef = 1; + + samplersDefined = true; + samplerStates[0] = SamplerClampLinear; +}; + +singleton ShaderData( SMAA_Edge_D_Shader ) +{ + DXVertexShaderFile = "./SMAA_Edge_V.hlsl"; + DXPixelShaderFile = "./SMAA_Edge_P.hlsl"; + + OGLVertexShaderFile = "./gl/SMAA_Edge_V.glsl"; + OGLPixelShaderFile = "./gl/SMAA_Edge_P.glsl"; + + samplerNames[0] = "$sceneTex"; + samplerNames[1] = "$depthTex"; + + pixVersion = 3.0; +}; + +singleton GFXStateBlockData( SMAA_Blend_W_StateBlock : PFX_DefaultStateBlock ) +{ + stencilDefined = true; + stencilEnable = true; + stencilPassOp = GFXStencilOpKeep; + stencilFunc = GFXCmpEqual; + stencilRef = 1; + + samplersDefined = true; + samplerStates[0] = SamplerClampLinear; + samplerStates[1] = SamplerClampLinear; + samplerStates[2] = SamplerClampLinear; +}; + +singleton ShaderData( SMAA_Blend_W_Shader ) +{ + DXVertexShaderFile = "./SMAA_BlendWeight_V.hlsl"; + DXPixelShaderFile = "./SMAA_BlendWeight_P.hlsl"; + + OGLVertexShaderFile = "./gl/SMAA_BlendWeight_V.glsl"; + OGLPixelShaderFile = "./gl/SMAA_BlendWeight_P.glsl"; + + samplerNames[0] = "$edgesTex"; + samplerNames[1] = "$areaTex"; + samplerNames[2] = "$searchTex"; + + pixVersion = 3.0; +}; + +singleton GFXStateBlockData( SMAA_Neighbor_H_StateBlock : PFX_DefaultStateBlock ) +{ + stencilEnable = false; + + samplersDefined = true; + samplerStates[0] = SamplerClampLinear; + samplerStates[1] = SamplerClampLinear; + +}; + +singleton ShaderData( SMAA_Neighbor_H_Shader ) +{ + DXVertexShaderFile = "./SMAA_Neighbor_H_Blending_V.hlsl"; + DXPixelShaderFile = "./SMAA_Neighbor_H_Blending_P.hlsl"; + + OGLVertexShaderFile = "./gl/SMAA_Neighbor_H_Blending_V.glsl"; + OGLPixelShaderFile = "./gl/SMAA_Neighbor_H_Blending_P.glsl"; + + samplerNames[0] = "$sceneTex"; + samplerNames[1] = "$blendTex"; + + pixVersion = 3.0; +}; + +singleton PostEffect( SMAAPostFX ) +{ + enabled = false; + + allowReflectPass = false; + renderTime = "PFXAfterDiffuse"; + + texture[0] = "$backBuffer"; + texture[1] = "#deferred"; + + target = "#edgesPass"; + targetClear = PFXTargetClear_OnDraw; + targetClearColor = "0 0 0 0"; + + shader = SMAA_Edge_D_Shader; + stateBlock = SMAA_Edge_D_StateBlock; + + singleton PostEffect() + { + internalName = "Edge Pass"; + + texture[0] = "#edgesPass"; + + textureAsset[1] = "Core_PostFX:AreaTexDX9_image"; + textureAsset[2] = "Core_PostFX:SearchTex_image"; + + target = "#blendPass"; + targetClear = PFXTargetClear_OnDraw; + targetClearColor = "0 0 0 0"; + + shader = SMAA_Blend_W_Shader; + stateBlock = SMAA_Blend_W_StateBlock; + }; + + singleton PostEffect() + { + internalName = "BlendPass"; + + texture[0] = "$backBuffer"; + texture[1] = "#blendPass"; + + target = "$backBuffer"; + + shader = SMAA_Neighbor_H_Shader; + stateBlock = SMAA_Neighbor_H_StateBlock; + }; + +}; + +function SMAAPostFX::preProcess( %this ) +{ + if ( $pref::video::AAMode !$= %this.quality ) + { + %this.quality = $pref::video::AAMode; + if(%this.quality $= "") + %this.quality = "SMAA"; + + if(%this.quality $= "SMAA") + { + %this.setShaderMacro( "SMAA_PRESET_LOW", 0 ); + %this.setShaderMacro( "SMAA_PRESET_MEDIUM", 0 ); + %this.setShaderMacro( "SMAA_PRESET_HIGH", 1 ); + %this.setShaderMacro( "SMAA_PRESET_ULTRA", 0 ); + } + else if(%this.quality $= "SMAA High") + { + %this.setShaderMacro( "SMAA_PRESET_LOW", 0 ); + %this.setShaderMacro( "SMAA_PRESET_MEDIUM", 0 ); + %this.setShaderMacro( "SMAA_PRESET_HIGH", 0 ); + %this.setShaderMacro( "SMAA_PRESET_ULTRA", 1 ); + } + } +} + + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_P.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_P.hlsl new file mode 100644 index 000000000..baa0ee176 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_P.hlsl @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/shaderModel.hlsl" + +#include "SMAA_Params.hlsl" + + +TORQUE_UNIFORM_SAMPLER2D(edgesTex, 0); +TORQUE_UNIFORM_SAMPLER2D(areaTex, 1); +TORQUE_UNIFORM_SAMPLER2D(searchTex, 2); + + +struct v_Blend +{ + float4 hpos : TORQUE_POSITION; + float2 uv0 : TEXCOORD0; + float2 pixcoord : TEXCOORD1; + float4 offset[3] : TEXCOORD2; +}; + + +float4 main( v_Blend IN ) : TORQUE_TARGET0 +{ + + return SMAABlendingWeightCalculationPS(IN.uv0, IN.pixcoord, IN.offset, texture_edgesTex, texture_areaTex, texture_searchTex, float4(0,0,0,0)); + +} + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_V.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_V.hlsl new file mode 100644 index 000000000..e33fc03b2 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_BlendWeight_V.hlsl @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- + +#include "core/rendering/shaders/torque.hlsl" +#include "core/rendering/shaders/postFX/postFx.hlsl" + + +#include "SMAA_Params.hlsl" + +uniform float4 rtParams0; + + +// (2) +struct v_Blend +{ + float4 hpos : TORQUE_POSITION; + float2 uv0 : TEXCOORD0; + float2 pixcoord : TEXCOORD1; + float4 offset[3] : TEXCOORD2; +}; + + + +v_Blend main( PFXVert IN ) +{ + + v_Blend OUT; + + OUT.hpos = float4(IN.pos,1.0f); + OUT.uv0 = viewportCoordToRenderTarget( IN.uv, rtParams0 ); + + SMAABlendingWeightCalculationVS(OUT.uv0, OUT.pixcoord, OUT.offset); + + return OUT; +} diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_P.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_P.hlsl new file mode 100644 index 000000000..4cd250b8f --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_P.hlsl @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/shaderModelAutoGen.hlsl" +#include "core/rendering/shaders/shaderModel.hlsl" +#include "SMAA_Params.hlsl" + + +TORQUE_UNIFORM_SAMPLER2D(sceneTex, 0); +TORQUE_UNIFORM_SAMPLER2D(depthTex, 1); + +struct v_Edge +{ + float4 hpos : TORQUE_POSITION; + float2 uv0 : TEXCOORD0; + float4 offset[3] : TEXCOORD1; +}; + + +float4 main( v_Edge IN ) : TORQUE_TARGET0 +{ + +#if SMAA_PREDICATION + float4 depth = TORQUE_DEFERRED_UNCONDITION( depthTex, IN.uv0 ).w; + + return float4(SMAALumaEdgeDetectionPS(IN.uv0, IN.offset, texture_sceneTex, texture_depthTex),0,0); +#else + return float4(SMAALumaEdgeDetectionPS(IN.uv0, IN.offset, texture_sceneTex),0,0); + #endif + +} + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_V.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_V.hlsl new file mode 100644 index 000000000..c299f2368 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Edge_V.hlsl @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- + +#include "core/rendering/shaders/torque.hlsl" +#include "core/rendering/shaders/postFX/postFx.hlsl" + + +#include "SMAA_Params.hlsl" + +uniform float4 rtParams0; + + +// (1) +struct v_Edge +{ + float4 hpos : TORQUE_POSITION; + float2 uv0 : TEXCOORD0; + float4 offset[3] : TEXCOORD1; +}; + + +v_Edge main( PFXVert IN ) +{ + + v_Edge OUT; + + OUT.hpos = float4(IN.pos,1.0f); + OUT.uv0 = viewportCoordToRenderTarget( IN.uv, rtParams0 ); + + SMAAEdgeDetectionVS(OUT.uv0, OUT.offset); + + return OUT; +} diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_P.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_P.hlsl new file mode 100644 index 000000000..2511dc448 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_P.hlsl @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/shaderModel.hlsl" + +#include "SMAA_Params.hlsl" + + +TORQUE_UNIFORM_SAMPLER2D(sceneTex, 0); +TORQUE_UNIFORM_SAMPLER2D(blendTex, 1); + +struct v_NHBlend +{ + float4 hpos : TORQUE_POSITION; + float2 uv0 : TEXCOORD0; + float4 offset : TEXCOORD1; +}; + + +float4 main( v_NHBlend IN ) : TORQUE_TARGET0 +{ + //return float4(TORQUE_TEX2D(blendTex, IN.uv0)); + return SMAANeighborhoodBlendingPS(IN.uv0, IN.offset, texture_sceneTex, texture_blendTex); +} + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_V.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_V.hlsl new file mode 100644 index 000000000..272e7ff67 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Neighbor_H_Blending_V.hlsl @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- + +#include "core/rendering/shaders/torque.hlsl" +#include "core/rendering/shaders/postFX/postFx.hlsl" + + +#include "SMAA_Params.hlsl" + +uniform float4 rtParams0; + + +// (3) +struct v_NHBlend +{ + float4 hpos : TORQUE_POSITION; + float2 uv0 : TEXCOORD0; + float4 offset : TEXCOORD1; +}; + + + +v_NHBlend main( PFXVert IN ) +{ + + v_NHBlend OUT; + + OUT.hpos = float4(IN.pos,1.0f); + OUT.uv0 = viewportCoordToRenderTarget( IN.uv, rtParams0 ); + + SMAANeighborhoodBlendingVS(OUT.uv0, OUT.offset); + + return OUT; +} diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Params.hlsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Params.hlsl new file mode 100644 index 000000000..52a5c4399 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/SMAA_Params.hlsl @@ -0,0 +1,16 @@ +uniform float2 oneOverTargetSize; +uniform float2 targetSize; + +#define SMAA_RT_METRICS float4(oneOverTargetSize, targetSize) + + +//Used for defining SMAA params +#define SMAA_HLSL_4 1 +#define SMAA_PREDICATION 0 + +#include "SMAA.h" + + + + + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex.dds b/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex.dds new file mode 100644 index 0000000000000000000000000000000000000000..7a3b1f8d1c9b2b59fa71211faa3622d3b9dcc314 GIT binary patch literal 268928 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 literal 0 HcmV?d00001 diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTexDX9.dds b/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTexDX9.dds new file mode 100644 index 0000000000000000000000000000000000000000..611aba021292c0a02683ce3533dd5f743fe9ec59 GIT binary patch literal 179328 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 diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex_image.asset.taml b/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex_image.asset.taml new file mode 100644 index 000000000..8a7e49a59 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/AreaTex_image.asset.taml @@ -0,0 +1,3 @@ + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/SearchTex.dds b/Templates/BaseGame/game/core/postFX/scripts/SMAA/Textures/SearchTex.dds new file mode 100644 index 0000000000000000000000000000000000000000..f48ec89dda75f9e6f61a3265c5b75fa6ee75639e GIT binary patch literal 1152 zcmZ>930A0KU|?Vu;9w8{(hfk(zycyj2MS;0|-ETY=Bbz*vuqFj8gk4)lZ6BX`vQ5s^I1$3qm6fSNtI>B}YG8519-~ N?Wa^fna& diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA.glsl new file mode 100644 index 000000000..1cab0ed5e --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA.glsl @@ -0,0 +1,1144 @@ +/** + * 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. + */ + +//See SMAA.h for further documentation + +#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 +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#define SMAA_PREDICATION_THRESHOLD 0.01 +#define SMAA_PREDICATION_SCALE 2.0 +#define SMAA_PREDICATION_STRENGTH 0.4 +#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 +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#define SMAA_PREDICATION_THRESHOLD 0.01 +#define SMAA_PREDICATION_SCALE 2.0 +#define SMAA_PREDICATION_STRENGTH 0.4 +#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 (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 and texture 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 +#if defined(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 (1.0 / float2(160.0, 560.0)) +#define SMAA_AREATEX_SUBTEX_SIZE (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(SMAA_CORNER_ROUNDING) / 100.0) + +//----------------------------------------------------------------------------- +// Porting Functions + +#if defined(SMAA_HLSL_3) +#ifndef SMAA_FLIP_Y +#define SMAA_FLIP_Y 0 +#endif // SMAA_FLIP_Y +#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) +#ifndef SMAA_FLIP_Y +#define SMAA_FLIP_Y 0 +#endif // SMAA_FLIP_Y +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) +#ifndef SMAA_FLIP_Y +#define SMAA_FLIP_Y 1 +#endif // SMAA_FLIP_Y + +#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 SMAATexture2DMS2(tex) sampler2DMS tex +#define SMAALoad(tex, pos, sample) texelFetch(tex, pos, sample) +#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 + + +#if SMAA_FLIP_Y + +#define API_V_DIR(v) -(v) +#define API_V_COORD(v) (1.0 - v) +#define API_V_BELOW(v1, v2) v1 < v2 +#define API_V_ABOVE(v1, v2) v1 > v2 + +#else // TORQUE_FLIP + +#define API_V_DIR(v) v +#define API_V_COORD(v) v +#define API_V_BELOW(v1, v2) v1 > v2 +#define API_V_ABOVE(v1, v2) v1 < v2 + +#endif // TORQUE_FLIP + + +//----------------------------------------------------------------------------- +// Misc functions + +#if SMAA_INCLUDE_PS +/** + * Gathers current pixel, and the top-left neighbors. + */ +float3 SMAAGatherNeighbours(float2 texcoord, + float4 offset[3], + SMAATexture2D(tex)) { + #ifdef SMAAGather + + #if SMAA_FLIP_Y + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, 0.5)).zwy; + #else // SMAA_FLIP_Y + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; + #endif // SMAA_FLIP_Y + + #else // SMAAGather + float P = SMAASamplePoint(tex, texcoord).r; + float Pleft = SMAASamplePoint(tex, offset[0].xy).r; + float Ptop = SMAASamplePoint(tex, offset[0].zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset[3], + 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); +} + +#endif // SMAA_INCLUDE_PS + +/** + * Conditional move: + */ +void SMAAMovc(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) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + + +#if SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset[3]) { + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, API_V_DIR(-1.0)), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, API_V_DIR(1.0)), texcoord.xyxy); + offset[2] = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, API_V_DIR(-2.0)), texcoord.xyxy); +} + +/** + * Blend Weight Calculation Vertex Shader + */ +void SMAABlendingWeightCalculationVS(float2 texcoord, + out float2 pixcoord, + out float4 offset[3]) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, API_V_DIR(-0.125), 1.25, API_V_DIR(-0.125)), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, API_V_DIR(-0.25), -0.125, API_V_DIR(1.25)), texcoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + offset[2] = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, API_V_DIR(-2.0), API_V_DIR(2.0)) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset[0].xz, offset[1].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, API_V_DIR(1.0)), texcoord.xyxy); +} +#endif // SMAA_INCLUDE_VS + +#if 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 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if 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, offset[0].xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset[0].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, offset[1].xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].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, offset[2].xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].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 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if 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, offset[0].xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset[0].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, offset[1].xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset[1].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, offset[2].xy).rgb; + t = abs(Cleft - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; + t = abs(Ctop - 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 offset[3], + 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 + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess(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) { + dir.y = API_V_DIR(dir.y); + 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) { + dir.y = API_V_DIR(dir.y); + 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 = SMAADecodeDiagBilinearAccess(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; + + texcoord.y = API_V_COORD(texcoord.y); + + // 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, API_V_DIR(d.x), d.y, API_V_DIR(-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: + SMAAMovc(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, API_V_DIR(-d.x), d.y, API_V_DIR(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, API_V_DIR(-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: + SMAAMovc(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; + + float2 coord = mad(scale, e, bias); + coord.y = API_V_COORD(coord.y); + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, coord)); +} + +/** + * 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 (API_V_BELOW(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, API_V_DIR(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, API_V_DIR(offset), texcoord.y); +} + +float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (API_V_ABOVE(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, API_V_DIR(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, API_V_DIR(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); + + texcoord.y = API_V_COORD(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) { + #if !defined(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, API_V_DIR(1))).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, API_V_DIR(1))).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, API_V_DIR(-2))).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, API_V_DIR(-2))).r; + + weights *= saturate(factor); + #endif +} + +void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(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, API_V_DIR(1))).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, API_V_DIR(1))).g; + + weights *= saturate(factor); + #endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +float4 SMAABlendingWeightCalculationPS(float2 texcoord, + float2 pixcoord, + float4 offset[3], + 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 + #if !defined(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), offset[0].xy, offset[2].x); + coords.y = offset[1].y; // offset[1].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), offset[0].zw, offset[2].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); + + #if !defined(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), offset[1].xy, offset[2].z); + coords.x = offset[0].x; // offset[1].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), offset[1].zw, offset[2].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, API_V_DIR(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) + #if 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); + + #if 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, API_V_DIR(a.y), 0.0, API_V_DIR(a.w)); + float2 blendingWeight = a.yw; + SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); + SMAAMovc(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); + + #if 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) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #if 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 diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_P.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_P.glsl new file mode 100644 index 000000000..248ad9a20 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_P.glsl @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" + + +#include "SMAA_Params.glsl" +#define SMAA_INCLUDE_VS 0 +#define SMAA_INCLUDE_PS 1 + +#include "SMAA.glsl" + +uniform sampler2D edgesTex; +uniform sampler2D areaTex; +uniform sampler2D searchTex; + +in vec4 hpos; +in vec2 uv0; +in vec2 pix_uv; +in vec4 offset[3]; + +out vec4 OUT_col; + +void main() +{ + OUT_col = SMAABlendingWeightCalculationPS(uv0, pix_uv, offset, edgesTex, areaTex, searchTex, vec4(0,0,0,0)); +} + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_V.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_V.glsl new file mode 100644 index 000000000..03d57974f --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_BlendWeight_V.glsl @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/gl/hlslCompat.glsl" +#include "core/rendering/shaders/gl/torque.glsl" + + +in vec4 vPosition; +in vec2 vTexCoord0; +in vec2 vTexCoord1; + +out vec4 hpos; +out vec2 uv0; +out vec2 pix_uv; +out vec4 offset[3]; + +uniform vec4 rtParams0; + +#include "SMAA_Params.glsl" +#define SMAA_INCLUDE_VS 1 +#define SMAA_INCLUDE_PS 0 + +#include "SMAA.glsl" + + +void main() +{ + +#ifndef T3D_FLIP + vTexCoord0 = vTexCoord0 * vec2(1.0, -1.0) + vec2(0.0, 1.0); +#endif // T3D_FLIP + + gl_Position = vPosition; + hpos = gl_Position; + pix_uv = vTexCoord1; + + uv0 = viewportCoordToRenderTarget( vTexCoord0, rtParams0 ); + + SMAABlendingWeightCalculationVS(uv0, pix_uv, offset); + correctSSP(gl_Position); +} diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_P.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_P.glsl new file mode 100644 index 000000000..236041f35 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_P.glsl @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- + +#include "core/rendering/shaders/gl/torque.glsl" +#include "core/rendering/shaders/gl/hlslCompat.glsl" +#include "shadergen:/autogenConditioners.h" + + +#include "SMAA_Params.glsl" +#define SMAA_INCLUDE_VS 0 +#define SMAA_INCLUDE_PS 1 + +#include "SMAA.glsl" + +uniform sampler2D sceneTex; +uniform sampler2D depthTex; + +in vec4 hpos; +in vec2 uv0; +in vec4 offset[3]; + +out vec4 OUT_col; + +void main() +{ + +#if SMAA_PREDICATION + OUT_col = vec4(SMAALumaEdgeDetectionPS(uv0, offset, sceneTex, depthTex),0,0); +#else + OUT_col = vec4(SMAALumaEdgeDetectionPS(uv0, offset, sceneTex),0,0); +#endif +} + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_V.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_V.glsl new file mode 100644 index 000000000..88b3bf52a --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Edge_V.glsl @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- + +#include "core/rendering/shaders/gl/hlslCompat.glsl" +#include "core/rendering/shaders/gl/torque.glsl" + +in vec4 vPosition; +in vec2 vTexCoord0; + +out vec4 hpos; +out vec2 uv0; +out vec4 offset[3]; + +uniform vec4 rtParams0; + +#include "SMAA_Params.glsl" +#define SMAA_INCLUDE_VS 1 +#define SMAA_INCLUDE_PS 0 + +#include "SMAA.glsl" + + +void main() +{ + +#ifndef T3D_FLIP + vTexCoord0 = vTexCoord0 * vec2(1.0, -1.0) + vec2(0.0, 1.0); +#endif // T3D_FLIP + + gl_Position = vPosition; + hpos = gl_Position; + uv0 = viewportCoordToRenderTarget( vTexCoord0, rtParams0 ); + + SMAAEdgeDetectionVS(uv0, offset); + correctSSP(gl_Position); +} diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_P.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_P.glsl new file mode 100644 index 000000000..a615d1d19 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_P.glsl @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/gl/hlslCompat.glsl" + + +#include "SMAA_Params.glsl" +#define SMAA_INCLUDE_VS 0 +#define SMAA_INCLUDE_PS 1 + +#include "SMAA.glsl" + +uniform sampler2D sceneTex; +uniform sampler2D blendTex; + + +in vec4 hpos; +in vec2 uv0; +in vec4 offset; + +out vec4 OUT_col; + +void main() +{ + //OUT_col = vec4(texture(blendTex, uv0.xy)); + OUT_col= SMAANeighborhoodBlendingPS(uv0, offset, sceneTex, blendTex); +} + diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_V.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_V.glsl new file mode 100644 index 000000000..4c2426221 --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Neighbor_H_Blending_V.glsl @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of 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. +// +// 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. +//----------------------------------------------------------------------------- +#include "core/rendering/shaders/gl/hlslCompat.glsl" +#include "core/rendering/shaders/gl/torque.glsl" + + +in vec4 vPosition; +in vec2 vTexCoord0; + +out vec4 hpos; +out vec2 uv0; +out vec4 offset; + +uniform vec4 rtParams0; + +#include "SMAA_Params.glsl" +#define SMAA_INCLUDE_VS 1 +#define SMAA_INCLUDE_PS 0 + +#include "SMAA.glsl" + + +void main() +{ + +#ifndef T3D_FLIP + vTexCoord0 = vTexCoord0 * vec2(1.0, -1.0) + vec2(0.0, 1.0); +#endif // T3D_FLIP + + gl_Position = vPosition; + hpos = gl_Position; + uv0 = viewportCoordToRenderTarget( vTexCoord0, rtParams0 ); + + SMAANeighborhoodBlendingVS(uv0, offset); + correctSSP(gl_Position); +} diff --git a/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Params.glsl b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Params.glsl new file mode 100644 index 000000000..82ef1439a --- /dev/null +++ b/Templates/BaseGame/game/core/postFX/scripts/SMAA/gl/SMAA_Params.glsl @@ -0,0 +1,21 @@ +uniform float2 oneOverTargetSize; +uniform float2 targetSize; +uniform float2 targetRatio; + +#define SMAA_RT_METRICS float4(oneOverTargetSize, targetSize) +#define SMAA_GLSL_4 1 +#define SMAA_PRESET_ULTRA 1 +#define SMAA_PREDICATION 0 + +#define SMAA_FLIP_Y 0 +#define T3D_FLIP 1 + + + + + + + + + + diff --git a/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript b/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript index b05214f3f..19b7f50cd 100644 --- a/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript +++ b/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript @@ -688,6 +688,7 @@ function AutodetectGraphics_Apply(%shaderVer, %intel, %videoMem ) $pref::PostFX::EnableLightRays = false; $pref::PostFX::EnableVignette = false; + $pref::Video::AAMode = "None"; $pref::Video::AA = 0; $pref::Video::defaultAnisotropy = 0; } @@ -714,6 +715,7 @@ function AutodetectGraphics_Apply(%shaderVer, %intel, %videoMem ) $pref::PostFX::EnableLightRays = false; $pref::PostFX::EnableVignette = false; + $pref::Video::AAMode = "None"; $pref::Video::AA = 0; $pref::Video::defaultAnisotropy = 0; } @@ -744,6 +746,7 @@ function AutodetectGraphics_Apply(%shaderVer, %intel, %videoMem ) $pref::PostFX::EnableLightRays = true; $pref::PostFX::EnableVignette = true; + $pref::Video::AAMode = "SMAA High"; $pref::Video::AA = 4; $pref::Video::defaultAnisotropy = 16; } @@ -770,6 +773,7 @@ function AutodetectGraphics_Apply(%shaderVer, %intel, %videoMem ) $pref::PostFX::EnableLightRays = true; $pref::PostFX::EnableVignette = true; + $pref::Video::AAMode = "SMAA"; $pref::Video::AA = 4; $pref::Video::defaultAnisotropy = 4; @@ -799,6 +803,7 @@ function AutodetectGraphics_Apply(%shaderVer, %intel, %videoMem ) $pref::PostFX::EnableLightRays = false; $pref::PostFX::EnableVignette = false; + $pref::Video::AAMode = "FXAA"; $pref::Video::AA = 0; $pref::Video::defaultAnisotropy = 0; } diff --git a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml index b202a86da..61d1a8664 100644 --- a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml +++ b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml @@ -7,4 +7,5 @@ DecalsFile="@assetFile=ExampleLevel.mis.decals" ForestFile="@assetFile=ExampleLevel.forest" NavmeshFile="@assetFile=ExampleLevel.nav" + staticObjectAssetDependency0="@asset=Prototyping:FloorGray" VersionId="1"/> diff --git a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis index 886ed08a4..1a27a6576 100644 --- a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis +++ b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis @@ -12,7 +12,6 @@ new Scene(ExampleLevel) { Enabled = "1"; }; new SkyBox(theSky) { - Material = "BlankSkyMat"; MaterialAsset = "Core_Rendering:BlankSkyMat"; drawBottom = "0"; dirtyGameObject = "0"; @@ -22,6 +21,7 @@ new Scene(ExampleLevel) { elevation = "45"; color = "0.968628 0.901961 0.901961 1"; ambient = "0.337255 0.533333 0.619608 1"; + brightness = "2"; texSize = "2048"; overDarkFactor = "3000 1500 750 250"; shadowDistance = "200"; diff --git a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.postfxpreset.tscript b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.postfxpreset.tscript index 571c1300f..db82dfa22 100644 --- a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.postfxpreset.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.postfxpreset.tscript @@ -1,14 +1,23 @@ $PostFX::HDRPostFX::Enabled = 1; +$PostFX::HDRPostFX::exposureValue = 1.5; +$PostFX::HDRPostFX::whitePoint = 2.5; +$PostFX::HDRPostFX::logContrast = 1; +$PostFX::HDRPostFX::saturationValue = 1; +$PostFX::HDRPostFX::colorFilter = "1.0 1.0 1.0"; $PostFX::HDRPostFX::minLuminace = 0.001; $PostFX::HDRPostFX::whiteCutoff = 1; $PostFX::HDRPostFX::adaptRate = 2; $PostFX::HDRPostFX::tonemapMode = "ACES"; -$PostFX::HDRPostFX::enableBloom = 1; -$PostFX::HDRPostFX::brightPassThreshold = 1; -$PostFX::HDRPostFX::gaussMultiplier = 0.3; -$PostFX::HDRPostFX::gaussMean = 0; -$PostFX::HDRPostFX::gaussStdDev = 0.8; $PostFX::HDRPostFX::enableAutoExposure = "0"; $PostFX::HDRPostFX::keyValue = 0.18; -$PostFX::HDRPostFX::enableBlueShift = 0; -$PostFX::HDRPostFX::blueShiftColor = "1.05 0.97 1.27"; +$PostFX::HDRPostFX::enableBloom = 1; +$PostFX::HDRPostFX::threshold = 1.25; +$PostFX::HDRPostFX::intensity = 0.25; +$PostFX::HDRPostFX::radius = 4; +$PostFX::HDRPostFX::enableDirt = 1; +$PostFX::HDRPostFX::dirtScale = 2048; +$PostFX::HDRPostFX::dirtIntensity = 2; +$PostFX::HDRPostFX::dirtImage = "core/postFX/images/lensDirt.png"; +$PostFX::HDRPostFX::dirtEdgeMinDist = 0.125; +$PostFX::HDRPostFX::dirtEdgeMaxDist = 0.75; +$PostFX::HDRPostFX::dirtEdgeMinVal = 0.05; diff --git a/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript b/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript index dd83f9082..2c01e62c5 100644 --- a/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript @@ -474,7 +474,7 @@ function populateGraphicsSettingsList() %yesNoList = "No\tYes"; %onOffList = "Off\tOn"; %anisoFilter = "Off\t4\t8\t16"; - %aaFilter = "Off\t1\t2\t4"; + %aaTypeFilter = "None\tFXAA\tSMAA\tSMAA High"; OptionsMenuSettingsList.addOptionQualityLevelRow("Lighting Quality", "$pref::Graphics::LightingQuality", LightingQualityList, false, "", true, "Amount and drawdistance of local lights"); OptionsMenuSettingsList.addOptionQualityLevelRow("Shadow Quality", "$pref::Graphics::ShadowQuality", @@ -502,8 +502,9 @@ function populateGraphicsSettingsList() OptionsMenuSettingsList.addOptionQualityLevelRow("Shader Quality", "$pref::Graphics::ShaderQuality", ShaderQualityGroup, false, "", true, "Dictates the overall shader quality level, adjusting what features are enabled."); OptionsMenuSettingsList.addOptionRow("Anisotropic Filtering", "$pref::Video::defaultAnisotropy", %anisoFilter, false, "", true, "Amount of Anisotropic Filtering on textures, which dictates their sharpness at a distance"); - OptionsMenuSettingsList.addOptionRow("Anti-Aliasing", "$pref::Video::AA", %aaFilter, false, "", true, "Amount of Post-Processing Anti-Aliasing applied to rendering"); + OptionsMenuSettingsList.addOptionRow("Anti-Aliasing Type", "$pref::Video::AAMode", %aaTypeFilter, false, "", true, "The Anti-Aliasing Method applied to rendering"); + OptionsMenuSettingsList.addOptionBoolRow("Parallax", "$pref::Video::enableParallaxMapping", %onOffList, false, "", true, "Whether the surface parallax shader effect is enabled", ""); OptionsMenuSettingsList.addOptionBoolRow("Water Reflections", "$pref::Water::enableTrueReflections", %onOffList, false, "", true, "Whether water reflections are enabled", ""); @@ -588,10 +589,10 @@ function updateDisplaySettings() %newFullScreen = %deviceModeName $= "Fullscreen" ? true : false; %newRefresh = $pref::Video::RefreshRate; %newVsync = $pref::Video::enableVerticalSync; - %newFSAA = $pref::Video::AA; + %newAA = $pref::Video::AA; // Build the final mode string. - %newMode = $pref::Video::Resolution SPC %newFullScreen SPC %newBpp SPC %newRefresh SPC %newFSAA; + %newMode = $pref::Video::Resolution SPC %newFullScreen SPC %newBpp SPC %newRefresh SPC %newAA; // Change the video mode. if ( %newMode !$= $pref::Video::mode || %newDeviceID != $pref::Video::deviceId || @@ -609,7 +610,7 @@ function updateDisplaySettings() $pref::Video::deviceId = %newDeviceID; $pref::Video::deviceMode = $Video::ModeBorderless; %tmpModeStr = Canvas.getMonitorMode(%newDeviceID, 0); - Canvas.setVideoMode(%tmpModeStr.x, %tmpModeStr.y, false, 32, getWord(%tmpModeStr, $WORD::REFRESH), %newFSAA); + Canvas.setVideoMode(%tmpModeStr.x, %tmpModeStr.y, false, 32, getWord(%tmpModeStr, $WORD::REFRESH), %newAA); } $pref::Video::mode = %newMode; @@ -619,7 +620,7 @@ function updateDisplaySettings() $pref::Video::Resolution = %newRes; $pref::Video::FullScreen = %newFullScreen; $pref::Video::RefreshRate = %newRefresh; - $pref::Video::AA = %newFSAA; + $pref::Video::AA = %newAA; configureCanvas(); } } diff --git a/Templates/BaseGame/game/data/defaults.tscript b/Templates/BaseGame/game/data/defaults.tscript index 594ec52ea..8b8237313 100644 --- a/Templates/BaseGame/game/data/defaults.tscript +++ b/Templates/BaseGame/game/data/defaults.tscript @@ -34,7 +34,7 @@ $pref::Video::screenShotSession = 0; $pref::Video::screenShotFormat = "PNG"; /// This disables the hardware FSAA/MSAA so that -/// we depend completely on the FXAA post effect +/// we depend completely on the AA post effects /// which works on all cards and in deferred mode. /// /// Note the new Intel Hybrid graphics on laptops @@ -43,6 +43,8 @@ $pref::Video::screenShotFormat = "PNG"; /// $pref::Video::disableHardwareAA = true; +$pref::video::AAMode = "None"; + $pref::Video::disableNormalmapping = false; $pref::Video::disablePixSpecular = false; diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shape.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shape.tscript index fc850cb09..ffe6c53ac 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shape.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shape.tscript @@ -34,6 +34,7 @@ function AssetBrowser::createShapeAsset(%this) function AssetBrowser::editShapeAsset(%this, %assetDef) { %this.hideDialog(); + EditorGui.setEditor( ShapeEditorPlugin ); ShapeEditorPlugin.openShapeAsset(%assetDef); } diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shapeAnimation.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shapeAnimation.tscript index bb76e40d0..2593599b5 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shapeAnimation.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/shapeAnimation.tscript @@ -28,6 +28,7 @@ function AssetBrowser::createShapeAnimationAsset(%this) function AssetBrowser::editShapeAnimationAsset(%this, %assetDef) { %this.hideDialog(); + EditorGui.setEditor( ShapeEditorPlugin ); ShapeEditorPlugin.openShapeAsset(%assetDef); } diff --git a/Tools/CMake/modules/module_curl.cmake b/Tools/CMake/modules/module_curl.cmake index 990a5bda1..5d2c8c777 100644 --- a/Tools/CMake/modules/module_curl.cmake +++ b/Tools/CMake/modules/module_curl.cmake @@ -20,7 +20,8 @@ # IN THE SOFTWARE. # ----------------------------------------------------------------------------- -option(TORQUE_NET_CURL "Use CURL and enable HTTPObject" ON) +option(TORQUE_NET_CURL "Use CURL and enable HTTPObject" OFF) +mark_as_advanced(TORQUE_NET_CURL) if( NOT TORQUE_NET_CURL ) return() diff --git a/Tools/CMake/torque3d.cmake b/Tools/CMake/torque3d.cmake index 0fd154d06..923151475 100644 --- a/Tools/CMake/torque3d.cmake +++ b/Tools/CMake/torque3d.cmake @@ -247,7 +247,7 @@ endif() # Always enabled paths first ############################################################################### addPath("${srcDir}/") # must come first :) -addPathRec("${srcDir}/app") +addPath("${srcDir}/app") addPath("${srcDir}/sfx/media") addPath("${srcDir}/sfx/null") addPath("${srcDir}/sfx") @@ -295,7 +295,7 @@ addPath("${srcDir}/platform/output") addPath("${srcDir}/app") if (NOT TORQUE_NET_CURL) - set(BLACKLIST "httpObject.h httpObject.cpp") + set(BLACKLIST "httpObject.h" "httpObject.cpp" ) endif() addPath("${srcDir}/app/net") set(BLACKLIST "")