From 85435c74bfe87ca9f1dc7e46a2a70aa3c080f764 Mon Sep 17 00:00:00 2001 From: WLs50 Date: Sat, 5 Apr 2025 12:16:40 +0200 Subject: [PATCH] fix gitignore & add missing shader files --- manul/shaders/.gitignore | 3 +- manul/shaders/XeGTAO/GTAO.hlsl | 162 ++ manul/shaders/XeGTAO/XeGTAO.h | 263 +++ manul/shaders/XeGTAO/XeGTAO.hlsli | 855 ++++++++ manul/shaders/XeGTAO/vaShaderCore.h | 82 + manul/shaders/manul/aerial_perspective.hlsli | 6 + manul/shaders/manul/alpha_mask.hlsli | 8 + manul/shaders/manul/amd/ffx_a.h | 1907 +++++++++++++++++ manul/shaders/manul/amd/ffx_spd.h | 1297 +++++++++++ manul/shaders/manul/apply_auto_exposure.hlsl | 7 + manul/shaders/manul/bloom.hlsl | 90 + manul/shaders/manul/color_transform.hlsli | 61 + manul/shaders/manul/comp_luminance.hlsl | 150 ++ manul/shaders/manul/draw_constants.hlsli | 31 + .../shaders/manul/draw_constants_shadow.hlsli | 19 + .../manul/forward_plus/forward_plus.hlsl | 436 ++++ manul/shaders/manul/forward_plus/light.hlsli | 34 + .../manul/forward_plus/primitives.hlsli | 259 +++ .../manul/gbuffer_contact_shadows.hlsli | 10 + manul/shaders/manul/gbuffer_lights.hlsl | 106 + manul/shaders/manul/gbuffer_ssao.hlsli | 27 + manul/shaders/manul/geo_line.hlsl | 0 manul/shaders/manul/lighting.hlsli | 124 ++ manul/shaders/manul/lighting_functions.hlsli | 122 ++ manul/shaders/manul/line.hlsl | 113 + manul/shaders/manul/macros.hlsli | 1 + manul/shaders/manul/material.hlsli | 102 + manul/shaders/manul/material_common.hlsli | 20 + manul/shaders/manul/math.hlsli | 28 + manul/shaders/manul/shadow.hlsli | 87 + manul/shaders/manul/sky copy.hlsli | 429 ++++ manul/shaders/manul/sky.hlsli | 107 + manul/shaders/manul/sky1.hlsli | 630 ++++++ manul/shaders/manul/sky_aerial_lut.hlsl | 116 + manul/shaders/manul/sky_common.hlsli | 328 +++ .../shaders/manul/sky_inscattering copy.hlsli | 95 + manul/shaders/manul/sky_inscattering.hlsli | 94 + manul/shaders/manul/sky_transmittance.hlsl | 40 + manul/shaders/manul/view_data.hlsli | 23 + 39 files changed, 8271 insertions(+), 1 deletion(-) create mode 100644 manul/shaders/XeGTAO/GTAO.hlsl create mode 100644 manul/shaders/XeGTAO/XeGTAO.h create mode 100644 manul/shaders/XeGTAO/XeGTAO.hlsli create mode 100644 manul/shaders/XeGTAO/vaShaderCore.h create mode 100644 manul/shaders/manul/aerial_perspective.hlsli create mode 100644 manul/shaders/manul/alpha_mask.hlsli create mode 100644 manul/shaders/manul/amd/ffx_a.h create mode 100644 manul/shaders/manul/amd/ffx_spd.h create mode 100644 manul/shaders/manul/apply_auto_exposure.hlsl create mode 100644 manul/shaders/manul/bloom.hlsl create mode 100644 manul/shaders/manul/color_transform.hlsli create mode 100644 manul/shaders/manul/comp_luminance.hlsl create mode 100644 manul/shaders/manul/draw_constants.hlsli create mode 100644 manul/shaders/manul/draw_constants_shadow.hlsli create mode 100644 manul/shaders/manul/forward_plus/forward_plus.hlsl create mode 100644 manul/shaders/manul/forward_plus/light.hlsli create mode 100644 manul/shaders/manul/forward_plus/primitives.hlsli create mode 100644 manul/shaders/manul/gbuffer_contact_shadows.hlsli create mode 100644 manul/shaders/manul/gbuffer_lights.hlsl create mode 100644 manul/shaders/manul/gbuffer_ssao.hlsli create mode 100644 manul/shaders/manul/geo_line.hlsl create mode 100644 manul/shaders/manul/lighting.hlsli create mode 100644 manul/shaders/manul/lighting_functions.hlsli create mode 100644 manul/shaders/manul/line.hlsl create mode 100644 manul/shaders/manul/macros.hlsli create mode 100644 manul/shaders/manul/material.hlsli create mode 100644 manul/shaders/manul/material_common.hlsli create mode 100644 manul/shaders/manul/math.hlsli create mode 100644 manul/shaders/manul/shadow.hlsli create mode 100644 manul/shaders/manul/sky copy.hlsli create mode 100644 manul/shaders/manul/sky.hlsli create mode 100644 manul/shaders/manul/sky1.hlsli create mode 100644 manul/shaders/manul/sky_aerial_lut.hlsl create mode 100644 manul/shaders/manul/sky_common.hlsli create mode 100644 manul/shaders/manul/sky_inscattering copy.hlsli create mode 100644 manul/shaders/manul/sky_inscattering.hlsli create mode 100644 manul/shaders/manul/sky_transmittance.hlsl create mode 100644 manul/shaders/manul/view_data.hlsli diff --git a/manul/shaders/.gitignore b/manul/shaders/.gitignore index a4949df6..ce59f358 100644 --- a/manul/shaders/.gitignore +++ b/manul/shaders/.gitignore @@ -1,4 +1,5 @@ -* +*.* !*.hlsl* +!*.h !project.manul \ No newline at end of file diff --git a/manul/shaders/XeGTAO/GTAO.hlsl b/manul/shaders/XeGTAO/GTAO.hlsl new file mode 100644 index 00000000..9a69f3fd --- /dev/null +++ b/manul/shaders/XeGTAO/GTAO.hlsl @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2021, Intel Corporation +// +// SPDX-License-Identifier: MIT +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// XeGTAO is based on GTAO/GTSO "Jimenez et al. / Practical Real-Time Strategies for Accurate Indirect Occlusion", +// https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf +// +// Implementation: Filip Strugar (filip.strugar@intel.com), Steve Mccalla (\_/) +// Version: (see XeGTAO.h) (='.'=) +// Details: https://github.com/GameTechDev/XeGTAO (")_(") +// +// Version history: see XeGTAO.h +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//#include "vaShared.hlsl" +//#include "vaNoise.hlsl" + +#ifndef __INTELLISENSE__ // avoids some pesky intellisense errors +#include "XeGTAO.h" +#endif + +cbuffer GTAOConstantBuffer : register( b0 ) +{ + GTAOConstants g_GTAOConsts; +} + +#include "XeGTAO.hlsli" + +// input output textures for the first pass (XeGTAO_PrefilterDepths16x16) +Texture2D g_srcRawDepth : register( t0 ); // source depth buffer data (in NDC space in DirectX) +RWTexture2D g_outWorkingDepthMIP0 : register( u0 ); // output viewspace depth MIP (these are views into g_srcWorkingDepth MIP levels) +RWTexture2D g_outWorkingDepthMIP1 : register( u1 ); // output viewspace depth MIP (these are views into g_srcWorkingDepth MIP levels) +RWTexture2D g_outWorkingDepthMIP2 : register( u2 ); // output viewspace depth MIP (these are views into g_srcWorkingDepth MIP levels) +RWTexture2D g_outWorkingDepthMIP3 : register( u3 ); // output viewspace depth MIP (these are views into g_srcWorkingDepth MIP levels) +RWTexture2D g_outWorkingDepthMIP4 : register( u4 ); // output viewspace depth MIP (these are views into g_srcWorkingDepth MIP levels) + +// input output textures for the second pass (XeGTAO_MainPass) +Texture2D g_srcWorkingDepth : register( t0 ); // viewspace depth with MIPs, output by XeGTAO_PrefilterDepths16x16 and consumed by XeGTAO_MainPass +Texture2D g_srcNormalmap : register( t1 ); // source normal map (if used) +Texture2D g_srcHilbertLUT : register( t5 ); // hilbert lookup table (if any) +RWTexture2D g_outWorkingAOTerm : register( u0 ); // output AO term (includes bent normals if enabled - packed as R11G11B10 scaled by AO) +RWTexture2D g_outWorkingEdges : register( u1 ); // output depth-based edges used by the denoiser +RWTexture2D g_outNormalmap : register( u0 ); // output viewspace normals if generating from depth + +// input output textures for the third pass (XeGTAO_Denoise) +Texture2D g_srcWorkingAOTerm : register( t0 ); // coming from previous pass +Texture2D g_srcWorkingEdges : register( t1 ); // coming from previous pass +RWTexture2D g_outFinalAOTerm : register( u0 ); // final AO term - just 'visibility' or 'visibility + bent normals' + +SamplerState g_samplerPointClamp : register( s0 ); + +// Engine-specific normal map loader +lpfloat3 LoadNormal( int2 pos ) +{ +#if 0 + // special decoding for external normals stored in 11_11_10 unorm - modify appropriately to support your own encoding + uint packedInput = g_srcNormalmap.Load( int3(pos, 0) ).x; + float3 unpackedOutput = XeGTAO_R11G11B10_UNORM_to_FLOAT3( packedInput ); + float3 normal = normalize(unpackedOutput * 2.0.xxx - 1.0.xxx); +#else + // example of a different encoding + float3 encodedNormal = g_srcNormalmap.Load(int3(pos, 0)).xyz; + float3 normal = normalize(encodedNormal * 2.0.xxx - 1.0.xxx); + normal.z *= -1.0; +#endif + +#if 0 // compute worldspace to viewspace here if your engine stores normals in worldspace; if generating normals from depth here, they're already in viewspace + normal = mul( (float3x3)g_globals.View, normal ); +#endif + + return (lpfloat3)normal; +} + +// Engine-specific screen & temporal noise loader +lpfloat2 SpatioTemporalNoise( uint2 pixCoord, uint temporalIndex ) // without TAA, temporalIndex is always 0 +{ + float2 noise; +#if 1 // Hilbert curve driving R2 (see https://www.shadertoy.com/view/3tB3z3) + #ifdef XE_GTAO_HILBERT_LUT_AVAILABLE // load from lookup texture... + uint index = g_srcHilbertLUT.Load( uint3( pixCoord % 64, 0 ) ).x; + #else // ...or generate in-place? + uint index = HilbertIndex( pixCoord.x, pixCoord.y ); + #endif + index += 288*(temporalIndex%64); // why 288? tried out a few and that's the best so far (with XE_HILBERT_LEVEL 6U) - but there's probably better :) + // R2 sequence - see http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ + return lpfloat2( frac( 0.5 + index * float2(0.75487766624669276005, 0.5698402909980532659114) ) ); +#else // Pseudo-random (fastest but looks bad - not a good choice) + uint baseHash = Hash32( pixCoord.x + (pixCoord.y << 15) ); + baseHash = Hash32Combine( baseHash, temporalIndex ); + return lpfloat2( Hash32ToFloat( baseHash ), Hash32ToFloat( Hash32( baseHash ) ) ); +#endif +} + +// Engine-specific entry point for the first pass +[numthreads(8, 8, 1)] // <- hard coded to 8x8; each thread computes 2x2 blocks so processing 16x16 block: Dispatch needs to be called with (width + 16-1) / 16, (height + 16-1) / 16 +void CSPrefilterDepths16x16( uint2 dispatchThreadID : SV_DispatchThreadID, uint2 groupThreadID : SV_GroupThreadID ) +{ + XeGTAO_PrefilterDepths16x16( dispatchThreadID, groupThreadID, g_GTAOConsts, g_srcRawDepth, g_samplerPointClamp, g_outWorkingDepthMIP0, g_outWorkingDepthMIP1, g_outWorkingDepthMIP2, g_outWorkingDepthMIP3, g_outWorkingDepthMIP4 ); +} + +// Engine-specific entry point for the second pass +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSGTAOLow( const uint2 pixCoord : SV_DispatchThreadID ) +{ + // g_samplerPointClamp is a sampler with D3D12_FILTER_MIN_MAG_MIP_POINT filter and D3D12_TEXTURE_ADDRESS_MODE_CLAMP addressing mode + XeGTAO_MainPass( pixCoord, 1, 2, SpatioTemporalNoise(pixCoord, g_GTAOConsts.NoiseIndex), LoadNormal(pixCoord), g_GTAOConsts, g_srcWorkingDepth, g_samplerPointClamp, g_outWorkingAOTerm, g_outWorkingEdges ); +} + +// Engine-specific entry point for the second pass +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSGTAOMedium( const uint2 pixCoord : SV_DispatchThreadID ) +{ + // g_samplerPointClamp is a sampler with D3D12_FILTER_MIN_MAG_MIP_POINT filter and D3D12_TEXTURE_ADDRESS_MODE_CLAMP addressing mode + XeGTAO_MainPass( pixCoord, 2, 2, SpatioTemporalNoise(pixCoord, g_GTAOConsts.NoiseIndex), LoadNormal(pixCoord), g_GTAOConsts, g_srcWorkingDepth, g_samplerPointClamp, g_outWorkingAOTerm, g_outWorkingEdges ); +} + +// Engine-specific entry point for the second pass +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSGTAOHigh( const uint2 pixCoord : SV_DispatchThreadID ) +{ + // g_samplerPointClamp is a sampler with D3D12_FILTER_MIN_MAG_MIP_POINT filter and D3D12_TEXTURE_ADDRESS_MODE_CLAMP addressing mode + XeGTAO_MainPass( pixCoord, 3, 3, SpatioTemporalNoise(pixCoord, g_GTAOConsts.NoiseIndex), LoadNormal(pixCoord), g_GTAOConsts, g_srcWorkingDepth, g_samplerPointClamp, g_outWorkingAOTerm, g_outWorkingEdges ); +} + +// Engine-specific entry point for the second pass +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSGTAOUltra( const uint2 pixCoord : SV_DispatchThreadID ) +{ + // g_samplerPointClamp is a sampler with D3D12_FILTER_MIN_MAG_MIP_POINT filter and D3D12_TEXTURE_ADDRESS_MODE_CLAMP addressing mode + XeGTAO_MainPass( pixCoord, 9, 3, SpatioTemporalNoise( pixCoord, g_GTAOConsts.NoiseIndex ), LoadNormal( pixCoord ), g_GTAOConsts, g_srcWorkingDepth, g_samplerPointClamp, g_outWorkingAOTerm, g_outWorkingEdges ); +} + +// Engine-specific entry point for the third pass +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSDenoisePass( const uint2 dispatchThreadID : SV_DispatchThreadID ) +{ + const uint2 pixCoordBase = dispatchThreadID * uint2( 2, 1 ); // we're computing 2 horizontal pixels at a time (performance optimization) + // g_samplerPointClamp is a sampler with D3D12_FILTER_MIN_MAG_MIP_POINT filter and D3D12_TEXTURE_ADDRESS_MODE_CLAMP addressing mode + XeGTAO_Denoise( pixCoordBase, g_GTAOConsts, g_srcWorkingAOTerm, g_srcWorkingEdges, g_samplerPointClamp, g_outFinalAOTerm, false ); +} + +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSDenoiseLastPass( const uint2 dispatchThreadID : SV_DispatchThreadID ) +{ + const uint2 pixCoordBase = dispatchThreadID * uint2( 2, 1 ); // we're computing 2 horizontal pixels at a time (performance optimization) + // g_samplerPointClamp is a sampler with D3D12_FILTER_MIN_MAG_MIP_POINT filter and D3D12_TEXTURE_ADDRESS_MODE_CLAMP addressing mode + XeGTAO_Denoise( pixCoordBase, g_GTAOConsts, g_srcWorkingAOTerm, g_srcWorkingEdges, g_samplerPointClamp, g_outFinalAOTerm, true ); +} + +// Optional screen space viewspace normals from depth generation +[numthreads(XE_GTAO_NUMTHREADS_X, XE_GTAO_NUMTHREADS_Y, 1)] +void CSGenerateNormals( const uint2 pixCoord : SV_DispatchThreadID ) +{ + float3 viewspaceNormal = XeGTAO_ComputeViewspaceNormal( pixCoord, g_GTAOConsts, g_srcRawDepth, g_samplerPointClamp ); + + // pack from [-1, 1] to [0, 1] and then to R11G11B10_UNORM + g_outNormalmap[ pixCoord ] = XeGTAO_FLOAT3_to_R11G11B10_UNORM( saturate( viewspaceNormal * 0.5 + 0.5 ) ); +} +/// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/manul/shaders/XeGTAO/XeGTAO.h b/manul/shaders/XeGTAO/XeGTAO.h new file mode 100644 index 00000000..a2b366c9 --- /dev/null +++ b/manul/shaders/XeGTAO/XeGTAO.h @@ -0,0 +1,263 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2021, Intel Corporation +// +// SPDX-License-Identifier: MIT +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// XeGTAO is based on GTAO/GTSO "Jimenez et al. / Practical Real-Time Strategies for Accurate Indirect Occlusion", +// https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf +// +// Implementation: Filip Strugar (filip.strugar@intel.com), Steve Mccalla (\_/) +// Version: 1.02 (='.'=) +// Details: https://github.com/GameTechDev/XeGTAO (")_(") +// +// Version history: +// 1.00 (2021-08-09): Initial release +// 1.01 (2021-09-02): Fix for depth going to inf for 'far' depth buffer values that are out of fp16 range +// 1.02 (2021-09-03): More fast_acos use and made final horizon cos clamping optional (off by default): 3-4% perf boost +// 1.10 (2021-09-03): Added a couple of heuristics to combat over-darkening errors in certain scenarios +// 1.20 (2021-09-06): Optional normal from depth generation is now a standalone pass: no longer integrated into +// main XeGTAO pass to reduce complexity and allow reuse; also quality of generated normals improved +// 1.21 (2021-09-28): Replaced 'groupshared'-based denoiser with a slightly slower multi-pass one where a 2-pass new +// equals 1-pass old. However, 1-pass new is faster than the 1-pass old and enough when TAA enabled. +// 1.22 (2021-09-28): Added 'XeGTAO_' prefix to all local functions to avoid name clashes with various user codebases. +// 1.30 (2021-10-10): Added support for directional component (bent normals). +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef __XE_GTAO_TYPES_H__ +#define __XE_GTAO_TYPES_H__ + +#ifdef __cplusplus + +#include + +namespace XeGTAO +{ + + // cpp<->hlsl mapping + struct Matrix4x4 { float m[16]; }; + struct Vector3 { float x,y,z; }; + struct Vector2 { float x,y; }; + struct Vector2i { int x,y; }; + typedef unsigned int uint; + +#else // #ifdef __cplusplus + + // cpp<->hlsl mapping + #define Matrix4x4 float4x4 + #define Vector3 float3 + #define Vector2 float2 + #define Vector2i int2 + +#endif + + // Global consts that need to be visible from both shader and cpp side + #define XE_GTAO_DEPTH_MIP_LEVELS 5 // this one is hard-coded to 5 for now + #define XE_GTAO_NUMTHREADS_X 8 // these can be changed + #define XE_GTAO_NUMTHREADS_Y 8 // these can be changed + + struct GTAOConstants + { + Vector2i ViewportSize; + Vector2 ViewportPixelSize; // .zw == 1.0 / ViewportSize.xy + + Vector2 DepthUnpackConsts; + Vector2 CameraTanHalfFOV; + + Vector2 NDCToViewMul; + Vector2 NDCToViewAdd; + + Vector2 NDCToViewMul_x_PixelSize; + float EffectRadius; // world (viewspace) maximum size of the shadow + float EffectFalloffRange; + + float RadiusMultiplier; + float Padding0; + float FinalValuePower; + float DenoiseBlurBeta; + + float SampleDistributionPower; + float ThinOccluderCompensation; + float DepthMIPSamplingOffset; + int NoiseIndex; // frameIndex % 64 if using TAA or 0 otherwise + }; + + // This is used only for the development (ray traced ground truth). + struct ReferenceRTAOConstants + { + float TotalRaysLength ; // similar to Radius from GTAO + float Albedo ; // the assumption on the average material albedo + int MaxBounces ; // how many rays to recurse before stopping + int AccumulatedFrames ; // how many frames have we accumulated so far (after resetting/clearing). If 0 - this is the first. + int AccumulateFrameMax ; // how many frames are we aiming to accumulate; stop when we hit! + int Padding0; + int Padding1; + int Padding2; +#ifdef __cplusplus + ReferenceRTAOConstants( ) { TotalRaysLength = 1.0f; Albedo = 0.0f; MaxBounces = 1; AccumulatedFrames = 0; AccumulateFrameMax = 0; } +#endif + }; + + #ifndef XE_GTAO_USE_DEFAULT_CONSTANTS + #define XE_GTAO_USE_DEFAULT_CONSTANTS 1 + #endif + + // some constants reduce performance if provided as dynamic values; if these constants are not required to be dynamic and they match default values, + // set XE_GTAO_USE_DEFAULT_CONSTANTS and the code will compile into a more efficient shader + #define XE_GTAO_DEFAULT_RADIUS_MULTIPLIER (1.457f ) // allows us to use different value as compared to ground truth radius to counter inherent screen space biases + #define XE_GTAO_DEFAULT_FALLOFF_RANGE (0.615f ) // distant samples contribute less + #define XE_GTAO_DEFAULT_SAMPLE_DISTRIBUTION_POWER (2.0f ) // small crevices more important than big surfaces + #define XE_GTAO_DEFAULT_THIN_OCCLUDER_COMPENSATION (0.0f ) // the new 'thickness heuristic' approach + #define XE_GTAO_DEFAULT_FINAL_VALUE_POWER (2.2f ) // modifies the final ambient occlusion value using power function - this allows some of the above heuristics to do different things + #define XE_GTAO_DEFAULT_DEPTH_MIP_SAMPLING_OFFSET (3.30f ) // main trade-off between performance (memory bandwidth) and quality (temporal stability is the first affected, thin objects next) + + #define XE_GTAO_OCCLUSION_TERM_SCALE (1.5f) // for packing in UNORM (because raw, pre-denoised occlusion term can overshoot 1 but will later average out to 1) + + // From https://www.shadertoy.com/view/3tB3z3 - except we're using R2 here + #define XE_HILBERT_LEVEL 6U + #define XE_HILBERT_WIDTH ( (1U << XE_HILBERT_LEVEL) ) + #define XE_HILBERT_AREA ( XE_HILBERT_WIDTH * XE_HILBERT_WIDTH ) + inline uint HilbertIndex( uint posX, uint posY ) + { + uint index = 0U; + for( uint curLevel = XE_HILBERT_WIDTH/2U; curLevel > 0U; curLevel /= 2U ) + { + uint regionX = ( posX & curLevel ) > 0U; + uint regionY = ( posY & curLevel ) > 0U; + index += curLevel * curLevel * ( (3U * regionX) ^ regionY); + if( regionY == 0U ) + { + if( regionX == 1U ) + { + posX = uint( (XE_HILBERT_WIDTH - 1U) ) - posX; + posY = uint( (XE_HILBERT_WIDTH - 1U) ) - posY; + } + + uint temp = posX; + posX = posY; + posY = temp; + } + } + return index; + } + +#ifdef __cplusplus + + struct GTAOSettings + { + int QualityLevel = 2; // 0: low; 1: medium; 2: high; 3: ultra + int DenoisePasses = 1; // 0: disabled; 1: sharp; 2: medium; 3: soft + float Radius = 0.5f; // [0.0, ~ ] World (view) space size of the occlusion sphere. + + // auto-tune-d settings + float RadiusMultiplier = XE_GTAO_DEFAULT_RADIUS_MULTIPLIER; + float FalloffRange = XE_GTAO_DEFAULT_FALLOFF_RANGE; + float SampleDistributionPower = XE_GTAO_DEFAULT_SAMPLE_DISTRIBUTION_POWER; + float ThinOccluderCompensation = XE_GTAO_DEFAULT_THIN_OCCLUDER_COMPENSATION; + float FinalValuePower = XE_GTAO_DEFAULT_FINAL_VALUE_POWER; + float DepthMIPSamplingOffset = XE_GTAO_DEFAULT_DEPTH_MIP_SAMPLING_OFFSET; + }; + + template inline T clamp( T const & v, T const & min, T const & max ) { assert( max >= min ); if( v < min ) return min; if( v > max ) return max; return v; } + + // If using TAA then set noiseIndex to frameIndex % 64 - otherwise use 0 + inline void GTAOUpdateConstants( XeGTAO::GTAOConstants& consts, int viewportWidth, int viewportHeight, const XeGTAO::GTAOSettings & settings, const float projMatrix[16], bool rowMajor, unsigned int frameCounter ) + { + consts.ViewportSize = { viewportWidth, viewportHeight }; + consts.ViewportPixelSize = { 1.0f / (float)viewportWidth, 1.0f / (float)viewportHeight }; + + float depthLinearizeMul = (rowMajor)?(-projMatrix[3 * 4 + 2]):(-projMatrix[3 + 2 * 4]); // float depthLinearizeMul = ( clipFar * clipNear ) / ( clipFar - clipNear ); + float depthLinearizeAdd = (rowMajor)?( projMatrix[2 * 4 + 2]):( projMatrix[2 + 2 * 4]); // float depthLinearizeAdd = clipFar / ( clipFar - clipNear ); + + // correct the handedness issue. need to make sure this below is correct, but I think it is. + if( depthLinearizeMul * depthLinearizeAdd < 0 ) + depthLinearizeAdd = -depthLinearizeAdd; + consts.DepthUnpackConsts = { depthLinearizeMul, depthLinearizeAdd }; + + float tanHalfFOVY = 1.0f / ((rowMajor)?(projMatrix[1 * 4 + 1]):(projMatrix[1 + 1 * 4])); // = tanf( drawContext.Camera.GetYFOV( ) * 0.5f ); + float tanHalfFOVX = 1.0F / ((rowMajor)?(projMatrix[0 * 4 + 0]):(projMatrix[0 + 0 * 4])); // = tanHalfFOVY * drawContext.Camera.GetAspect( ); + consts.CameraTanHalfFOV = { tanHalfFOVX, tanHalfFOVY }; + + consts.NDCToViewMul = { consts.CameraTanHalfFOV.x * 2.0f, consts.CameraTanHalfFOV.y * -2.0f }; + consts.NDCToViewAdd = { consts.CameraTanHalfFOV.x * -1.0f, consts.CameraTanHalfFOV.y * 1.0f }; + + consts.NDCToViewMul_x_PixelSize = { consts.NDCToViewMul.x * consts.ViewportPixelSize.x, consts.NDCToViewMul.y * consts.ViewportPixelSize.y }; + + consts.EffectRadius = settings.Radius; + + consts.EffectFalloffRange = settings.FalloffRange; + consts.DenoiseBlurBeta = (settings.DenoisePasses==0)?(1e4f):(1.2f); // high value disables denoise - more elegant & correct way would be do set all edges to 0 + + consts.RadiusMultiplier = settings.RadiusMultiplier; + consts.SampleDistributionPower = settings.SampleDistributionPower; + consts.ThinOccluderCompensation = settings.ThinOccluderCompensation; + consts.FinalValuePower = settings.FinalValuePower; + consts.DepthMIPSamplingOffset = settings.DepthMIPSamplingOffset; + consts.NoiseIndex = (settings.DenoisePasses>0)?(frameCounter % 64):(0); + consts.Padding0 = 0; + } + +#ifdef IMGUI_API + inline bool GTAOImGuiSettings( XeGTAO::GTAOSettings & settings ) + { + bool hadChanges = false; + + ImGui::PushItemWidth( 120.0f ); + + ImGui::Text( "Performance/quality settings:" ); + + ImGui::Combo( "Quality Level", &settings.QualityLevel, "Low\0Medium\0High\0Ultra\00"); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Higher quality settings use more samples per pixel but are slower" ); + settings.QualityLevel = clamp( settings.QualityLevel , 0, 3 ); + + ImGui::Combo( "Denoising level", &settings.DenoisePasses, "Disabled\0Sharp\0Medium\0Soft\00"); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "The amount of edge-aware spatial denoise applied" ); + settings.DenoisePasses = clamp( settings.DenoisePasses , 0, 3 ); + + ImGui::Text( "Visual settings:" ); + + settings.Radius = clamp( settings.Radius, 0.0f, 100000.0f ); + + hadChanges |= ImGui::InputFloat( "Effect radius", &settings.Radius , 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "World (viewspace) effect radius\nExpected range: depends on the scene & requirements, anything from 0.01 to 1000+" ); + settings.Radius = clamp( settings.Radius , 0.0f, 10000.0f ); + + if( ImGui::CollapsingHeader( "Auto-tuned settings (heuristics)" ) ) + { + hadChanges |= ImGui::InputFloat( "Radius multiplier", &settings.RadiusMultiplier , 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Multiplies the 'Effect Radius' - used by the auto-tune to best match raytraced ground truth\nExpected range: [0.3, 3.0], defaults to %.3f", XE_GTAO_DEFAULT_RADIUS_MULTIPLIER ); + settings.RadiusMultiplier = clamp( settings.RadiusMultiplier , 0.3f, 3.0f ); + + hadChanges |= ImGui::InputFloat( "Falloff range", &settings.FalloffRange , 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Gently reduce sample impact as it gets out of 'Effect radius' bounds\nExpected range: [0.0, 1.0], defaults to %.3f", XE_GTAO_DEFAULT_FALLOFF_RANGE ); + settings.FalloffRange = clamp( settings.FalloffRange , 0.0f, 1.0f ); + + hadChanges |= ImGui::InputFloat( "Sample distribution power", &settings.SampleDistributionPower , 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Make samples on a slice equally distributed (1.0) or focus more towards the center (>1.0)\nExpected range: [1.0, 3.0], 2defaults to %.3f", XE_GTAO_DEFAULT_SAMPLE_DISTRIBUTION_POWER ); + settings.SampleDistributionPower = clamp( settings.SampleDistributionPower , 1.0f, 3.0f ); + + hadChanges |= ImGui::InputFloat( "Thin occluder compensation", &settings.ThinOccluderCompensation, 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Slightly reduce impact of samples further back to counter the bias from depth-based (incomplete) input scene geometry data\nExpected range: [0.0, 0.7], defaults to %.3f", XE_GTAO_DEFAULT_THIN_OCCLUDER_COMPENSATION ); + settings.ThinOccluderCompensation = clamp( settings.ThinOccluderCompensation , 0.0f, 0.7f ); + + hadChanges |= ImGui::InputFloat( "Final power", &settings.FinalValuePower, 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Applies power function to the final value: occlusion = pow( occlusion, finalPower )\nExpected range: [0.5, 5.0], defaults to %.3f", XE_GTAO_DEFAULT_FINAL_VALUE_POWER ); + settings.FinalValuePower = clamp( settings.FinalValuePower , 0.5f, 5.0f ); + + hadChanges |= ImGui::InputFloat( "Depth MIP sampling offset", &settings.DepthMIPSamplingOffset, 0.05f, 0.0f, "%.2f" ); + if( ImGui::IsItemHovered( ) ) ImGui::SetTooltip( "Mainly performance (texture memory bandwidth) setting but as a side-effect reduces overshadowing by thin objects and increases temporal instability\nExpected range: [2.0, 6.0], defaults to %.3f", XE_GTAO_DEFAULT_DEPTH_MIP_SAMPLING_OFFSET ); + settings.DepthMIPSamplingOffset = clamp( settings.DepthMIPSamplingOffset , 0.0f, 30.0f ); + } + + ImGui::PopItemWidth( ); + + return hadChanges; + } +#endif // IMGUI_API + +} // close the namespace + +#endif // #ifdef __cplusplus + + +#endif // __XE_GTAO_TYPES_H__ \ No newline at end of file diff --git a/manul/shaders/XeGTAO/XeGTAO.hlsli b/manul/shaders/XeGTAO/XeGTAO.hlsli new file mode 100644 index 00000000..cfb9566e --- /dev/null +++ b/manul/shaders/XeGTAO/XeGTAO.hlsli @@ -0,0 +1,855 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2021, Intel Corporation +// +// SPDX-License-Identifier: MIT +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// XeGTAO is based on GTAO/GTSO "Jimenez et al. / Practical Real-Time Strategies for Accurate Indirect Occlusion", +// https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf +// +// Implementation: Filip Strugar (filip.strugar@intel.com), Steve Mccalla (\_/) +// Version: (see XeGTAO.h) (='.'=) +// Details: https://github.com/GameTechDev/XeGTAO (")_(") +// +// Version history: see XeGTAO.h +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef XE_GTAO_SHOW_DEBUG_VIZ +#include "vaShared.hlsl" +#endif + +#if defined( XE_GTAO_SHOW_NORMALS ) || defined( XE_GTAO_SHOW_EDGES ) || defined( XE_GTAO_SHOW_BENT_NORMALS ) +RWTexture2D g_outputDbgImage : register( u2 ); +#endif + +#include "XeGTAO.h" + +#define XE_GTAO_PI (3.1415926535897932384626433832795) +#define XE_GTAO_PI_HALF (1.5707963267948966192313216916398) + +#ifndef XE_GTAO_USE_HALF_FLOAT_PRECISION +#define XE_GTAO_USE_HALF_FLOAT_PRECISION 1 +#endif + +#if defined(XE_GTAO_FP32_DEPTHS) && XE_GTAO_USE_HALF_FLOAT_PRECISION +#error Using XE_GTAO_USE_HALF_FLOAT_PRECISION with 32bit depths is not supported yet unfortunately (it is possible to apply fp16 on parts not related to depth but this has not been done yet) +#endif + + +#if (XE_GTAO_USE_HALF_FLOAT_PRECISION != 0) +#if 1 // old fp16 approach ( float3 +float3 XeGTAO_R11G11B10_UNORM_to_FLOAT3( uint packedInput ) +{ + float3 unpackedOutput; + unpackedOutput.x = (float)( ( packedInput ) & 0x000007ff ) / 2047.0f; + unpackedOutput.y = (float)( ( packedInput >> 11 ) & 0x000007ff ) / 2047.0f; + unpackedOutput.z = (float)( ( packedInput >> 22 ) & 0x000003ff ) / 1023.0f; + return unpackedOutput; +} +// 'unpackedInput' is float3 and not float3 on purpose as half float lacks precision for below! +uint XeGTAO_FLOAT3_to_R11G11B10_UNORM( float3 unpackedInput ) +{ + uint packedOutput; + packedOutput =( ( uint( VA_SATURATE( unpackedInput.x ) * 2047 + 0.5f ) ) | + ( uint( VA_SATURATE( unpackedInput.y ) * 2047 + 0.5f ) << 11 ) | + ( uint( VA_SATURATE( unpackedInput.z ) * 1023 + 0.5f ) << 22 ) ); + return packedOutput; +} +// +lpfloat4 XeGTAO_R8G8B8A8_UNORM_to_FLOAT4( uint packedInput ) +{ + lpfloat4 unpackedOutput; + unpackedOutput.x = (lpfloat)( packedInput & 0x000000ff ) / (lpfloat)255; + unpackedOutput.y = (lpfloat)( ( ( packedInput >> 8 ) & 0x000000ff ) ) / (lpfloat)255; + unpackedOutput.z = (lpfloat)( ( ( packedInput >> 16 ) & 0x000000ff ) ) / (lpfloat)255; + unpackedOutput.w = (lpfloat)( packedInput >> 24 ) / (lpfloat)255; + return unpackedOutput; +} +// +uint XeGTAO_FLOAT4_to_R8G8B8A8_UNORM( lpfloat4 unpackedInput ) +{ + return (( uint( saturate( unpackedInput.x ) * (lpfloat)255 + (lpfloat)0.5 ) ) | + ( uint( saturate( unpackedInput.y ) * (lpfloat)255 + (lpfloat)0.5 ) << 8 ) | + ( uint( saturate( unpackedInput.z ) * (lpfloat)255 + (lpfloat)0.5 ) << 16 ) | + ( uint( saturate( unpackedInput.w ) * (lpfloat)255 + (lpfloat)0.5 ) << 24 ) ); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Inputs are screen XY and viewspace depth, output is viewspace position +float3 XeGTAO_ComputeViewspacePosition( const float2 screenPos, const float viewspaceDepth, const GTAOConstants consts ) +{ + float3 ret; + ret.xy = (consts.NDCToViewMul * screenPos.xy + consts.NDCToViewAdd) * viewspaceDepth; + ret.z = viewspaceDepth; + return ret; +} + +float XeGTAO_ScreenSpaceToViewSpaceDepth( const float screenDepth, const GTAOConstants consts ) +{ + float depthLinearizeMul = consts.DepthUnpackConsts.x; + float depthLinearizeAdd = consts.DepthUnpackConsts.y; + // Optimised version of "-cameraClipNear / (cameraClipFar - projDepth * (cameraClipFar - cameraClipNear)) * cameraClipFar" + //return 2500. / (.1 - screenDepth * (.1 - 2500.)) * .1; + return depthLinearizeMul / (depthLinearizeAdd - screenDepth); +} + +lpfloat4 XeGTAO_CalculateEdges( const lpfloat centerZ, const lpfloat leftZ, const lpfloat rightZ, const lpfloat topZ, const lpfloat bottomZ ) +{ + lpfloat4 edgesLRTB = lpfloat4( leftZ, rightZ, topZ, bottomZ ) - (lpfloat)centerZ; + + lpfloat slopeLR = (edgesLRTB.y - edgesLRTB.x) * 0.5; + lpfloat slopeTB = (edgesLRTB.w - edgesLRTB.z) * 0.5; + lpfloat4 edgesLRTBSlopeAdjusted = edgesLRTB + lpfloat4( slopeLR, -slopeLR, slopeTB, -slopeTB ); + edgesLRTB = min( abs( edgesLRTB ), abs( edgesLRTBSlopeAdjusted ) ); + return lpfloat4(saturate( ( 1.25 - edgesLRTB / (centerZ * 0.011) ) )); +} + +// packing/unpacking for edges; 2 bits per edge mean 4 gradient values (0, 0.33, 0.66, 1) for smoother transitions! +lpfloat XeGTAO_PackEdges( lpfloat4 edgesLRTB ) +{ + // integer version: + // edgesLRTB = saturate(edgesLRTB) * 2.9.xxxx + 0.5.xxxx; + // return (((uint)edgesLRTB.x) << 6) + (((uint)edgesLRTB.y) << 4) + (((uint)edgesLRTB.z) << 2) + (((uint)edgesLRTB.w)); + // + // optimized, should be same as above + edgesLRTB = round( saturate( edgesLRTB ) * 2.9 ); + return dot( edgesLRTB, lpfloat4( 64.0 / 255.0, 16.0 / 255.0, 4.0 / 255.0, 1.0 / 255.0 ) ) ; +} + +float3 XeGTAO_CalculateNormal( const float4 edgesLRTB, float3 pixCenterPos, float3 pixLPos, float3 pixRPos, float3 pixTPos, float3 pixBPos ) +{ + // Get this pixel's viewspace normal + float4 acceptedNormals = saturate( float4( edgesLRTB.x*edgesLRTB.z, edgesLRTB.z*edgesLRTB.y, edgesLRTB.y*edgesLRTB.w, edgesLRTB.w*edgesLRTB.x ) + 0.01 ); + + pixLPos = normalize(pixLPos - pixCenterPos); + pixRPos = normalize(pixRPos - pixCenterPos); + pixTPos = normalize(pixTPos - pixCenterPos); + pixBPos = normalize(pixBPos - pixCenterPos); + + float3 pixelNormal = acceptedNormals.x * cross( pixLPos, pixTPos ) + + + acceptedNormals.y * cross( pixTPos, pixRPos ) + + + acceptedNormals.z * cross( pixRPos, pixBPos ) + + + acceptedNormals.w * cross( pixBPos, pixLPos ); + pixelNormal = normalize( pixelNormal ); + + return pixelNormal; +} + +#ifdef XE_GTAO_SHOW_DEBUG_VIZ +float4 DbgGetSliceColor(int slice, int sliceCount, bool mirror) +{ + float red = (float)slice / (float)sliceCount; float green = 0.01; float blue = 1.0 - (float)slice / (float)sliceCount; + return (mirror)?(float4(blue, green, red, 0.9)):(float4(red, green, blue, 0.9)); +} +#endif + +// http://h14s.p5r.org/2012/09/0x5f3759df.html, [Drobot2014a] Low Level Optimizations for GCN, https://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf slide 63 +lpfloat XeGTAO_FastSqrt( float x ) +{ + return (lpfloat)(asfloat( 0x1fbd1df5 + ( asint( x ) >> 1 ) )); +} +// input [-1, 1] and output [0, PI], from https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/ +lpfloat XeGTAO_FastACos( lpfloat inX ) +{ + const lpfloat PI = 3.141593; + const lpfloat HALF_PI = 1.570796; + lpfloat x = abs(inX); + lpfloat res = -0.156583 * x + HALF_PI; + res *= XeGTAO_FastSqrt(1.0 - x); + return (inX >= 0) ? res : PI - res; +} + +uint XeGTAO_EncodeVisibilityBentNormal( lpfloat visibility, lpfloat3 bentNormal ) +{ + return XeGTAO_FLOAT4_to_R8G8B8A8_UNORM( lpfloat4( bentNormal * 0.5 + 0.5, visibility ) ); +} + +void XeGTAO_DecodeVisibilityBentNormal( const uint packedValue, out lpfloat visibility, out lpfloat3 bentNormal ) +{ + lpfloat4 decoded = XeGTAO_R8G8B8A8_UNORM_to_FLOAT4( packedValue ); + bentNormal = decoded.xyz * 2.0.xxx - 1.0.xxx; // could normalize - don't want to since it's done so many times, better to do it at the final step only + visibility = decoded.w; +} + +void XeGTAO_OutputWorkingTerm( const uint2 pixCoord, lpfloat visibility, lpfloat3 bentNormal, RWTexture2D outWorkingAOTerm ) +{ + visibility = saturate( visibility / lpfloat(XE_GTAO_OCCLUSION_TERM_SCALE) ); +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS + outWorkingAOTerm[pixCoord] = XeGTAO_EncodeVisibilityBentNormal( visibility, bentNormal ); +#else + outWorkingAOTerm[pixCoord] = uint(visibility * 255.0 + 0.5); +#endif +} + +// "Efficiently building a matrix to rotate one vector to another" +// http://cs.brown.edu/research/pubs/pdfs/1999/Moller-1999-EBA.pdf / https://dl.acm.org/doi/10.1080/10867651.1999.10487509 +// (using https://github.com/assimp/assimp/blob/master/include/assimp/matrix3x3.inl#L275 as a code reference as it seems to be best) +lpfloat3x3 XeGTAO_RotFromToMatrix( lpfloat3 from, lpfloat3 to ) +{ + const lpfloat e = dot(from, to); + const lpfloat f = abs(e); //(e < 0)? -e:e; + + // WARNING: This has not been tested/worked through, especially not for 16bit floats; seems to work in our special use case (from is always {0, 0, -1}) but wouldn't use it in general + if( f > lpfloat( 1.0 - 0.0003 ) ) + return lpfloat3x3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); + + const lpfloat3 v = cross( from, to ); + /* ... use this hand optimized version (9 mults less) */ + const lpfloat h = (1.0)/(1.0 + e); /* optimization by Gottfried Chen */ + const lpfloat hvx = h * v.x; + const lpfloat hvz = h * v.z; + const lpfloat hvxy = hvx * v.y; + const lpfloat hvxz = hvx * v.z; + const lpfloat hvyz = hvz * v.y; + + lpfloat3x3 mtx; + mtx[0][0] = e + hvx * v.x; + mtx[0][1] = hvxy - v.z; + mtx[0][2] = hvxz + v.y; + + mtx[1][0] = hvxy + v.z; + mtx[1][1] = e + h * v.y * v.y; + mtx[1][2] = hvyz - v.x; + + mtx[2][0] = hvxz - v.y; + mtx[2][1] = hvyz + v.x; + mtx[2][2] = e + hvz * v.z; + + return mtx; +} + +void XeGTAO_MainPass( const uint2 pixCoord, lpfloat sliceCount, lpfloat stepsPerSlice, const lpfloat2 localNoise, lpfloat3 viewspaceNormal, const GTAOConstants consts, + Texture2D sourceViewspaceDepth, SamplerState depthSampler, RWTexture2D outWorkingAOTerm, RWTexture2D outWorkingEdges ) +{ + float2 normalizedScreenPos = (pixCoord + 0.5.xx) * consts.ViewportPixelSize; + + lpfloat4 valuesUL = sourceViewspaceDepth.GatherRed( depthSampler, float2( pixCoord * consts.ViewportPixelSize ) ); + lpfloat4 valuesBR = sourceViewspaceDepth.GatherRed( depthSampler, float2( pixCoord * consts.ViewportPixelSize ), int2( 1, 1 ) ); + + // viewspace Z at the center + lpfloat viewspaceZ = valuesUL.y; //sourceViewspaceDepth.SampleLevel( depthSampler, normalizedScreenPos, 0 ).x; + + // viewspace Zs left top right bottom + const lpfloat pixLZ = valuesUL.x; + const lpfloat pixTZ = valuesUL.z; + const lpfloat pixRZ = valuesBR.z; + const lpfloat pixBZ = valuesBR.x; + + lpfloat4 edgesLRTB = XeGTAO_CalculateEdges( (lpfloat)viewspaceZ, (lpfloat)pixLZ, (lpfloat)pixRZ, (lpfloat)pixTZ, (lpfloat)pixBZ ); + outWorkingEdges[pixCoord] = XeGTAO_PackEdges(edgesLRTB); + + // Generating screen space normals in-place is faster than generating normals in a separate pass but requires + // use of 32bit depth buffer (16bit works but visibly degrades quality) which in turn slows everything down. So to + // reduce complexity and allow for screen space normal reuse by other effects, we've pulled it out into a separate + // pass. + // However, we leave this code in, in case anyone has a use-case where it fits better. +#ifdef XE_GTAO_GENERATE_NORMALS_INPLACE + float3 CENTER = XeGTAO_ComputeViewspacePosition( normalizedScreenPos, viewspaceZ, consts ); + float3 LEFT = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2(-1, 0) * consts.ViewportPixelSize, pixLZ, consts ); + float3 RIGHT = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2( 1, 0) * consts.ViewportPixelSize, pixRZ, consts ); + float3 TOP = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2( 0, -1) * consts.ViewportPixelSize, pixTZ, consts ); + float3 BOTTOM = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2( 0, 1) * consts.ViewportPixelSize, pixBZ, consts ); + viewspaceNormal = (lpfloat3)XeGTAO_CalculateNormal( edgesLRTB, CENTER, LEFT, RIGHT, TOP, BOTTOM ); +#endif + + // Move center pixel slightly towards camera to avoid imprecision artifacts due to depth buffer imprecision; offset depends on depth texture format used +#ifdef XE_GTAO_FP32_DEPTHS + viewspaceZ *= 0.99999; // this is good for FP32 depth buffer +#else + viewspaceZ *= 0.99920; // this is good for FP16 depth buffer +#endif + + const float3 pixCenterPos = XeGTAO_ComputeViewspacePosition( normalizedScreenPos, viewspaceZ, consts ); + const lpfloat3 viewVec = (lpfloat3)normalize(-pixCenterPos); + + // prevents normals that are facing away from the view vector - xeGTAO struggles with extreme cases, but in Vanilla it seems rare so it's disabled by default + // viewspaceNormal = normalize( viewspaceNormal + max( 0, -dot( viewspaceNormal, viewVec ) ) * viewVec ); + +#ifdef XE_GTAO_SHOW_NORMALS + g_outputDbgImage[pixCoord] = float4( DisplayNormalSRGB( viewspaceNormal.xyz ), 1 ); +#endif + +#ifdef XE_GTAO_SHOW_EDGES + g_outputDbgImage[pixCoord] = 1.0 - float4( edgesLRTB.x, edgesLRTB.y * 0.5 + edgesLRTB.w * 0.5, edgesLRTB.z, 1.0 ); +#endif + +#if XE_GTAO_USE_DEFAULT_CONSTANTS != 0 + const lpfloat effectRadius = (lpfloat)consts.EffectRadius * (lpfloat)XE_GTAO_DEFAULT_RADIUS_MULTIPLIER; + const lpfloat sampleDistributionPower = (lpfloat)XE_GTAO_DEFAULT_SAMPLE_DISTRIBUTION_POWER; + const lpfloat thinOccluderCompensation = (lpfloat)XE_GTAO_DEFAULT_THIN_OCCLUDER_COMPENSATION; + const lpfloat falloffRange = (lpfloat)XE_GTAO_DEFAULT_FALLOFF_RANGE * effectRadius; +#else + const lpfloat effectRadius = (lpfloat)consts.EffectRadius * (lpfloat)consts.RadiusMultiplier; + const lpfloat sampleDistributionPower = (lpfloat)consts.SampleDistributionPower; + const lpfloat thinOccluderCompensation = (lpfloat)consts.ThinOccluderCompensation; + const lpfloat falloffRange = (lpfloat)consts.EffectFalloffRange * effectRadius; +#endif + + const lpfloat falloffFrom = effectRadius * ((lpfloat)1-(lpfloat)consts.EffectFalloffRange); + + // fadeout precompute optimisation + const lpfloat falloffMul = (lpfloat)-1.0 / ( falloffRange ); + const lpfloat falloffAdd = falloffFrom / ( falloffRange ) + (lpfloat)1.0; + + lpfloat visibility = 0; +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS + lpfloat3 bentNormal = 0; +#else + lpfloat3 bentNormal = viewspaceNormal; +#endif + +#ifdef XE_GTAO_SHOW_DEBUG_VIZ + float3 dbgWorldPos = mul(g_globals.ViewInv, float4(pixCenterPos, 1)).xyz; +#endif + + // see "Algorithm 1" in https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf + { + const lpfloat noiseSlice = (lpfloat)localNoise.x; + const lpfloat noiseSample = (lpfloat)localNoise.y; + + // quality settings / tweaks / hacks + const lpfloat pixelTooCloseThreshold = 1.3; // if the offset is under approx pixel size (pixelTooCloseThreshold), push it out to the minimum distance + + // approx viewspace pixel size at pixCoord; approximation of NDCToViewspace( normalizedScreenPos.xy + consts.ViewportPixelSize.xy, pixCenterPos.z ).xy - pixCenterPos.xy; + const float2 pixelDirRBViewspaceSizeAtCenterZ = viewspaceZ.xx * consts.NDCToViewMul_x_PixelSize; + + lpfloat screenspaceRadius = effectRadius / (lpfloat)pixelDirRBViewspaceSizeAtCenterZ.x; + + // fade out for small screen radii + visibility += saturate((10 - screenspaceRadius)/100)*0.5; + +#if 0 // sensible early-out for even more performance; disabled because not yet tested + [branch] + if( screenspaceRadius < pixelTooCloseThreshold ) + { + XeGTAO_OutputWorkingTerm( pixCoord, 1, viewspaceNormal, outWorkingAOTerm ); + return; + } +#endif + +#ifdef XE_GTAO_SHOW_DEBUG_VIZ + [branch] if (IsUnderCursorRange(pixCoord, int2(1, 1))) + { + float3 dbgWorldNorm = mul((float3x3)g_globals.ViewInv, viewspaceNormal).xyz; + float3 dbgWorldViewVec = mul((float3x3)g_globals.ViewInv, viewVec).xyz; + //DebugDraw3DArrow(dbgWorldPos, dbgWorldPos + 0.5 * dbgWorldViewVec, 0.02, float4(0, 1, 0, 0.95)); + //DebugDraw2DCircle(pixCoord, screenspaceRadius, float4(1, 0, 0.2, 1)); + DebugDraw3DSphere(dbgWorldPos, effectRadius, float4(1, 0.2, 0, 0.1)); + //DebugDraw3DText(dbgWorldPos, float2(0, 0), float4(0.6, 0.3, 0.3, 1), float4( pixelDirRBViewspaceSizeAtCenterZ.xy, 0, screenspaceRadius) ); + } +#endif + + // this is the min distance to start sampling from to avoid sampling from the center pixel (no useful data obtained from sampling center pixel) + const lpfloat minS = (lpfloat)pixelTooCloseThreshold / screenspaceRadius; + + //[unroll] + for( lpfloat slice = 0; slice < sliceCount; slice++ ) + { + lpfloat sliceK = (slice+noiseSlice) / sliceCount; + // lines 5, 6 from the paper + lpfloat phi = sliceK * XE_GTAO_PI; + lpfloat cosPhi = cos(phi); + lpfloat sinPhi = sin(phi); + lpfloat2 omega = lpfloat2(cosPhi, -sinPhi); //lpfloat2 on omega causes issues with big radii + + // convert to screen units (pixels) for later use + omega *= screenspaceRadius; + + // line 8 from the paper + const lpfloat3 directionVec = lpfloat3(cosPhi, sinPhi, 0); + + // line 9 from the paper + const lpfloat3 orthoDirectionVec = directionVec - (dot(directionVec, viewVec) * viewVec); + + // line 10 from the paper + //axisVec is orthogonal to directionVec and viewVec, used to define projectedNormal + const lpfloat3 axisVec = normalize( cross(orthoDirectionVec, viewVec) ); + + // alternative line 9 from the paper + // float3 orthoDirectionVec = cross( viewVec, axisVec ); + + // line 11 from the paper + lpfloat3 projectedNormalVec = viewspaceNormal - axisVec * dot(viewspaceNormal, axisVec); + + // line 13 from the paper + lpfloat signNorm = (lpfloat)sign( dot( orthoDirectionVec, projectedNormalVec ) ); + + // line 14 from the paper + lpfloat projectedNormalVecLength = length(projectedNormalVec); + lpfloat cosNorm = (lpfloat)saturate(dot(projectedNormalVec, viewVec) / projectedNormalVecLength); + + // line 15 from the paper + lpfloat n = signNorm * XeGTAO_FastACos(cosNorm); + + // this is a lower weight target; not using -1 as in the original paper because it is under horizon, so a 'weight' has different meaning based on the normal + const lpfloat lowHorizonCos0 = cos(n+XE_GTAO_PI_HALF); + const lpfloat lowHorizonCos1 = cos(n-XE_GTAO_PI_HALF); + + // lines 17, 18 from the paper, manually unrolled the 'side' loop + lpfloat horizonCos0 = lowHorizonCos0; //-1; + lpfloat horizonCos1 = lowHorizonCos1; //-1; + + [unroll] + for( lpfloat step = 0; step < stepsPerSlice; step++ ) + { + // R1 sequence (http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/) + const lpfloat stepBaseNoise = lpfloat(slice + step * stepsPerSlice) * 0.6180339887498948482; // <- this should unroll + lpfloat stepNoise = frac(noiseSample + stepBaseNoise); + + // approx line 20 from the paper, with added noise + lpfloat s = (step+stepNoise) / (stepsPerSlice); // + (lpfloat2)1e-6f); + + // additional distribution modifier + s = (lpfloat)pow( s, (lpfloat)sampleDistributionPower ); + + // avoid sampling center pixel + s += minS; + + // approx lines 21-22 from the paper, unrolled + lpfloat2 sampleOffset = s * omega; + + lpfloat sampleOffsetLength = length( sampleOffset ); + + // note: when sampling, using point_point_point or point_point_linear sampler works, but linear_linear_linear will cause unwanted interpolation between neighbouring depth values on the same MIP level! + const lpfloat mipLevel = (lpfloat)clamp( log2( sampleOffsetLength ) - consts.DepthMIPSamplingOffset, 0, XE_GTAO_DEPTH_MIP_LEVELS ); + + // Snap to pixel center (more correct direction math, avoids artifacts due to sampling pos not matching depth texel center - messes up slope - but adds other + // artifacts due to them being pushed off the slice). Also use full precision for high res cases. + sampleOffset = round(sampleOffset) * (lpfloat2)consts.ViewportPixelSize; + +#ifdef XE_GTAO_SHOW_DEBUG_VIZ + int mipLevelU = (int)round(mipLevel); + float4 mipColor = saturate( float4( mipLevelU>=3, mipLevelU>=1 && mipLevelU<=3, mipLevelU<=1, 1.0 ) ); + if( all( sampleOffset == 0 ) ) + DebugDraw2DText( pixCoord, float4( 1, 0, 0, 1), pixelTooCloseThreshold ); + [branch] if (IsUnderCursorRange(pixCoord, int2(1, 1))) + { + //DebugDraw2DText( (normalizedScreenPos + sampleOffset) * consts.ViewportSize, mipColor, mipLevelU ); + //DebugDraw2DText( (normalizedScreenPos + sampleOffset) * consts.ViewportSize, mipColor, (uint)slice ); + //DebugDraw2DText( (normalizedScreenPos - sampleOffset) * consts.ViewportSize, mipColor, (uint)slice ); + //DebugDraw2DText( (normalizedScreenPos - sampleOffset) * consts.ViewportSize, saturate( float4( mipLevelU>=3, mipLevelU>=1 && mipLevelU<=3, mipLevelU<=1, 1.0 ) ), mipLevelU ); + } +#endif + + float2 sampleScreenPos0 = normalizedScreenPos + sampleOffset; + float SZ0 = sourceViewspaceDepth.SampleLevel( depthSampler, sampleScreenPos0, mipLevel ).x; + float3 samplePos0 = XeGTAO_ComputeViewspacePosition( sampleScreenPos0, SZ0, consts ); + + float2 sampleScreenPos1 = normalizedScreenPos - sampleOffset; + float SZ1 = sourceViewspaceDepth.SampleLevel( depthSampler, sampleScreenPos1, mipLevel ).x; + float3 samplePos1 = XeGTAO_ComputeViewspacePosition( sampleScreenPos1, SZ1, consts ); + + float3 sampleDelta0 = (samplePos0 - float3(pixCenterPos)); // using lpfloat for sampleDelta causes precision issues + float3 sampleDelta1 = (samplePos1 - float3(pixCenterPos)); // using lpfloat for sampleDelta causes precision issues + lpfloat sampleDist0 = (lpfloat)length( sampleDelta0 ); + lpfloat sampleDist1 = (lpfloat)length( sampleDelta1 ); + + // approx lines 23, 24 from the paper, unrolled + lpfloat3 sampleHorizonVec0 = (lpfloat3)(sampleDelta0 / sampleDist0); + lpfloat3 sampleHorizonVec1 = (lpfloat3)(sampleDelta1 / sampleDist1); + + // any sample out of radius should be discarded - also use fallof range for smooth transitions; this is a modified idea from "4.3 Implementation details, Bounding the sampling area" +#if XE_GTAO_USE_DEFAULT_CONSTANTS != 0 && XE_GTAO_DEFAULT_THIN_OBJECT_HEURISTIC == 0 + lpfloat weight0 = saturate( sampleDist0 * falloffMul + falloffAdd ); + lpfloat weight1 = saturate( sampleDist1 * falloffMul + falloffAdd ); +#else + // this is our own thickness heuristic that relies on sooner discarding samples behind the center + lpfloat falloffBase0 = length( lpfloat3(sampleDelta0.x, sampleDelta0.y, sampleDelta0.z * (1+thinOccluderCompensation) ) ); + lpfloat falloffBase1 = length( lpfloat3(sampleDelta1.x, sampleDelta1.y, sampleDelta1.z * (1+thinOccluderCompensation) ) ); + lpfloat weight0 = saturate( falloffBase0 * falloffMul + falloffAdd ); + lpfloat weight1 = saturate( falloffBase1 * falloffMul + falloffAdd ); +#endif + + // sample horizon cos + lpfloat shc0 = (lpfloat)dot(sampleHorizonVec0, viewVec); + lpfloat shc1 = (lpfloat)dot(sampleHorizonVec1, viewVec); + + // discard unwanted samples + shc0 = lerp( lowHorizonCos0, shc0, weight0 ); // this would be more correct but too expensive: cos(lerp( acos(lowHorizonCos0), acos(shc0), weight0 )); + shc1 = lerp( lowHorizonCos1, shc1, weight1 ); // this would be more correct but too expensive: cos(lerp( acos(lowHorizonCos1), acos(shc1), weight1 )); + + // thickness heuristic - see "4.3 Implementation details, Height-field assumption considerations" +#if 0 // (disabled, not used) this should match the paper + lpfloat newhorizonCos0 = max( horizonCos0, shc0 ); + lpfloat newhorizonCos1 = max( horizonCos1, shc1 ); + horizonCos0 = (horizonCos0 > shc0)?( lerp( newhorizonCos0, shc0, thinOccluderCompensation ) ):( newhorizonCos0 ); + horizonCos1 = (horizonCos1 > shc1)?( lerp( newhorizonCos1, shc1, thinOccluderCompensation ) ):( newhorizonCos1 ); +#elif 0 // (disabled, not used) this is slightly different from the paper but cheaper and provides very similar results + horizonCos0 = lerp( max( horizonCos0, shc0 ), shc0, thinOccluderCompensation ); + horizonCos1 = lerp( max( horizonCos1, shc1 ), shc1, thinOccluderCompensation ); +#else // this is a version where thicknessHeuristic is completely disabled + horizonCos0 = max( horizonCos0, shc0 ); + horizonCos1 = max( horizonCos1, shc1 ); +#endif + + +#ifdef XE_GTAO_SHOW_DEBUG_VIZ + [branch] if (IsUnderCursorRange(pixCoord, int2(1, 1))) + { + float3 WS_samplePos0 = mul(g_globals.ViewInv, float4(samplePos0, 1)).xyz; + float3 WS_samplePos1 = mul(g_globals.ViewInv, float4(samplePos1, 1)).xyz; + float3 WS_sampleHorizonVec0 = mul( (float3x3)g_globals.ViewInv, sampleHorizonVec0).xyz; + float3 WS_sampleHorizonVec1 = mul( (float3x3)g_globals.ViewInv, sampleHorizonVec1).xyz; + // DebugDraw3DSphere( WS_samplePos0, effectRadius * 0.02, DbgGetSliceColor(slice, sliceCount, false) ); + // DebugDraw3DSphere( WS_samplePos1, effectRadius * 0.02, DbgGetSliceColor(slice, sliceCount, true) ); + DebugDraw3DSphere( WS_samplePos0, effectRadius * 0.02, mipColor ); + DebugDraw3DSphere( WS_samplePos1, effectRadius * 0.02, mipColor ); + // DebugDraw3DArrow( WS_samplePos0, WS_samplePos0 - WS_sampleHorizonVec0, 0.002, float4(1, 0, 0, 1 ) ); + // DebugDraw3DArrow( WS_samplePos1, WS_samplePos1 - WS_sampleHorizonVec1, 0.002, float4(1, 0, 0, 1 ) ); + // DebugDraw3DText( WS_samplePos0, float2(0, 0), float4( 1, 0, 0, 1), weight0 ); + // DebugDraw3DText( WS_samplePos1, float2(0, 0), float4( 1, 0, 0, 1), weight1 ); + + // DebugDraw2DText( float2( 500, 94+(step+slice*3)*12 ), float4( 0, 1, 0, 1 ), float4( projectedNormalVecLength, 0, horizonCos0, horizonCos1 ) ); + } +#endif + } + +#if 1 // I can't figure out the slight overdarkening on high slopes, so I'm adding this fudge - in the training set, 0.05 is close (PSNR 21.34) to disabled (PSNR 21.45) + projectedNormalVecLength = lerp( projectedNormalVecLength, 1, 0.05 ); +#endif + + // line ~27, unrolled + lpfloat h0 = -XeGTAO_FastACos((lpfloat)horizonCos1); + lpfloat h1 = XeGTAO_FastACos((lpfloat)horizonCos0); +#if 0 // we can skip clamping for a tiny little bit more performance + h0 = n + clamp( h0-n, (lpfloat)-XE_GTAO_PI_HALF, (lpfloat)XE_GTAO_PI_HALF ); + h1 = n + clamp( h1-n, (lpfloat)-XE_GTAO_PI_HALF, (lpfloat)XE_GTAO_PI_HALF ); +#endif + lpfloat iarc0 = ((lpfloat)cosNorm + (lpfloat)2 * (lpfloat)h0 * (lpfloat)sin(n)-(lpfloat)cos((lpfloat)2 * (lpfloat)h0-n))/(lpfloat)4; + lpfloat iarc1 = ((lpfloat)cosNorm + (lpfloat)2 * (lpfloat)h1 * (lpfloat)sin(n)-(lpfloat)cos((lpfloat)2 * (lpfloat)h1-n))/(lpfloat)4; + lpfloat localVisibility = (lpfloat)projectedNormalVecLength * (lpfloat)(iarc0+iarc1); + visibility += localVisibility; + +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS + // see "Algorithm 2 Extension that computes bent normals b." + lpfloat t0 = (6*sin(h0-n)-sin(3*h0-n)+6*sin(h1-n)-sin(3*h1-n)+16*sin(n)-3*(sin(h0+n)+sin(h1+n)))/12; + lpfloat t1 = (-cos(3 * h0-n)-cos(3 * h1-n) +8 * cos(n)-3 * (cos(h0+n) +cos(h1+n)))/12; + lpfloat3 localBentNormal = lpfloat3( directionVec.x * (lpfloat)t0, directionVec.y * (lpfloat)t0, -lpfloat(t1) ); + localBentNormal = (lpfloat3)mul( XeGTAO_RotFromToMatrix( lpfloat3(0,0,-1), viewVec ), localBentNormal ) * projectedNormalVecLength; + bentNormal += localBentNormal; +#endif + } + visibility /= (lpfloat)sliceCount; + visibility = pow( visibility, (lpfloat)consts.FinalValuePower ); + visibility = max( (lpfloat)0.03, visibility ); // disallow total occlusion (which wouldn't make any sense anyhow since pixel is visible but also helps with packing bent normals) + +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS + bentNormal = normalize(bentNormal) ; +#endif + } + +#if defined(XE_GTAO_SHOW_DEBUG_VIZ) && defined(XE_GTAO_COMPUTE_BENT_NORMALS) + [branch] if (IsUnderCursorRange(pixCoord, int2(1, 1))) + { + float3 dbgWorldViewNorm = mul((float3x3)g_globals.ViewInv, viewspaceNormal).xyz; + float3 dbgWorldBentNorm = mul((float3x3)g_globals.ViewInv, bentNormal).xyz; + DebugDraw3DSphereCone( dbgWorldPos, dbgWorldViewNorm, 0.3, VA_PI*0.5 - acos(saturate(visibility)), float4( 0.2, 0.2, 0.2, 0.5 ) ); + DebugDraw3DSphereCone( dbgWorldPos, dbgWorldBentNorm, 0.3, VA_PI*0.5 - acos(saturate(visibility)), float4( 0.0, 1.0, 0.0, 0.7 ) ); + } +#endif + + XeGTAO_OutputWorkingTerm( pixCoord, visibility, bentNormal, outWorkingAOTerm ); +} + +// weighted average depth filter +lpfloat XeGTAO_DepthMIPFilter( lpfloat depth0, lpfloat depth1, lpfloat depth2, lpfloat depth3, const GTAOConstants consts ) +{ + lpfloat maxDepth = max( max( depth0, depth1 ), max( depth2, depth3 ) ); + + const lpfloat depthRangeScaleFactor = 0.75; // found empirically :) +#if XE_GTAO_USE_DEFAULT_CONSTANTS != 0 + const lpfloat effectRadius = depthRangeScaleFactor * (lpfloat)consts.EffectRadius * (lpfloat)XE_GTAO_DEFAULT_RADIUS_MULTIPLIER; + const lpfloat falloffRange = (lpfloat)XE_GTAO_DEFAULT_FALLOFF_RANGE * effectRadius; +#else + const lpfloat effectRadius = depthRangeScaleFactor * (lpfloat)consts.EffectRadius * (lpfloat)consts.RadiusMultiplier; + const lpfloat falloffRange = (lpfloat)consts.EffectFalloffRange * effectRadius; +#endif + const lpfloat falloffFrom = effectRadius * ((lpfloat)1-(lpfloat)consts.EffectFalloffRange); + // fadeout precompute optimisation + const lpfloat falloffMul = (lpfloat)-1.0 / ( falloffRange ); + const lpfloat falloffAdd = falloffFrom / ( falloffRange ) + (lpfloat)1.0; + + lpfloat weight0 = saturate( (maxDepth-depth0) * falloffMul + falloffAdd ); + lpfloat weight1 = saturate( (maxDepth-depth1) * falloffMul + falloffAdd ); + lpfloat weight2 = saturate( (maxDepth-depth2) * falloffMul + falloffAdd ); + lpfloat weight3 = saturate( (maxDepth-depth3) * falloffMul + falloffAdd ); + + lpfloat weightSum = weight0 + weight1 + weight2 + weight3; + return (weight0 * depth0 + weight1 * depth1 + weight2 * depth2 + weight3 * depth3) / weightSum; +} + +// This is also a good place to do non-linear depth conversion for cases where one wants the 'radius' (effectively the threshold between near-field and far-field GI), +// is required to be non-linear (i.e. very large outdoors environments). +lpfloat XeGTAO_ClampDepth( float depth ) +{ +#ifdef XE_GTAO_USE_HALF_FLOAT_PRECISION + return (lpfloat)clamp( depth, 0.0, 65504.0 ); +#else + return clamp( depth, 0.0, 3.402823466e+38 ); +#endif +} + +groupshared lpfloat g_scratchDepths[8][8]; +void XeGTAO_PrefilterDepths16x16( uint2 dispatchThreadID /*: SV_DispatchThreadID*/, uint2 groupThreadID /*: SV_GroupThreadID*/, const GTAOConstants consts, Texture2D sourceNDCDepth, SamplerState depthSampler, RWTexture2D outDepth0, RWTexture2D outDepth1, RWTexture2D outDepth2, RWTexture2D outDepth3, RWTexture2D outDepth4 ) +{ + // MIP 0 + const uint2 baseCoord = dispatchThreadID; + const uint2 pixCoord = baseCoord * 2; + float4 depths4 = sourceNDCDepth.GatherRed( depthSampler, float2( pixCoord * consts.ViewportPixelSize ), int2(1,1) ); + lpfloat depth0 = XeGTAO_ClampDepth( XeGTAO_ScreenSpaceToViewSpaceDepth( depths4.w, consts ) ); + lpfloat depth1 = XeGTAO_ClampDepth( XeGTAO_ScreenSpaceToViewSpaceDepth( depths4.z, consts ) ); + lpfloat depth2 = XeGTAO_ClampDepth( XeGTAO_ScreenSpaceToViewSpaceDepth( depths4.x, consts ) ); + lpfloat depth3 = XeGTAO_ClampDepth( XeGTAO_ScreenSpaceToViewSpaceDepth( depths4.y, consts ) ); + outDepth0[ pixCoord + uint2(0, 0) ] = (lpfloat)depth0; + outDepth0[ pixCoord + uint2(1, 0) ] = (lpfloat)depth1; + outDepth0[ pixCoord + uint2(0, 1) ] = (lpfloat)depth2; + outDepth0[ pixCoord + uint2(1, 1) ] = (lpfloat)depth3; + + // MIP 1 + lpfloat dm1 = XeGTAO_DepthMIPFilter( depth0, depth1, depth2, depth3, consts ); + outDepth1[ baseCoord ] = (lpfloat)dm1; + g_scratchDepths[ groupThreadID.x ][ groupThreadID.y ] = dm1; + + GroupMemoryBarrierWithGroupSync( ); + + // MIP 2 + [branch] + if( all( ( groupThreadID.xy % 2.xx ) == 0 ) ) + { + lpfloat inTL = g_scratchDepths[groupThreadID.x+0][groupThreadID.y+0]; + lpfloat inTR = g_scratchDepths[groupThreadID.x+1][groupThreadID.y+0]; + lpfloat inBL = g_scratchDepths[groupThreadID.x+0][groupThreadID.y+1]; + lpfloat inBR = g_scratchDepths[groupThreadID.x+1][groupThreadID.y+1]; + + lpfloat dm2 = XeGTAO_DepthMIPFilter( inTL, inTR, inBL, inBR, consts ); + outDepth2[ baseCoord / 2 ] = (lpfloat)dm2; + g_scratchDepths[ groupThreadID.x ][ groupThreadID.y ] = dm2; + } + + GroupMemoryBarrierWithGroupSync( ); + + // MIP 3 + [branch] + if( all( ( groupThreadID.xy % 4.xx ) == 0 ) ) + { + lpfloat inTL = g_scratchDepths[groupThreadID.x+0][groupThreadID.y+0]; + lpfloat inTR = g_scratchDepths[groupThreadID.x+2][groupThreadID.y+0]; + lpfloat inBL = g_scratchDepths[groupThreadID.x+0][groupThreadID.y+2]; + lpfloat inBR = g_scratchDepths[groupThreadID.x+2][groupThreadID.y+2]; + + lpfloat dm3 = XeGTAO_DepthMIPFilter( inTL, inTR, inBL, inBR, consts ); + outDepth3[ baseCoord / 4 ] = (lpfloat)dm3; + g_scratchDepths[ groupThreadID.x ][ groupThreadID.y ] = dm3; + } + + GroupMemoryBarrierWithGroupSync( ); + + // MIP 4 + [branch] + if( all( ( groupThreadID.xy % 8.xx ) == 0 ) ) + { + lpfloat inTL = g_scratchDepths[groupThreadID.x+0][groupThreadID.y+0]; + lpfloat inTR = g_scratchDepths[groupThreadID.x+4][groupThreadID.y+0]; + lpfloat inBL = g_scratchDepths[groupThreadID.x+0][groupThreadID.y+4]; + lpfloat inBR = g_scratchDepths[groupThreadID.x+4][groupThreadID.y+4]; + + lpfloat dm4 = XeGTAO_DepthMIPFilter( inTL, inTR, inBL, inBR, consts ); + outDepth4[ baseCoord / 8 ] = (lpfloat)dm4; + //g_scratchDepths[ groupThreadID.x ][ groupThreadID.y ] = dm4; + } +} + +lpfloat4 XeGTAO_UnpackEdges( lpfloat _packedVal ) +{ + uint packedVal = (uint)(_packedVal * 255.5); + lpfloat4 edgesLRTB; + edgesLRTB.x = lpfloat((packedVal >> 6) & 0x03) / 3.0; // there's really no need for mask (as it's an 8 bit input) but I'll leave it in so it doesn't cause any trouble in the future + edgesLRTB.y = lpfloat((packedVal >> 4) & 0x03) / 3.0; + edgesLRTB.z = lpfloat((packedVal >> 2) & 0x03) / 3.0; + edgesLRTB.w = lpfloat((packedVal >> 0) & 0x03) / 3.0; + + return saturate( edgesLRTB ); +} + +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS +typedef lpfloat4 AOTermType; // .xyz is bent normal, .w is visibility term +#else +typedef lpfloat AOTermType; // .x is visibility term +#endif + +void XeGTAO_AddSample( AOTermType ssaoValue, lpfloat edgeValue, inout AOTermType sum, inout lpfloat sumWeight ) +{ + lpfloat weight = edgeValue; + + sum += (weight * ssaoValue); + sumWeight += weight; +} + +void XeGTAO_Output( uint2 pixCoord, RWTexture2D outputTexture, AOTermType outputValue, const uniform bool finalApply ) +{ +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS + lpfloat visibility = outputValue.w * ((finalApply)?((lpfloat)XE_GTAO_OCCLUSION_TERM_SCALE):(1)); + lpfloat3 bentNormal = normalize(outputValue.xyz); + outputTexture[pixCoord.xy] = XeGTAO_EncodeVisibilityBentNormal( visibility, bentNormal ); +#else + outputValue *= (finalApply)?((lpfloat)XE_GTAO_OCCLUSION_TERM_SCALE):(1); + outputTexture[pixCoord.xy] = uint(outputValue * 255.0 + 0.5); +#endif +} + +void XeGTAO_DecodeGatherPartial( const uint4 packedValue, out AOTermType outDecoded[4] ) +{ + for( int i = 0; i < 4; i++ ) +#ifdef XE_GTAO_COMPUTE_BENT_NORMALS + XeGTAO_DecodeVisibilityBentNormal( packedValue[i], outDecoded[i].w, outDecoded[i].xyz ); +#else + outDecoded[i] = lpfloat(packedValue[i]) / lpfloat(255.0); +#endif +} + +void XeGTAO_Denoise( const uint2 pixCoordBase, const GTAOConstants consts, Texture2D sourceAOTerm, Texture2D sourceEdges, SamplerState texSampler, RWTexture2D outputTexture, const uniform bool finalApply ) +{ + const lpfloat blurAmount = (finalApply)?((lpfloat)consts.DenoiseBlurBeta):((lpfloat)consts.DenoiseBlurBeta/(lpfloat)5.0); + const lpfloat diagWeight = 0.85 * 0.5; + + AOTermType aoTerm[2]; // pixel pixCoordBase and pixel pixCoordBase + int2( 1, 0 ) + lpfloat4 edgesC_LRTB[2]; + lpfloat weightTL[2]; + lpfloat weightTR[2]; + lpfloat weightBL[2]; + lpfloat weightBR[2]; + + // gather edge and visibility quads, used later + const float2 gatherCenter = float2( pixCoordBase.x, pixCoordBase.y ) * consts.ViewportPixelSize; + lpfloat4 edgesQ0 = sourceEdges.GatherRed( texSampler, gatherCenter, int2( 0, 0 ) ); + lpfloat4 edgesQ1 = sourceEdges.GatherRed( texSampler, gatherCenter, int2( 2, 0 ) ); + lpfloat4 edgesQ2 = sourceEdges.GatherRed( texSampler, gatherCenter, int2( 1, 2 ) ); + + AOTermType visQ0[4]; XeGTAO_DecodeGatherPartial( sourceAOTerm.GatherRed( texSampler, gatherCenter, int2( 0, 0 ) ), visQ0 ); + AOTermType visQ1[4]; XeGTAO_DecodeGatherPartial( sourceAOTerm.GatherRed( texSampler, gatherCenter, int2( 2, 0 ) ), visQ1 ); + AOTermType visQ2[4]; XeGTAO_DecodeGatherPartial( sourceAOTerm.GatherRed( texSampler, gatherCenter, int2( 0, 2 ) ), visQ2 ); + AOTermType visQ3[4]; XeGTAO_DecodeGatherPartial( sourceAOTerm.GatherRed( texSampler, gatherCenter, int2( 2, 2 ) ), visQ3 ); + + for( int side = 0; side < 2; side++ ) + { + const int2 pixCoord = int2( pixCoordBase.x + side, pixCoordBase.y ); + + lpfloat4 edgesL_LRTB = XeGTAO_UnpackEdges( (side==0)?(edgesQ0.x):(edgesQ0.y) ); + lpfloat4 edgesT_LRTB = XeGTAO_UnpackEdges( (side==0)?(edgesQ0.z):(edgesQ1.w) ); + lpfloat4 edgesR_LRTB = XeGTAO_UnpackEdges( (side==0)?(edgesQ1.x):(edgesQ1.y) ); + lpfloat4 edgesB_LRTB = XeGTAO_UnpackEdges( (side==0)?(edgesQ2.w):(edgesQ2.z) ); + + edgesC_LRTB[side] = XeGTAO_UnpackEdges( (side==0)?(edgesQ0.y):(edgesQ1.x) ); + + // Edges aren't perfectly symmetrical: edge detection algorithm does not guarantee that a left edge on the right pixel will match the right edge on the left pixel (although + // they will match in majority of cases). This line further enforces the symmetricity, creating a slightly sharper blur. Works real nice with TAA. + edgesC_LRTB[side] *= lpfloat4( edgesL_LRTB.y, edgesR_LRTB.x, edgesT_LRTB.w, edgesB_LRTB.z ); + +#if 1 // this allows some small amount of AO leaking from neighbours if there are 3 or 4 edges; this reduces both spatial and temporal aliasing + const lpfloat leak_threshold = 2.5; const lpfloat leak_strength = 0.5; + lpfloat edginess = (saturate(4.0 - leak_threshold - dot( edgesC_LRTB[side], 1.xxxx )) / (4-leak_threshold)) * leak_strength; + edgesC_LRTB[side] = saturate( edgesC_LRTB[side] + edginess ); +#endif + +#ifdef XE_GTAO_SHOW_EDGES + g_outputDbgImage[pixCoord] = 1.0 - lpfloat4( edgesC_LRTB[side].x, edgesC_LRTB[side].y * 0.5 + edgesC_LRTB[side].w * 0.5, edgesC_LRTB[side].z, 1.0 ); + //g_outputDbgImage[pixCoord] = 1 - float4( edgesC_LRTB[side].z, edgesC_LRTB[side].w , 1, 0 ); + //g_outputDbgImage[pixCoord] = edginess.xxxx; +#endif + + // for diagonals; used by first and second pass + weightTL[side] = diagWeight * (edgesC_LRTB[side].x * edgesL_LRTB.z + edgesC_LRTB[side].z * edgesT_LRTB.x); + weightTR[side] = diagWeight * (edgesC_LRTB[side].z * edgesT_LRTB.y + edgesC_LRTB[side].y * edgesR_LRTB.z); + weightBL[side] = diagWeight * (edgesC_LRTB[side].w * edgesB_LRTB.x + edgesC_LRTB[side].x * edgesL_LRTB.w); + weightBR[side] = diagWeight * (edgesC_LRTB[side].y * edgesR_LRTB.w + edgesC_LRTB[side].w * edgesB_LRTB.y); + + // first pass + AOTermType ssaoValue = (side==0)?(visQ0[1]):(visQ1[0]); + AOTermType ssaoValueL = (side==0)?(visQ0[0]):(visQ0[1]); + AOTermType ssaoValueT = (side==0)?(visQ0[2]):(visQ1[3]); + AOTermType ssaoValueR = (side==0)?(visQ1[0]):(visQ1[1]); + AOTermType ssaoValueB = (side==0)?(visQ2[2]):(visQ3[3]); + AOTermType ssaoValueTL = (side==0)?(visQ0[3]):(visQ0[2]); + AOTermType ssaoValueBR = (side==0)?(visQ3[3]):(visQ3[2]); + AOTermType ssaoValueTR = (side==0)?(visQ1[3]):(visQ1[2]); + AOTermType ssaoValueBL = (side==0)?(visQ2[3]):(visQ2[2]); + + lpfloat sumWeight = blurAmount; + AOTermType sum = ssaoValue * sumWeight; + + XeGTAO_AddSample( ssaoValueL, edgesC_LRTB[side].x, sum, sumWeight ); + XeGTAO_AddSample( ssaoValueR, edgesC_LRTB[side].y, sum, sumWeight ); + XeGTAO_AddSample( ssaoValueT, edgesC_LRTB[side].z, sum, sumWeight ); + XeGTAO_AddSample( ssaoValueB, edgesC_LRTB[side].w, sum, sumWeight ); + + XeGTAO_AddSample( ssaoValueTL, weightTL[side], sum, sumWeight ); + XeGTAO_AddSample( ssaoValueTR, weightTR[side], sum, sumWeight ); + XeGTAO_AddSample( ssaoValueBL, weightBL[side], sum, sumWeight ); + XeGTAO_AddSample( ssaoValueBR, weightBR[side], sum, sumWeight ); + + aoTerm[side] = sum / sumWeight; + + XeGTAO_Output( pixCoord, outputTexture, aoTerm[side], finalApply ); + +#ifdef XE_GTAO_SHOW_BENT_NORMALS + if( finalApply ) + { + g_outputDbgImage[pixCoord] = float4( DisplayNormalSRGB( aoTerm[side].xyz /** aoTerm[side].www*/ ), 1 ); + } +#endif + + } +} + + +// Generic viewspace normal generate pass +float3 XeGTAO_ComputeViewspaceNormal( const uint2 pixCoord, const GTAOConstants consts, Texture2D sourceNDCDepth, SamplerState depthSampler ) +{ + float2 normalizedScreenPos = (pixCoord + 0.5.xx) * consts.ViewportPixelSize; + + float4 valuesUL = sourceNDCDepth.GatherRed( depthSampler, float2( pixCoord * consts.ViewportPixelSize ) ); + float4 valuesBR = sourceNDCDepth.GatherRed( depthSampler, float2( pixCoord * consts.ViewportPixelSize ), int2( 1, 1 ) ); + + // viewspace Z at the center + float viewspaceZ = XeGTAO_ScreenSpaceToViewSpaceDepth( valuesUL.y, consts ); //sourceViewspaceDepth.SampleLevel( depthSampler, normalizedScreenPos, 0 ).x; + + // viewspace Zs left top right bottom + const float pixLZ = XeGTAO_ScreenSpaceToViewSpaceDepth( valuesUL.x, consts ); + const float pixTZ = XeGTAO_ScreenSpaceToViewSpaceDepth( valuesUL.z, consts ); + const float pixRZ = XeGTAO_ScreenSpaceToViewSpaceDepth( valuesBR.z, consts ); + const float pixBZ = XeGTAO_ScreenSpaceToViewSpaceDepth( valuesBR.x, consts ); + + lpfloat4 edgesLRTB = XeGTAO_CalculateEdges( (lpfloat)viewspaceZ, (lpfloat)pixLZ, (lpfloat)pixRZ, (lpfloat)pixTZ, (lpfloat)pixBZ ); + + float3 CENTER = XeGTAO_ComputeViewspacePosition( normalizedScreenPos, viewspaceZ, consts ); + float3 LEFT = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2(-1, 0) * consts.ViewportPixelSize, pixLZ, consts ); + float3 RIGHT = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2( 1, 0) * consts.ViewportPixelSize, pixRZ, consts ); + float3 TOP = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2( 0, -1) * consts.ViewportPixelSize, pixTZ, consts ); + float3 BOTTOM = XeGTAO_ComputeViewspacePosition( normalizedScreenPos + float2( 0, 1) * consts.ViewportPixelSize, pixBZ, consts ); + return XeGTAO_CalculateNormal( edgesLRTB, CENTER, LEFT, RIGHT, TOP, BOTTOM ); +} \ No newline at end of file diff --git a/manul/shaders/XeGTAO/vaShaderCore.h b/manul/shaders/XeGTAO/vaShaderCore.h new file mode 100644 index 00000000..e9083f10 --- /dev/null +++ b/manul/shaders/XeGTAO/vaShaderCore.h @@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2021, Intel Corporation +// +// SPDX-License-Identifier: MIT +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Author(s): Filip Strugar (filip.strugar@intel.com) +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef VA_SHADER_CORE_H +#define VA_SHADER_CORE_H + +#ifndef VA_COMPILED_AS_SHADER_CODE + +namespace Vanilla +{ +#define VA_SATURATE vaMath::Saturate +#define VA_MIN vaComponentMin +#define VA_MAX vaComponentMax +#define VA_LENGTH vaLength +#define VA_INLINE inline +#define VA_REFERENCE & +#define VA_CONST const + +} + +#else + +#define VA_SATURATE saturate +#define VA_MIN min +#define VA_MAX max +#define VA_LENGTH length +#define VA_INLINE +#define VA_REFERENCE +#define VA_CONST + +#endif + +#ifndef VA_COMPILED_AS_SHADER_CODE + +#include "Core/vaCoreIncludes.h" + +#else + +// Vanilla-specific; this include gets intercepted and macros are provided through it to allow for some macro +// shenanigans that don't work through the normal macro string pairs (like #include macros!). +#include "MagicMacrosMagicFile.h" + +// Vanilla defaults to column-major matrices in shaders because that is the DXC default with no arguments, and it +// seems to be more common* in general. (*AFAIK) +// This is in contrast to the C++ side, which is row-major, so the ordering of matrix operations in shaders needs +// to be inverted, which is fine, for ex., "projectedPos = mul( g_globals.ViewProj, worldspacePos )" +// One nice side-effect is that it's easy to drop the 4th column for 4x3 matrix (on c++ side), which becomes 3x4 +// (on the shader side), which is useful for reducing memory traffic for affine transforms. +// See https://github.com/microsoft/DirectXShaderCompiler/blob/master/docs/SPIR-V.rst#appendix-a-matrix-representation +// and http://www.mindcontrol.org/~hplus/graphics/matrix-layout.html for more detail. + +#define vaMatrix4x4 column_major float4x4 +#define vaMatrix4x3 column_major float3x4 +#define vaMatrix3x3 column_major float3x3 +#define vaVector4 float4 +#define vaVector3 float3 +#define vaVector2 float2 +#define vaVector2i int2 +#define vaVector2ui uint2 +#define vaVector4i int4 +#define vaVector4ui uint4 + +#define CONCATENATE_HELPER(a, b) a##b +#define CONCATENATE(a, b) CONCATENATE_HELPER(a, b) + +#define B_CONCATENATER(x) CONCATENATE(b,x) +#define S_CONCATENATER(x) CONCATENATE(s,x) +#define T_CONCATENATER(x) CONCATENATE(t,x) +#define U_CONCATENATER(x) CONCATENATE(u,x) + +#define ShaderMin( x, y ) min( x, y ) + +#endif + +#endif // VA_SHADER_CORE_H \ No newline at end of file diff --git a/manul/shaders/manul/aerial_perspective.hlsli b/manul/shaders/manul/aerial_perspective.hlsli new file mode 100644 index 00000000..deb590db --- /dev/null +++ b/manul/shaders/manul/aerial_perspective.hlsli @@ -0,0 +1,6 @@ +#ifndef AERIAL_PERSPECTIVE_HLSLI +#define AERIAL_PERSPECTIVE_HLSLI + +SamplerState g_AerialPerspectiveLutSampler : register(s13); + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/alpha_mask.hlsli b/manul/shaders/manul/alpha_mask.hlsli new file mode 100644 index 00000000..f23bc898 --- /dev/null +++ b/manul/shaders/manul/alpha_mask.hlsli @@ -0,0 +1,8 @@ +#ifndef ALPHA_MASK_HLSLI +#define ALPHA_MASK_HLSLI + +void AlphaMask(in float alpha) { + if(g_DrawConstants.m_AlphaThreshold >= 0. ? (alpha < g_DrawConstants.m_AlphaThreshold) : (alpha >= -g_DrawConstants.m_AlphaThreshold)) discard; +} + +#endif diff --git a/manul/shaders/manul/amd/ffx_a.h b/manul/shaders/manul/amd/ffx_a.h new file mode 100644 index 00000000..b92546eb --- /dev/null +++ b/manul/shaders/manul/amd/ffx_a.h @@ -0,0 +1,1907 @@ +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20190530 +// +//============================================================================================================================== +// LICENSE +// ======= +// Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved. +// Copyright (c) <2014> +// ------- +// 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. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +// - Add subgroup ops. +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20190531 - Fixed changed to llabs() because long is int on Windows. +// 20190530 - Updated for new CPU/GPU portability. +// 20190528 - Fix AU1_AH2_x() on HLSL (had incorrectly swapped x and y), fixed asuint() cases. +// 20190527 - Added min3/max3 for low precision for HLSL. +// 20190526 - Updated with half approximations, added ARsq*(), and ASat*() for CPU. +// 20190519 - Added more approximations. +// 20190514 - Added long conversions. +// 20190513 - Added the real BFI moved the other one to ABfiM(). +// 20190507 - Added extra remap useful for 2D reductions. +// 20190507 - Started adding wave ops, add parabolic sin/cos. +// 20190505 - Added ASigned*() and friends, setup more auto-typecast, GLSL extensions, etc. +// 20190504 - Added min3/max3 for 32-bit integers. +// 20190503 - Added type reinterpretation for half. +// 20190416 - Added min3/max3 for half. +// 20190405 - Misc bug fixing. +// 20190404 - Cleaned up color conversion code. Switched "splat" to shorter naming "type_". Misc bug fixing. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +// Requires standard C types: stdint.h +// Requires a collection of standard math intrinsics. +// - Requires VS2013 when not using GCC to get exp2() and log2(). +// - https://blogs.msdn.microsoft.com/vcblog/2013/07/19/c99-library-support-in-visual-studio-2013/ +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a minimum subset of functionality compared to the GPU parts. +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Hackary to get CPU and GPU to share all setup code, without duplicate code paths. +// Unfortunately this is the level of "ugly" that is required since the languages are very different. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_labs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(llabs(ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + // TODO: Fixme to more portable extension!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + AF1 AWaveAdd(AF1 v){return subgroupAdd(v);} + AF2 AWaveAdd(AF2 v){return subgroupAdd(v);} + AF3 AWaveAdd(AF3 v){return subgroupAdd(v);} + AF4 AWaveAdd(AF4 v){return subgroupAdd(v);} + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + // TODO: These are broken!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + #define AW1_AH1(x) AW1(asuint(AF1(x))) + #define AW2_AH2(x) AW2(asuint(AF2(x))) + #define AW3_AH3(x) AW3(asuint(AF3(x))) + #define AW4_AH4(x) AW4(asuint(AF4(x))) +//------------------------------------------------------------------------------------------------------------------------------ + // TODO: These are broken!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + #define AH1_AW1(x) AH1(asfloat(AU1(x))) + #define AH2_AW2(x) AH2(asfloat(AU2(x))) + #define AH3_AW3(x) AH3(asfloat(AU3(x))) + #define AH4_AW4(x) AH4(asfloat(AU4(x))) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFN_F AF1_AU1(0x7f800000u) + #define A_INFP_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//============================================================================================================================== + #ifdef A_HALF + #define A_INFN_H AH1_AW1(0x7c00u) + #define A_INFP_H AH1_AW1(0xfc00u) +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// IDEAS +// ===== +// - Polaris hardware has 16-bit support, but non-double rate. +// Could be possible still get part double rate for some of this logic, +// by clearing out the lower half's sign when necessary and using 32-bit ops... +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Verify packed math ABS is correctly doing an AND. +//============================================================================================================================== + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to -1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + AF1 ATo709F1(AF1 c){return max(min(c*AF1_(4.5),AF1_(0.018)),AF1_(1.099)*pow(c,AF1_(0.45))-AF1_(0.099));} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,rcpX);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){return max(min(c*AF1_(12.92),AF1_(0.0031308)),AF1_(1.055)*pow(c,AF1_(0.41666))-AF1_(0.055));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} +//============================================================================================================================== + AF1 AFrom709F1(AF1 c){return max(min(c*AF1_(1.0/4.5),AF1_(0.081)), + pow((c+AF1_(0.099))*(AF1_(1.0)/(AF1_(1.099))),AF1_(1.0/0.45)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromSrgbF1(AF1 c){return max(min(c*AF1_(1.0/12.92),AF1_(0.04045)), + pow((c+AF1_(0.055))*(AF1_(1.0)/AF1_(1.055)),AF1_(2.4)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} +//============================================================================================================================== + #ifdef A_HALF + AH2 ATo709H2(AH2 c){return max(min(c*AH2_(4.5),AH2_(0.018)),AH2_(1.099)*pow(c,AH2_(0.45))-AH2_(0.099));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 AToSrgbH2(AH2 c){return max(min(c*AH2_(12.92),AH2_(0.0031308)),AH2_(1.055)*pow(c,AH2_(0.41666))-AH2_(0.055));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 AToTwoH2(AH2 c){return sqrt(c);} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH2 AFrom709H2(AH2 c){return max(min(c*AH2_(1.0/4.5),AH2_(0.081)), + pow((c+AH2_(0.099))*(AH2_(1.0)/(AH2_(1.099))),AH2_(1.0/0.45)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 AFromSrgbH2(AH2 c){return max(min(c*AH2_(1.0/12.92),AH2_(0.04045)), + pow((c+AH2_(0.055))*(AH2_(1.0)/AH2_(1.055)),AH2_(2.4)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 AFromTwoH2(AH2 c){return c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/128 +// 1024 : 8 +// 2047 : a little less than 16 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) min(a,b) + #define AMaxF1(a,b) min(a,b) + #define AMaxL1(a,b) min(a,b) + #define AMaxU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif diff --git a/manul/shaders/manul/amd/ffx_spd.h b/manul/shaders/manul/amd/ffx_spd.h new file mode 100644 index 00000000..e0b58b87 --- /dev/null +++ b/manul/shaders/manul/amd/ffx_spd.h @@ -0,0 +1,1297 @@ +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// [FFX SPD] Single Pass Downsampler 2.0 +// +//============================================================================================================================== +// LICENSE +// ======= +// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved. +// ------- +// 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. +// +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGELIST v2.0 +// =============== +// - Added support for cube and array textures. SpdDownsample and SpdDownsampleH shader functions now take index of texture slice +// as an additional parameter. For regular texture use 0. +// - Added support for updating only sub-rectangle of the texture. Additional, optional parameter workGroupOffset added to shader +// functions SpdDownsample and SpdDownsampleH. +// - Added C function SpdSetup that helps to setup constants to be passed as a constant buffer. +// - The global atomic counter is automatically reset to 0 by the shader at the end, so you do not need to clear it before every +// use, just once after creation +// +//------------------------------------------------------------------------------------------------------------------------------ +// INTEGRATION SUMMARY FOR CPU +// =========================== +// // you need to provide as constants: +// // number of mip levels to be computed (maximum is 12) +// // number of total thread groups: ((widthInPixels+63)>>6) * ((heightInPixels+63)>>6) +// // workGroupOffset -> by default 0, if you only downsample a rectancle within the source texture use SpdSetup function to calculate correct offset +// ... +// // Dispatch the shader such that each thread group works on a 64x64 sub-tile of the source image +// // for Cube Textures or Texture2DArray, use the z dimension +// vkCmdDispatch(cmdBuf,(widthInPixels+63)>>6,(heightInPixels+63)>>6, slices); + +// // you can also use the SpdSetup function: +// //on top of your cpp file: +// #define A_CPU +// #include "ffx_a.h" +// #include "ffx_spd.h" +// // before your dispatch call, use SpdSetup function to get your constants +// varAU2(dispatchThreadGroupCountXY); // output variable +// varAU2(workGroupOffset); // output variable, this constants are required if Left and Top are not 0,0 +// varAU2(numWorkGroupsAndMips); // output variable +// // input information about your source texture: +// // left and top of the rectancle within your texture you want to downsample +// // width and height of the rectancle you want to downsample +// // if complete source texture should get downsampled: left = 0, top = 0, width = sourceTexture.width, height = sourceTexture.height +// varAU4(rectInfo) = initAU4(0, 0, m_Texture.GetWidth(), m_Texture.GetHeight()); // left, top, width, height +// SpdSetup(dispatchThreadGroupCountXY, workGroupOffset, numWorkGroupsAndMips, rectInfo); +// ... +// // constants: +// data.numWorkGroupsPerSlice = numWorkGroupsAndMips[0]; +// data.mips = numWorkGroupsAndMips[1]; +// data.workGroupOffset[0] = workGroupOffset[0]; +// data.workGroupOffset[1] = workGroupOffset[1]; +// ... +// uint32_t dispatchX = dispatchThreadGroupCountXY[0]; +// uint32_t dispatchY = dispatchThreadGroupCountXY[1]; +// uint32_t dispatchZ = m_CubeTexture.GetArraySize(); // slices - for 2D Texture this is 1, for cube texture 6 +// vkCmdDispatch(cmd_buf, dispatchX, dispatchY, dispatchZ); + +//------------------------------------------------------------------------------------------------------------------------------ +// INTEGRATION SUMMARY FOR GPU +// =========================== + +// [SAMPLER] - if you want to use a sampler with linear filtering for loading the source image +// follow additionally the instructions marked with [SAMPLER] +// add following define: +// #define SPD_LINEAR_SAMPLER +// this is recommended, as using one sample() with linear filter to reduce 2x2 is faster +// than 4x load() plus manual averaging + +// // Setup layout. Example below for VK_FORMAT_R16G16B16A16_SFLOAT. +// // Note: If you use SRGB format for UAV load() and store() (if it's supported), you need to convert to and from linear space +// // when using UAV load() and store() +// // approximate conversion to linear (load function): x*x +// // approximate conversion from linear (store function): sqrt() +// // or use more accurate functions from ffx_a.h: AFromSrgbF1(value) and AToSrgbF1(value) +// // Recommendation: use UNORM format instead of SRGB for UAV access, and SRGB for SRV access +// // look in the sample app to see how it's done + +// // source image +// // if cube texture use image2DArray / Texture2DArray and adapt your load/store/sample calls +// GLSL: layout(set=0,binding=0,rgba16f)uniform image2D imgSrc; +// [SAMPLER]: layout(set=0,binding=0)uniform texture2D imgSrc; +// HLSL: [[vk::binding(0)]] Texture2D imgSrc :register(u0); + +// // destination -> 12 is the maximum number of mips supported by SPD +// GLSL: layout(set=0,binding=1,rgba16f) uniform coherent image2D imgDst[12]; +// HLSL: [[vk::binding(1)]] globallycoherent RWTexture2D imgDst[12] :register(u1); + +// // global atomic counter - MUST be initialized to 0 +// // SPD resets the counter back after each run by calling SpdResetAtomicCounter(slice) +// // if you have more than 1 slice (== if you downsample a cube texture or a texture2Darray) +// // you have an array of counters: counter[6] -> if you have 6 slices for example +// // GLSL: +// layout(std430, set=0, binding=2) coherent buffer SpdGlobalAtomicBuffer +// { +// uint counter; +// } spdGlobalAtomic; +// // HLSL: +// struct SpdGlobalAtomicBuffer +// { +// uint counter; +// }; +// [[vk::binding(2)]] globallycoherent RWStructuredBuffer spdGlobalAtomic; + +// // [SAMPLER] add sampler +// GLSL: layout(set=0, binding=3) uniform sampler srcSampler; +// HLSL: [[vk::binding(3)]] SamplerState srcSampler :register(s0); + +// // constants - either push constant or constant buffer +// // or calculate within shader +// // [SAMPLER] when using sampler add inverse source image size +// // GLSL: +// layout(push_constant) uniform SpdConstants { +// uint mips; // needed to opt out earlier if mips are < 12 +// uint numWorkGroups; // number of total thread groups, so numWorkGroupsX * numWorkGroupsY * 1 +// // it is important to NOT take the number of slices (z dimension) into account here +// // as each slice has its own counter! +// vec2 workGroupOffset; // optional - use SpdSetup() function to calculate correct workgroup offset +// } spdConstants; +// // HLSL: +// [[vk::push_constant]] +// cbuffer spdConstants { +// uint mips; +// uint numWorkGroups; +// float2 workGroupOffset; // optional +// }; + +// ... +// // Setup pre-portability-header defines (sets up GLSL/HLSL path, etc) +// #define A_GPU 1 +// #define A_GLSL 1 // or // #define A_HLSL 1 + +// // if you want to use PACKED version +// // recommended if bpc <= 16bit +// #define A_HALF + +// ... +// // Include the portability header (or copy it in without an include). +// #include "ffx_a.h" +// ... + +// // Define LDS variables +// shared AF4 spdIntermediate[16][16]; // HLSL: groupshared +// shared AU1 spdCounter; // HLSL: groupshared +// // PACKED version +// shared AH4 spdIntermediate[16][16]; // HLSL: groupshared +// // Note: You can also use +// shared AF1 spdIntermediateR[16][16]; +// shared AF1 spdIntermediateG[16][16]; +// shared AF1 spdIntermediateB[16][16]; +// shared AF1 spdIntermediateA[16][16]; +// // or for Packed version: +// shared AH2 spdIntermediateRG[16][16]; +// shared AH2 spdIntermediateBA[16][16]; +// // This is potentially faster +// // Adapt your load and store functions accordingly + +// // if subgroup operations are not supported / can't use SM6.0 +// #define SPD_NO_WAVE_OPERATIONS + +// // Define the fetch function(s) and the reduction function +// // if non-power-of-2 textures, add border controls to the load and store functions +// // to make sure the borders of the mip level look as you want it +// // if you don't add border controls you'll read zeros past the border +// // if you load with a sampler, this is obv. handled by your sampler :) +// // this is also the place where you need to do color space transformation if needed +// // E.g. if your texture format is SRGB/UNORM and you use the UAV load and store functions +// // no automatic to/from linear conversions are happening +// // there is to/from linear conversions when using a sampler and render target approach +// // conversion to linear (load function): x*x +// // conversion from linear (store function): sqrt() + +// AU1 slice parameter is for Cube textures and texture2DArray +// if downsampling Texture2D you can ignore this parameter, otherwise use it to access correct slice +// // Load from source image +// GLSL: AF4 SpdLoadSourceImage(ASU2 p, AU1 slice){return imageLoad(imgSrc, p);} +// HLSL: AF4 SpdLoadSourceImage(ASU2 tex, AU1 slice){return imgSrc[tex];} +// [SAMPLER] don't forget to add the define #SPD_LINEAR_SAMPLER :) +// GLSL: +// AF4 SpdLoadSourceImage(ASU2 p, AU1 slice){ +// AF2 textureCoord = p * invInputSize + invInputSize; +// return texture(sampler2D(imgSrc, srcSampler), textureCoord); +// } +// HLSL: +// AF4 SpdLoadSourceImage(ASU2 p, AU1 slice){ +// AF2 textureCoord = p * invInputSize + invInputSize; +// return imgSrc.SampleLevel(srcSampler, textureCoord, 0); +// } + +// // SpdLoad() takes a 32-bit signed integer 2D coordinate and loads color. +// // Loads the 5th mip level, each value is computed by a different thread group +// // last thread group will access all its elements and compute the subsequent mips +// // reminder: if non-power-of-2 textures, add border controls if you do not want to read zeros past the border +// GLSL: AF4 SpdLoad(ASU2 p, AU1 slice){return imageLoad(imgDst[5],p);} +// HLSL: AF4 SpdLoad(ASU2 tex, AU1 slice){return imgDst[5][tex];} + +// Define the store function +// GLSL: void SpdStore(ASU2 p, AF4 value, AU1 mip, AU1 slice){imageStore(imgDst[mip], p, value);} +// HLSL: void SpdStore(ASU2 pix, AF4 value, AU1 mip, AU1 slice){imgDst[mip][pix] = value;} + +// // Define the atomic counter increase function +// // each slice only reads and stores to its specific slice counter +// // so, if you have several slices it's +// // InterlockedAdd(spdGlobalAtomic[0].counter[slice], 1, spdCounter); +// // GLSL: +// void SpdIncreaseAtomicCounter(AU1 slice){spdCounter = atomicAdd(spdGlobalAtomic.counter, 1);} +// AU1 SpdGetAtomicCounter() {return spdCounter;} +// void SpdResetAtomicCounter(AU1 slice){spdGlobalAtomic.counter[slice] = 0;} +// // HLSL: +// void SpdIncreaseAtomicCounter(AU1 slice){InterlockedAdd(spdGlobalAtomic[0].counter, 1, spdCounter);} +// AU1 SpdGetAtomicCounter(){return spdCounter;} +// void SpdResetAtomicCounter(AU1 slice){spdGlobalAtomic[0].counter[slice] = 0;} + +// // Define the LDS load and store functions +// // GLSL: +// AF4 SpdLoadIntermediate(AU1 x, AU1 y){return spdIntermediate[x][y];} +// void SpdStoreIntermediate(AU1 x, AU1 y, AF4 value){spdIntermediate[x][y] = value;} +// // HLSL: +// AF4 SpdLoadIntermediate(AU1 x, AU1 y){return spdIntermediate[x][y];} +// void SpdStoreIntermediate(AU1 x, AU1 y, AF4 value){spdIntermediate[x][y] = value;} + +// // Define your reduction function: takes as input the four 2x2 values and returns 1 output value +// Example below: computes the average value +// AF4 SpdReduce4(AF4 v0, AF4 v1, AF4 v2, AF4 v3){return (v0+v1+v2+v3)*0.25;} + +// // PACKED VERSION +// Load from source image +// GLSL: AH4 SpdLoadSourceImageH(ASU2 p, AU1 slice){return AH4(imageLoad(imgSrc, p));} +// HLSL: AH4 SpdLoadSourceImageH(ASU2 tex, AU1 slice){return AH4(imgSrc[tex]);} +// [SAMPLER] +// GLSL: +// AH4 SpdLoadSourceImageH(ASU2 p, AU1 slice){ +// AF2 textureCoord = p * invInputSize + invInputSize; +// return AH4(texture(sampler2D(imgSrc, srcSampler), textureCoord)); +// } +// HLSL: +// AH4 SpdLoadSourceImageH(ASU2 p, AU1 slice){ +// AF2 textureCoord = p * invInputSize + invInputSize; +// return AH4(imgSrc.SampleLevel(srcSampler, textureCoord, 0)); +// } + +// // SpdLoadH() takes a 32-bit signed integer 2D coordinate and loads color. +// // Loads the 5th mip level, each value is computed by a different thread group +// // last thread group will access all its elements and compute the subsequent mips +// GLSL: AH4 SpdLoadH(ASU2 p, AU1 slice){return AH4(imageLoad(imgDst[5],p));} +// HLSL: AH4 SpdLoadH(ASU2 tex, AU1 slice){return AH4(imgDst[5][tex]);} + +// Define the store function +// GLSL: void SpdStoreH(ASU2 p, AH4 value, AU1 mip, AU1 slice){imageStore(imgDst[mip], p, AF4(value));} +// HLSL: void SpdStoreH(ASU2 pix, AH4 value, AU1 index, AU1 slice){imgDst[index][pix] = AF4(value);} + +// // Define the atomic counter increase function +// // GLSL: +// void SpdIncreaseAtomicCounter(AU1 slice){spd_counter = atomicAdd(spdGlobalAtomic.counter, 1);} +// AU1 SpdGetAtomicCounter() {return spdCounter;} +// // HLSL: +// void SpdIncreaseAtomicCounter(AU1 slice){InterlockedAdd(spdGlobalAtomic[0].counter, 1, spdCounter);} +// AU1 SpdGetAtomicCounter(){return spdCounter;} + +// // Define the LDS load and store functions +// // GLSL: +// AH4 SpdLoadIntermediateH(AU1 x, AU1 y){return spdIntermediate[x][y];} +// void SpdStoreIntermediateH(AU1 x, AU1 y, AH4 value){spdIntermediate[x][y] = value;} +// // HLSL: +// AH4 SpdLoadIntermediate(AU1 x, AU1 y){return spdIntermediate[x][y];} +// void SpdStoreIntermediate(AU1 x, AU1 y, AH4 value){spdIntermediate[x][y] = value;} + +// // Define your reduction function: takes as input the four 2x2 values and returns 1 output value +// Example below: computes the average value +// AH4 SpdReduce4H(AH4 v0, AH4 v1, AH4 v2, AH4 v3){return (v0+v1+v2+v3)*AH1(0.25);} + +// // + +// // If you only use PACKED version +// #define SPD_PACKED_ONLY + +// // Include this SPD (single pass downsampler) header file (or copy it in without an include). +// #include "ffx_spd.h" +// ... + +// // Example in shader integration +// // GLSL: +// layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; +// void main(){ +// // Call the downsampling function +// // WorkGroupId.z should be 0 if you only downsample a Texture2D! +// SpdDownsample(AU2(gl_WorkGroupID.xy), AU1(gl_LocalInvocationIndex), +// AU1(spdConstants.mips), AU1(spdConstants.numWorkGroups), AU1(WorkGroupId.z)); +// +// // PACKED: +// SpdDownsampleH(AU2(gl_WorkGroupID.xy), AU1(gl_LocalInvocationIndex), +// AU1(spdConstants.mips), AU1(spdConstants.numWorkGroups), AU1(WorkGroupId.z)); +// ... +// // HLSL: +// [numthreads(256,1,1)] +// void main(uint3 WorkGroupId : SV_GroupID, uint LocalThreadIndex : SV_GroupIndex) { +// SpdDownsample(AU2(WorkGroupId.xy), AU1(LocalThreadIndex), +// AU1(mips), AU1(numWorkGroups), AU1(WorkGroupId.z)); +// +// // PACKED: +// SpdDownsampleH(AU2(WorkGroupId.xy), AU1(LocalThreadIndex), +// AU1(mips), AU1(numWorkGroups), AU1(WorkGroupId.z)); +// ... + +// +//------------------------------------------------------------------------------------------------------------------------------ + +//============================================================================================================================== +// SPD Setup +//============================================================================================================================== +#ifdef A_CPU +A_STATIC void SpdSetup( +outAU2 dispatchThreadGroupCountXY, // CPU side: dispatch thread group count xy +outAU2 workGroupOffset, // GPU side: pass in as constant +outAU2 numWorkGroupsAndMips, // GPU side: pass in as constant +inAU4 rectInfo, // left, top, width, height +ASU1 mips // optional: if -1, calculate based on rect width and height +){ + workGroupOffset[0] = rectInfo[0] / 64; // rectInfo[0] = left + workGroupOffset[1] = rectInfo[1] / 64; // rectInfo[1] = top + + AU1 endIndexX = (rectInfo[0] + rectInfo[2] - 1) / 64; // rectInfo[0] = left, rectInfo[2] = width + AU1 endIndexY = (rectInfo[1] + rectInfo[3] - 1) / 64; // rectInfo[1] = top, rectInfo[3] = height + + dispatchThreadGroupCountXY[0] = endIndexX + 1 - workGroupOffset[0]; + dispatchThreadGroupCountXY[1] = endIndexY + 1 - workGroupOffset[1]; + + numWorkGroupsAndMips[0] = (dispatchThreadGroupCountXY[0]) * (dispatchThreadGroupCountXY[1]); + + if (mips >= 0) { + numWorkGroupsAndMips[1] = AU1(mips); + } else { // calculate based on rect width and height + AU1 resolution = AMaxU1(rectInfo[2], rectInfo[3]); + numWorkGroupsAndMips[1] = AU1((AMinF1(AFloorF1(ALog2F1(AF1(resolution))), AF1(12)))); + } +} + +A_STATIC void SpdSetup( + outAU2 dispatchThreadGroupCountXY, // CPU side: dispatch thread group count xy + outAU2 workGroupOffset, // GPU side: pass in as constant + outAU2 numWorkGroupsAndMips, // GPU side: pass in as constant + inAU4 rectInfo // left, top, width, height +) { + SpdSetup(dispatchThreadGroupCountXY, workGroupOffset, numWorkGroupsAndMips, rectInfo, -1); +} +#endif // #ifdef A_CPU +//============================================================================================================================== +// NON-PACKED VERSION +//============================================================================================================================== +#ifdef A_GPU +#ifdef SPD_PACKED_ONLY + // Avoid compiler error + AF4 SpdLoadSourceImage(ASU2 p, AU1 slice){return AF4(0.0,0.0,0.0,0.0);} + AF4 SpdLoad(ASU2 p, AU1 slice){return AF4(0.0,0.0,0.0,0.0);} + void SpdStore(ASU2 p, AF4 value, AU1 mip, AU1 slice){} + AF4 SpdLoadIntermediate(AU1 x, AU1 y){return AF4(0.0,0.0,0.0,0.0);} + void SpdStoreIntermediate(AU1 x, AU1 y, AF4 value){} + AF4 SpdReduce4(AF4 v0, AF4 v1, AF4 v2, AF4 v3){return AF4(0.0,0.0,0.0,0.0);} +#endif // #ifdef SPD_PACKED_ONLY + +//_____________________________________________________________/\_______________________________________________________________ +#if defined(A_GLSL) && !defined(SPD_NO_WAVE_OPERATIONS) +#extension GL_KHR_shader_subgroup_quad:require +#endif + +void SpdWorkgroupShuffleBarrier() { +#ifdef A_GLSL + barrier(); +#endif +#ifdef A_HLSL + GroupMemoryBarrierWithGroupSync(); +#endif +} + +// Only last active workgroup should proceed +bool SpdExitWorkgroup(AU1 numWorkGroups, AU1 localInvocationIndex, AU1 slice) +{ + // global atomic counter + if (localInvocationIndex == 0) + { + SpdIncreaseAtomicCounter(slice); + } + SpdWorkgroupShuffleBarrier(); + return (SpdGetAtomicCounter() != (numWorkGroups - 1)); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// User defined: AF4 SpdReduce4(AF4 v0, AF4 v1, AF4 v2, AF4 v3); + +AF4 SpdReduceQuad(AF4 v) +{ + #if defined(A_GLSL) && !defined(SPD_NO_WAVE_OPERATIONS) + AF4 v0 = v; + AF4 v1 = subgroupQuadSwapHorizontal(v); + AF4 v2 = subgroupQuadSwapVertical(v); + AF4 v3 = subgroupQuadSwapDiagonal(v); + return SpdReduce4(v0, v1, v2, v3); + #elif defined(A_HLSL) && !defined(SPD_NO_WAVE_OPERATIONS) + // requires SM6.0 + AU1 quad = WaveGetLaneIndex() & (~0x3); + AF4 v0 = v; + AF4 v1 = WaveReadLaneAt(v, quad | 1); + AF4 v2 = WaveReadLaneAt(v, quad | 2); + AF4 v3 = WaveReadLaneAt(v, quad | 3); + return SpdReduce4(v0, v1, v2, v3); + /* + // if SM6.0 is not available, you can use the AMD shader intrinsics + // the AMD shader intrinsics are available in AMD GPU Services (AGS) library: + // https://gpuopen.com/amd-gpu-services-ags-library/ + // works for DX11 + AF4 v0 = v; + AF4 v1; + v1.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + v1.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + v1.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + v1.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + AF4 v2; + v2.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + v2.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + v2.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + v2.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + AF4 v3; + v3.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + v3.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + v3.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + v3.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + return SpdReduce4(v0, v1, v2, v3); + */ + #endif + return v; +} + +AF4 SpdReduceIntermediate(AU2 i0, AU2 i1, AU2 i2, AU2 i3) +{ + AF4 v0 = SpdLoadIntermediate(i0.x, i0.y); + AF4 v1 = SpdLoadIntermediate(i1.x, i1.y); + AF4 v2 = SpdLoadIntermediate(i2.x, i2.y); + AF4 v3 = SpdLoadIntermediate(i3.x, i3.y); + return SpdReduce4(v0, v1, v2, v3); +} + +AF4 SpdReduceLoad4(AU2 i0, AU2 i1, AU2 i2, AU2 i3, AU1 slice) +{ + AF4 v0 = SpdLoad(ASU2(i0), slice); + AF4 v1 = SpdLoad(ASU2(i1), slice); + AF4 v2 = SpdLoad(ASU2(i2), slice); + AF4 v3 = SpdLoad(ASU2(i3), slice); + return SpdReduce4(v0, v1, v2, v3); +} + +AF4 SpdReduceLoad4(AU2 base, AU1 slice) +{ + return SpdReduceLoad4( + AU2(base + AU2(0, 0)), + AU2(base + AU2(0, 1)), + AU2(base + AU2(1, 0)), + AU2(base + AU2(1, 1)), + slice); +} + +AF4 SpdReduceLoadSourceImage4(AU2 i0, AU2 i1, AU2 i2, AU2 i3, AU1 slice) +{ + AF4 v0 = SpdLoadSourceImage(ASU2(i0), slice); + AF4 v1 = SpdLoadSourceImage(ASU2(i1), slice); + AF4 v2 = SpdLoadSourceImage(ASU2(i2), slice); + AF4 v3 = SpdLoadSourceImage(ASU2(i3), slice); + return SpdReduce4(v0, v1, v2, v3); +} + +AF4 SpdReduceLoadSourceImage(AU2 base, AU1 slice) +{ +#ifdef SPD_LINEAR_SAMPLER + return SpdLoadSourceImage(ASU2(base), slice); +#else + return SpdReduceLoadSourceImage4( + AU2(base + AU2(0, 0)), + AU2(base + AU2(0, 1)), + AU2(base + AU2(1, 0)), + AU2(base + AU2(1, 1)), + slice); +#endif +} + +void SpdDownsampleMips_0_1_Intrinsics(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ + AF4 v[4]; + + ASU2 tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2); + ASU2 pix = ASU2(workGroupID.xy * 32) + ASU2(x, y); + v[0] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[0], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y); + v[1] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[1], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x, y + 16); + v[2] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[2], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y + 16); + v[3] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[3], 0, slice); + + if (mip <= 1) + return; + + v[0] = SpdReduceQuad(v[0]); + v[1] = SpdReduceQuad(v[1]); + v[2] = SpdReduceQuad(v[2]); + v[3] = SpdReduceQuad(v[3]); + + if ((localInvocationIndex % 4) == 0) + { + SpdStore(ASU2(workGroupID.xy * 16) + + ASU2(x/2, y/2), v[0], 1, slice); + SpdStoreIntermediate( + x/2, y/2, v[0]); + + SpdStore(ASU2(workGroupID.xy * 16) + + ASU2(x/2 + 8, y/2), v[1], 1, slice); + SpdStoreIntermediate( + x/2 + 8, y/2, v[1]); + + SpdStore(ASU2(workGroupID.xy * 16) + + ASU2(x/2, y/2 + 8), v[2], 1, slice); + SpdStoreIntermediate( + x/2, y/2 + 8, v[2]); + + SpdStore(ASU2(workGroupID.xy * 16) + + ASU2(x/2 + 8, y/2 + 8), v[3], 1, slice); + SpdStoreIntermediate( + x/2 + 8, y/2 + 8, v[3]); + } +} + +void SpdDownsampleMips_0_1_LDS(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ + AF4 v[4]; + + ASU2 tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2); + ASU2 pix = ASU2(workGroupID.xy * 32) + ASU2(x, y); + v[0] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[0], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y); + v[1] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[1], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x, y + 16); + v[2] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[2], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y + 16); + v[3] = SpdReduceLoadSourceImage(tex, slice); + SpdStore(pix, v[3], 0, slice); + + if (mip <= 1) + return; + + for (int i = 0; i < 4; i++) + { + SpdStoreIntermediate(x, y, v[i]); + SpdWorkgroupShuffleBarrier(); + if (localInvocationIndex < 64) + { + v[i] = SpdReduceIntermediate( + AU2(x * 2 + 0, y * 2 + 0), + AU2(x * 2 + 1, y * 2 + 0), + AU2(x * 2 + 0, y * 2 + 1), + AU2(x * 2 + 1, y * 2 + 1) + ); + SpdStore(ASU2(workGroupID.xy * 16) + ASU2(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice); + } + SpdWorkgroupShuffleBarrier(); + } + + if (localInvocationIndex < 64) + { + SpdStoreIntermediate(x + 0, y + 0, v[0]); + SpdStoreIntermediate(x + 8, y + 0, v[1]); + SpdStoreIntermediate(x + 0, y + 8, v[2]); + SpdStoreIntermediate(x + 8, y + 8, v[3]); + } +} + +void SpdDownsampleMips_0_1(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + SpdDownsampleMips_0_1_LDS(x, y, workGroupID, localInvocationIndex, mip, slice); +#else + SpdDownsampleMips_0_1_Intrinsics(x, y, workGroupID, localInvocationIndex, mip, slice); +#endif +} + + +void SpdDownsampleMip_2(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 64) + { + AF4 v = SpdReduceIntermediate( + AU2(x * 2 + 0, y * 2 + 0), + AU2(x * 2 + 1, y * 2 + 0), + AU2(x * 2 + 0, y * 2 + 1), + AU2(x * 2 + 1, y * 2 + 1) + ); + SpdStore(ASU2(workGroupID.xy * 8) + ASU2(x, y), v, mip, slice); + // store to LDS, try to reduce bank conflicts + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + // ... + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + SpdStoreIntermediate(x * 2 + y % 2, y * 2, v); + } +#else + AF4 v = SpdLoadIntermediate(x, y); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ASU2(workGroupID.xy * 8) + ASU2(x/2, y/2), v, mip, slice); + SpdStoreIntermediate(x + (y/2) % 2, y, v); + } +#endif +} + +void SpdDownsampleMip_3(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 16) + { + // x 0 x 0 + // 0 0 0 0 + // 0 x 0 x + // 0 0 0 0 + AF4 v = SpdReduceIntermediate( + AU2(x * 4 + 0 + 0, y * 4 + 0), + AU2(x * 4 + 2 + 0, y * 4 + 0), + AU2(x * 4 + 0 + 1, y * 4 + 2), + AU2(x * 4 + 2 + 1, y * 4 + 2) + ); + SpdStore(ASU2(workGroupID.xy * 4) + ASU2(x, y), v, mip, slice); + // store to LDS + // x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 + // ... + // 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 + // ... + // 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x + // ... + SpdStoreIntermediate(x * 4 + y, y * 4, v); + } +#else + if (localInvocationIndex < 64) + { + AF4 v = SpdLoadIntermediate(x * 2 + y % 2,y * 2); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ASU2(workGroupID.xy * 4) + ASU2(x/2, y/2), v, mip, slice); + SpdStoreIntermediate(x * 2 + y/2, y * 2, v); + } + } +#endif +} + +void SpdDownsampleMip_4(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 4) + { + // x 0 0 0 x 0 0 0 + // ... + // 0 x 0 0 0 x 0 0 + AF4 v = SpdReduceIntermediate( + AU2(x * 8 + 0 + 0 + y * 2, y * 8 + 0), + AU2(x * 8 + 4 + 0 + y * 2, y * 8 + 0), + AU2(x * 8 + 0 + 1 + y * 2, y * 8 + 4), + AU2(x * 8 + 4 + 1 + y * 2, y * 8 + 4) + ); + SpdStore(ASU2(workGroupID.xy * 2) + ASU2(x, y), v, mip, slice); + // store to LDS + // x x x x 0 ... + // 0 ... + SpdStoreIntermediate(x + y * 2, 0, v); + } +#else + if (localInvocationIndex < 16) + { + AF4 v = SpdLoadIntermediate(x * 4 + y,y * 4); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ASU2(workGroupID.xy * 2) + ASU2(x/2, y/2), v, mip, slice); + SpdStoreIntermediate(x / 2 + y, 0, v); + } + } +#endif +} + +void SpdDownsampleMip_5(AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 1) + { + // x x x x 0 ... + // 0 ... + AF4 v = SpdReduceIntermediate( + AU2(0, 0), + AU2(1, 0), + AU2(2, 0), + AU2(3, 0) + ); + SpdStore(ASU2(workGroupID.xy), v, mip, slice); + } +#else + if (localInvocationIndex < 4) + { + AF4 v = SpdLoadIntermediate(localInvocationIndex,0); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ASU2(workGroupID.xy), v, mip, slice); + } + } +#endif +} + +void SpdDownsampleMips_6_7(AU1 x, AU1 y, AU1 mips, AU1 slice) +{ + ASU2 tex = ASU2(x * 4 + 0, y * 4 + 0); + ASU2 pix = ASU2(x * 2 + 0, y * 2 + 0); + AF4 v0 = SpdReduceLoad4(tex, slice); + SpdStore(pix, v0, 6, slice); + + tex = ASU2(x * 4 + 2, y * 4 + 0); + pix = ASU2(x * 2 + 1, y * 2 + 0); + AF4 v1 = SpdReduceLoad4(tex, slice); + SpdStore(pix, v1, 6, slice); + + tex = ASU2(x * 4 + 0, y * 4 + 2); + pix = ASU2(x * 2 + 0, y * 2 + 1); + AF4 v2 = SpdReduceLoad4(tex, slice); + SpdStore(pix, v2, 6, slice); + + tex = ASU2(x * 4 + 2, y * 4 + 2); + pix = ASU2(x * 2 + 1, y * 2 + 1); + AF4 v3 = SpdReduceLoad4(tex, slice); + SpdStore(pix, v3, 6, slice); + + if (mips <= 7) return; + // no barrier needed, working on values only from the same thread + + AF4 v = SpdReduce4(v0, v1, v2, v3); + SpdStore(ASU2(x, y), v, 7, slice); + SpdStoreIntermediate(x, y, v); +} + +void SpdDownsampleNextFour(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 baseMip, AU1 mips, AU1 slice) +{ + if (mips <= baseMip) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_2(x, y, workGroupID, localInvocationIndex, baseMip, slice); + + if (mips <= baseMip + 1) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_3(x, y, workGroupID, localInvocationIndex, baseMip + 1, slice); + + if (mips <= baseMip + 2) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_4(x, y, workGroupID, localInvocationIndex, baseMip + 2, slice); + + if (mips <= baseMip + 3) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_5(workGroupID, localInvocationIndex, baseMip + 3, slice); +} + +void SpdDownsample( + AU2 workGroupID, + AU1 localInvocationIndex, + AU1 mips, + AU1 numWorkGroups, + AU1 slice +) { + AU2 sub_xy = ARmpRed8x8(localInvocationIndex % 64); + AU1 x = sub_xy.x + 8 * ((localInvocationIndex >> 6) % 2); + AU1 y = sub_xy.y + 8 * ((localInvocationIndex >> 7)); + SpdDownsampleMips_0_1(x, y, workGroupID, localInvocationIndex, mips, slice); + + SpdDownsampleNextFour(x, y, workGroupID, localInvocationIndex, 2, mips, slice); + + if (mips <= 6) return; + + if (SpdExitWorkgroup(numWorkGroups, localInvocationIndex, slice)) return; + + SpdResetAtomicCounter(slice); + + // After mip 6 there is only a single workgroup left that downsamples the remaining up to 64x64 texels. + SpdDownsampleMips_6_7(x, y, mips, slice); + + SpdDownsampleNextFour(x, y, AU2(0,0), localInvocationIndex, 8, mips, slice); +} + +void SpdDownsample( + AU2 workGroupID, + AU1 localInvocationIndex, + AU1 mips, + AU1 numWorkGroups, + AU1 slice, + AU2 workGroupOffset +) { + SpdDownsample(workGroupID + workGroupOffset, localInvocationIndex, mips, numWorkGroups, slice); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//============================================================================================================================== +// PACKED VERSION +//============================================================================================================================== + +#ifdef A_HALF + +#ifdef A_GLSL +#extension GL_EXT_shader_subgroup_extended_types_float16:require +#endif + +AH4 SpdReduceQuadH(AH4 v) +{ + #if defined(A_GLSL) && !defined(SPD_NO_WAVE_OPERATIONS) + AH4 v0 = v; + AH4 v1 = subgroupQuadSwapHorizontal(v); + AH4 v2 = subgroupQuadSwapVertical(v); + AH4 v3 = subgroupQuadSwapDiagonal(v); + return SpdReduce4H(v0, v1, v2, v3); + #elif defined(A_HLSL) && !defined(SPD_NO_WAVE_OPERATIONS) + // requires SM6.0 + AU1 quad = WaveGetLaneIndex() & (~0x3); + AH4 v0 = v; + AH4 v1 = WaveReadLaneAt(v, quad | 1); + AH4 v2 = WaveReadLaneAt(v, quad | 2); + AH4 v3 = WaveReadLaneAt(v, quad | 3); + return SpdReduce4H(v0, v1, v2, v3); + /* + // if SM6.0 is not available, you can use the AMD shader intrinsics + // the AMD shader intrinsics are available in AMD GPU Services (AGS) library: + // https://gpuopen.com/amd-gpu-services-ags-library/ + // works for DX11 + AH4 v0 = v; + AH4 v1; + v1.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + v1.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + v1.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + v1.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX1); + AH4 v2; + v2.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + v2.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + v2.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + v2.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_SwapX2); + AH4 v3; + v3.x = AmdExtD3DShaderIntrinsics_SwizzleF(v.x, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + v3.y = AmdExtD3DShaderIntrinsics_SwizzleF(v.y, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + v3.z = AmdExtD3DShaderIntrinsics_SwizzleF(v.z, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + v3.w = AmdExtD3DShaderIntrinsics_SwizzleF(v.w, AmdExtD3DShaderIntrinsicsSwizzle_ReverseX4); + return SpdReduce4H(v0, v1, v2, v3); + */ + #endif + return AH4(0.0, 0.0, 0.0, 0.0); + +} + +AH4 SpdReduceIntermediateH(AU2 i0, AU2 i1, AU2 i2, AU2 i3) +{ + AH4 v0 = SpdLoadIntermediateH(i0.x, i0.y); + AH4 v1 = SpdLoadIntermediateH(i1.x, i1.y); + AH4 v2 = SpdLoadIntermediateH(i2.x, i2.y); + AH4 v3 = SpdLoadIntermediateH(i3.x, i3.y); + return SpdReduce4H(v0, v1, v2, v3); +} + +AH4 SpdReduceLoad4H(AU2 i0, AU2 i1, AU2 i2, AU2 i3, AU1 slice) +{ + AH4 v0 = SpdLoadH(ASU2(i0), slice); + AH4 v1 = SpdLoadH(ASU2(i1), slice); + AH4 v2 = SpdLoadH(ASU2(i2), slice); + AH4 v3 = SpdLoadH(ASU2(i3), slice); + return SpdReduce4H(v0, v1, v2, v3); +} + +AH4 SpdReduceLoad4H(AU2 base, AU1 slice) +{ + return SpdReduceLoad4H( + AU2(base + AU2(0, 0)), + AU2(base + AU2(0, 1)), + AU2(base + AU2(1, 0)), + AU2(base + AU2(1, 1)), + slice); +} + +AH4 SpdReduceLoadSourceImage4H(AU2 i0, AU2 i1, AU2 i2, AU2 i3, AU1 slice) +{ + AH4 v0 = SpdLoadSourceImageH(ASU2(i0), slice); + AH4 v1 = SpdLoadSourceImageH(ASU2(i1), slice); + AH4 v2 = SpdLoadSourceImageH(ASU2(i2), slice); + AH4 v3 = SpdLoadSourceImageH(ASU2(i3), slice); + return SpdReduce4H(v0, v1, v2, v3); +} + +AH4 SpdReduceLoadSourceImageH(AU2 base, AU1 slice) +{ +#ifdef SPD_LINEAR_SAMPLER + return SpdLoadSourceImageH(ASU2(base), slice); +#else + return SpdReduceLoadSourceImage4H( + AU2(base + AU2(0, 0)), + AU2(base + AU2(0, 1)), + AU2(base + AU2(1, 0)), + AU2(base + AU2(1, 1)), + slice); +#endif +} + +void SpdDownsampleMips_0_1_IntrinsicsH(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mips, AU1 slice) +{ + AH4 v[4]; + + ASU2 tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2); + ASU2 pix = ASU2(workGroupID.xy * 32) + ASU2(x, y); + v[0] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[0], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y); + v[1] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[1], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x, y + 16); + v[2] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[2], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y + 16); + v[3] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[3], 0, slice); + + if (mips <= 1) + return; + + v[0] = SpdReduceQuadH(v[0]); + v[1] = SpdReduceQuadH(v[1]); + v[2] = SpdReduceQuadH(v[2]); + v[3] = SpdReduceQuadH(v[3]); + + if ((localInvocationIndex % 4) == 0) + { + SpdStoreH(ASU2(workGroupID.xy * 16) + ASU2(x/2, y/2), v[0], 1, slice); + SpdStoreIntermediateH(x/2, y/2, v[0]); + + SpdStoreH(ASU2(workGroupID.xy * 16) + ASU2(x/2 + 8, y/2), v[1], 1, slice); + SpdStoreIntermediateH(x/2 + 8, y/2, v[1]); + + SpdStoreH(ASU2(workGroupID.xy * 16) + ASU2(x/2, y/2 + 8), v[2], 1, slice); + SpdStoreIntermediateH(x/2, y/2 + 8, v[2]); + + SpdStoreH(ASU2(workGroupID.xy * 16) + ASU2(x/2 + 8, y/2 + 8), v[3], 1, slice); + SpdStoreIntermediateH(x/2 + 8, y/2 + 8, v[3]); + } +} + +void SpdDownsampleMips_0_1_LDSH(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mips, AU1 slice) +{ + AH4 v[4]; + + ASU2 tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2); + ASU2 pix = ASU2(workGroupID.xy * 32) + ASU2(x, y); + v[0] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[0], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y); + v[1] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[1], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x, y + 16); + v[2] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[2], 0, slice); + + tex = ASU2(workGroupID.xy * 64) + ASU2(x * 2 + 32, y * 2 + 32); + pix = ASU2(workGroupID.xy * 32) + ASU2(x + 16, y + 16); + v[3] = SpdReduceLoadSourceImageH(tex, slice); + SpdStoreH(pix, v[3], 0, slice); + + if (mips <= 1) + return; + + for (int i = 0; i < 4; i++) + { + SpdStoreIntermediateH(x, y, v[i]); + SpdWorkgroupShuffleBarrier(); + if (localInvocationIndex < 64) + { + v[i] = SpdReduceIntermediateH( + AU2(x * 2 + 0, y * 2 + 0), + AU2(x * 2 + 1, y * 2 + 0), + AU2(x * 2 + 0, y * 2 + 1), + AU2(x * 2 + 1, y * 2 + 1) + ); + SpdStoreH(ASU2(workGroupID.xy * 16) + ASU2(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice); + } + SpdWorkgroupShuffleBarrier(); + } + + if (localInvocationIndex < 64) + { + SpdStoreIntermediateH(x + 0, y + 0, v[0]); + SpdStoreIntermediateH(x + 8, y + 0, v[1]); + SpdStoreIntermediateH(x + 0, y + 8, v[2]); + SpdStoreIntermediateH(x + 8, y + 8, v[3]); + } +} + +void SpdDownsampleMips_0_1H(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mips, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + SpdDownsampleMips_0_1_LDSH(x, y, workGroupID, localInvocationIndex, mips, slice); +#else + SpdDownsampleMips_0_1_IntrinsicsH(x, y, workGroupID, localInvocationIndex, mips, slice); +#endif +} + + +void SpdDownsampleMip_2H(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 64) + { + AH4 v = SpdReduceIntermediateH( + AU2(x * 2 + 0, y * 2 + 0), + AU2(x * 2 + 1, y * 2 + 0), + AU2(x * 2 + 0, y * 2 + 1), + AU2(x * 2 + 1, y * 2 + 1) + ); + SpdStoreH(ASU2(workGroupID.xy * 8) + ASU2(x, y), v, mip, slice); + // store to LDS, try to reduce bank conflicts + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + // ... + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + SpdStoreIntermediateH(x * 2 + y % 2, y * 2, v); + } +#else + AH4 v = SpdLoadIntermediateH(x, y); + v = SpdReduceQuadH(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStoreH(ASU2(workGroupID.xy * 8) + ASU2(x/2, y/2), v, mip, slice); + SpdStoreIntermediateH(x + (y/2) % 2, y, v); + } +#endif +} + +void SpdDownsampleMip_3H(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 16) + { + // x 0 x 0 + // 0 0 0 0 + // 0 x 0 x + // 0 0 0 0 + AH4 v = SpdReduceIntermediateH( + AU2(x * 4 + 0 + 0, y * 4 + 0), + AU2(x * 4 + 2 + 0, y * 4 + 0), + AU2(x * 4 + 0 + 1, y * 4 + 2), + AU2(x * 4 + 2 + 1, y * 4 + 2) + ); + SpdStoreH(ASU2(workGroupID.xy * 4) + ASU2(x, y), v, mip, slice); + // store to LDS + // x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 + // ... + // 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 + // ... + // 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x + // ... + SpdStoreIntermediateH(x * 4 + y, y * 4, v); + } +#else + if (localInvocationIndex < 64) + { + AH4 v = SpdLoadIntermediateH(x * 2 + y % 2,y * 2); + v = SpdReduceQuadH(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStoreH(ASU2(workGroupID.xy * 4) + ASU2(x/2, y/2), v, mip, slice); + SpdStoreIntermediateH(x * 2 + y/2, y * 2, v); + } + } +#endif +} + +void SpdDownsampleMip_4H(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 4) + { + // x 0 0 0 x 0 0 0 + // ... + // 0 x 0 0 0 x 0 0 + AH4 v = SpdReduceIntermediateH( + AU2(x * 8 + 0 + 0 + y * 2, y * 8 + 0), + AU2(x * 8 + 4 + 0 + y * 2, y * 8 + 0), + AU2(x * 8 + 0 + 1 + y * 2, y * 8 + 4), + AU2(x * 8 + 4 + 1 + y * 2, y * 8 + 4) + ); + SpdStoreH(ASU2(workGroupID.xy * 2) + ASU2(x, y), v, mip, slice); + // store to LDS + // x x x x 0 ... + // 0 ... + SpdStoreIntermediateH(x + y * 2, 0, v); + } +#else + if (localInvocationIndex < 16) + { + AH4 v = SpdLoadIntermediateH(x * 4 + y,y * 4); + v = SpdReduceQuadH(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStoreH(ASU2(workGroupID.xy * 2) + ASU2(x/2, y/2), v, mip, slice); + SpdStoreIntermediateH(x / 2 + y, 0, v); + } + } +#endif +} + +void SpdDownsampleMip_5H(AU2 workGroupID, AU1 localInvocationIndex, AU1 mip, AU1 slice) +{ +#ifdef SPD_NO_WAVE_OPERATIONS + if (localInvocationIndex < 1) + { + // x x x x 0 ... + // 0 ... + AH4 v = SpdReduceIntermediateH( + AU2(0, 0), + AU2(1, 0), + AU2(2, 0), + AU2(3, 0) + ); + SpdStoreH(ASU2(workGroupID.xy), v, mip, slice); + } +#else + if (localInvocationIndex < 4) + { + AH4 v = SpdLoadIntermediateH(localInvocationIndex,0); + v = SpdReduceQuadH(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStoreH(ASU2(workGroupID.xy), v, mip, slice); + } + } +#endif +} + +void SpdDownsampleMips_6_7H(AU1 x, AU1 y, AU1 mips, AU1 slice) +{ + ASU2 tex = ASU2(x * 4 + 0, y * 4 + 0); + ASU2 pix = ASU2(x * 2 + 0, y * 2 + 0); + AH4 v0 = SpdReduceLoad4H(tex, slice); + SpdStoreH(pix, v0, 6, slice); + + tex = ASU2(x * 4 + 2, y * 4 + 0); + pix = ASU2(x * 2 + 1, y * 2 + 0); + AH4 v1 = SpdReduceLoad4H(tex, slice); + SpdStoreH(pix, v1, 6, slice); + + tex = ASU2(x * 4 + 0, y * 4 + 2); + pix = ASU2(x * 2 + 0, y * 2 + 1); + AH4 v2 = SpdReduceLoad4H(tex, slice); + SpdStoreH(pix, v2, 6, slice); + + tex = ASU2(x * 4 + 2, y * 4 + 2); + pix = ASU2(x * 2 + 1, y * 2 + 1); + AH4 v3 = SpdReduceLoad4H(tex, slice); + SpdStoreH(pix, v3, 6, slice); + + if (mips < 8) return; + // no barrier needed, working on values only from the same thread + + AH4 v = SpdReduce4H(v0, v1, v2, v3); + SpdStoreH(ASU2(x, y), v, 7, slice); + SpdStoreIntermediateH(x, y, v); +} + +void SpdDownsampleNextFourH(AU1 x, AU1 y, AU2 workGroupID, AU1 localInvocationIndex, AU1 baseMip, AU1 mips, AU1 slice) +{ + if (mips <= baseMip) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_2H(x, y, workGroupID, localInvocationIndex, baseMip, slice); + + if (mips <= baseMip + 1) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_3H(x, y, workGroupID, localInvocationIndex, baseMip + 1, slice); + + if (mips <= baseMip + 2) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_4H(x, y, workGroupID, localInvocationIndex, baseMip + 2, slice); + + if (mips <= baseMip + 3) return; + SpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_5H(workGroupID, localInvocationIndex, baseMip + 3, slice); +} + +void SpdDownsampleH( + AU2 workGroupID, + AU1 localInvocationIndex, + AU1 mips, + AU1 numWorkGroups, + AU1 slice +) { + AU2 sub_xy = ARmpRed8x8(localInvocationIndex % 64); + AU1 x = sub_xy.x + 8 * ((localInvocationIndex >> 6) % 2); + AU1 y = sub_xy.y + 8 * ((localInvocationIndex >> 7)); + + SpdDownsampleMips_0_1H(x, y, workGroupID, localInvocationIndex, mips, slice); + + SpdDownsampleNextFourH(x, y, workGroupID, localInvocationIndex, 2, mips, slice); + + if (mips < 7) return; + + if (SpdExitWorkgroup(numWorkGroups, localInvocationIndex, slice)) return; + + SpdResetAtomicCounter(slice); + + // After mip 6 there is only a single workgroup left that downsamples the remaining up to 64x64 texels. + SpdDownsampleMips_6_7H(x, y, mips, slice); + + SpdDownsampleNextFourH(x, y, AU2(0,0), localInvocationIndex, 8, mips, slice); +} + +void SpdDownsampleH( + AU2 workGroupID, + AU1 localInvocationIndex, + AU1 mips, + AU1 numWorkGroups, + AU1 slice, + AU2 workGroupOffset +) { + SpdDownsampleH(workGroupID + workGroupOffset, localInvocationIndex, mips, numWorkGroups, slice); +} + +#endif // #ifdef A_HALF +#endif // #ifdef A_GPU \ No newline at end of file diff --git a/manul/shaders/manul/apply_auto_exposure.hlsl b/manul/shaders/manul/apply_auto_exposure.hlsl new file mode 100644 index 00000000..537cda1a --- /dev/null +++ b/manul/shaders/manul/apply_auto_exposure.hlsl @@ -0,0 +1,7 @@ +RWTexture2D g_ColorImage : register(u0); +Texture2D g_AutoExposureTexture : register(t0); + +[numthreads(8, 8, 1)] +void CS_ApplyAutoExposure(uint3 PixCoord : SV_DispatchThreadID) { + g_ColorImage[PixCoord.xy] = max(1.e-6, g_ColorImage[PixCoord.xy] * pow(2., g_AutoExposureTexture[uint2(0, 0)].x)); +} diff --git a/manul/shaders/manul/bloom.hlsl b/manul/shaders/manul/bloom.hlsl new file mode 100644 index 00000000..4753a811 --- /dev/null +++ b/manul/shaders/manul/bloom.hlsl @@ -0,0 +1,90 @@ + +Texture2D g_Source : register(t0); +Texture2D g_SourceAdd : register(t1); +RWTexture2D g_Output : register(u0); +SamplerState g_SamplerLinearClamp : register(s0); + +cbuffer BloomConstants : register(b0){ + float4 g_PrefilterVector; + float g_MixFactor; +} + +float3 Prefilter(float3 c) { + float brightness = max(c.r, max(c.g, c.b)); + float soft = brightness - g_PrefilterVector.y; + soft = clamp(soft, 0, g_PrefilterVector.z); + soft = soft * soft * g_PrefilterVector.w; + float contribution = max(soft, brightness - g_PrefilterVector.x); + contribution /= max(brightness, 0.00001); + return c * contribution; +} + +[numthreads(8, 8, 1)] +void CS_BloomPrefilter(uint3 PixCoord : SV_DispatchThreadID) { + g_Output[PixCoord.xy].xyz = Prefilter(g_Source[PixCoord.xy]); +} + +[numthreads(8, 8, 1)] +void CS_BloomDownsample(uint3 PixCoord : SV_DispatchThreadID) { + uint2 pixel = PixCoord.xy; + uint2 source_size; + g_Source.GetDimensions(source_size.x, source_size.y); + uint2 output_size; + g_Output.GetDimensions(output_size.x, output_size.y); + float2 uv = (pixel + .5) / output_size; + float2 offset = 1. / source_size; + + float3 a = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-2, 2), 0.); + float3 b = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(0, 2), 0.); + float3 c = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(2, 2), 0.); + + float3 d = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-2, 0), 0.); + float3 e = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(0, 0), 0.); + float3 f = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(2, 0), 0.); + + float3 g = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-2, -2), 0.); + float3 h = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(0, -2), 0.); + float3 i = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(2, -2), 0.); + + float3 j = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-1, 1), 0.); + float3 k = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(1, 1), 0.); + float3 l = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-1, -1), 0.); + float3 m = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(1, -1), 0.); + + g_Output[pixel].xyz = e*0.125+(a+c+g+i)*0.03125+(b+d+f+h)*0.0625+(j+k+l+m)*0.125; +} + +float3 SampleTent(Texture2D source, SamplerState source_sampler, float2 uv) { + uint2 source_size; + source.GetDimensions(source_size.x, source_size.y); + float2 offset = 1. / source_size; + + float3 a = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-1, 1), 0.); + float3 b = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(0, 1), 0.); + float3 c = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(1, 1), 0.); + + float3 d = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-1, 0), 0.); + float3 e = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(0, 0), 0.); + float3 f = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(1, 0), 0.); + + float3 g = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(-1, -1), 0.); + float3 h = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(0, -1), 0.); + float3 i = g_Source.SampleLevel(g_SamplerLinearClamp, uv + offset * float2(1, -1), 0.); + return e*0.25+(a+c+g+i)*0.0625+(b+d+f+h)*0.125; +} + +[numthreads(8, 8, 1)] +void CS_BloomUpsample(uint3 PixCoord : SV_DispatchThreadID) { + uint2 output_size; + g_Output.GetDimensions(output_size.x, output_size.y); + float2 uv = (PixCoord.xy + .5) / output_size; + g_Output[PixCoord.xy].xyz += SampleTent(g_Source, g_SamplerLinearClamp, uv); +} + +[numthreads(8, 8, 1)] +void CS_BloomApply(uint3 PixCoord : SV_DispatchThreadID) { + uint2 output_size; + g_Output.GetDimensions(output_size.x, output_size.y); + float2 uv = (PixCoord.xy + .5) / output_size; + g_Output[PixCoord.xy].xyz = g_SourceAdd[PixCoord.xy] + SampleTent(g_Source, g_SamplerLinearClamp, uv) * g_MixFactor; +} \ No newline at end of file diff --git a/manul/shaders/manul/color_transform.hlsli b/manul/shaders/manul/color_transform.hlsli new file mode 100644 index 00000000..18bfcc7e --- /dev/null +++ b/manul/shaders/manul/color_transform.hlsli @@ -0,0 +1,61 @@ +#ifndef COLOR_TRANSFORM_HLSLI +#define COLOR_TRANSFORM_HLSLI + +float3 ACEScg_to_XYZ(in float3 c); +float3 REC709_to_XYZ(in float3 c); +float3 AdobeRGB_to_XYZ(in float3 c); +float3 XYZ_to_REC2020(in float3 c); +float3 XYZ_to_REC709(in float3 c); +float3 ACEScg_to_REC709(in float3 c); + +float3 ACEScg_to_XYZ(in float3 c){ + return + c.r * float3(0.66245418, 0.27222872, -0.00557465) + + c.g * float3(0.13400421, 0.67408177, 0.00406073) + + c.b * float3(0.15618769, 0.05368952, 1.0103391); +} + +float3 REC709_to_XYZ(in float3 c) { + return + c.r * float3(0.4123908, 0.21263901, 0.01933082) + + c.g * float3(0.35758434, 0.71516868, 0.11919478) + + c.b * float3(0.18048079, 0.07219232, 0.95053215); +} + +float3 AdobeRGB_to_XYZ(in float3 c) { + return + c.r * float3(0.57667, 0.29734, 0.02703) + + c.g * float3(0.18556, 0.62736, 0.07069) + + c.b * float3(0.18823, 0.07529, 0.99134); +} + +float3 XYZ_to_REC2020(in float3 c) { + return + c.r * float3(1.71665119, -0.66668435, 0.01763986) + + c.g * float3(-0.35567078, 1.61648124, -0.04277061) + + c.b * float3(-0.25336628, 0.01576855, 0.94210312); +} + +float3 XYZ_to_REC709(in float3 c) { + return + c.r * float3(3.24096994, -0.96924364, 0.05563008) + + c.g * float3(-1.53738318, 1.8759675, -0.20397696) + + c.b * float3(-0.49861076, 0.04155506, 1.05697151); +} + +float3 ACEScg_to_REC709(in float3 c) { + return + c.r * float3(1.70507964, -0.12970053, -0.02416634) + + c.g * float3(-0.62423346, 1.13846855, -0.12461416) + + c.b * float3(-0.08084618, -0.00876802, 1.1487805); +} + +float REC709_to_Luma(in float3 c) { + return dot(c, float3(.2126, .7152, .0722)); +} + +float XYZ_to_Luma(in float3 c) { + return REC709_to_Luma(XYZ_to_REC709(c)); +} + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/comp_luminance.hlsl b/manul/shaders/manul/comp_luminance.hlsl new file mode 100644 index 00000000..83f529e0 --- /dev/null +++ b/manul/shaders/manul/comp_luminance.hlsl @@ -0,0 +1,150 @@ + +#include "color_transform.hlsli" + +/* ---------------------------------------------------------------------------------------------- */ +/* Resource bindings */ +/* ---------------------------------------------------------------------------------------------- */ + +Texture2D g_ColorImage : register(t0); + +globallycoherent RWTexture2D g_AutoExposureTexture : register(u0); + +globallycoherent RWStructuredBuffer g_SpdAtomicCounter : register(u2); +globallycoherent RWTexture2D g_ImgMip6 : register(u3); + +/* --------------------------------------- Push constants --------------------------------------- */ + +struct PushConstantsCompLuminance { + uint m_num_mips; + uint m_num_workgroups; + float m_delta_time; + float m_min_luminance_ev; + float m_max_luminance_ev; +}; + +#ifdef SPIRV +[[vk::push_constant]] ConstantBuffer g_PushConstants; +#else +cbuffer g_Const : register(b1) { PushConstantsCompLuminance g_PushConstants; } +#endif + +/* ---------------------------------------------------------------------------------------------- */ +/* Function interface */ +/* ---------------------------------------------------------------------------------------------- */ + +uint GetNumMips() { + return g_PushConstants.m_num_mips; +} + +uint GetNumWorkGroups() { + return g_PushConstants.m_num_workgroups; +} + +float GetDeltaTime() { + return g_PushConstants.m_delta_time; +} + +float ComputeAutoExposureFromLavg(float l_avg) { + l_avg = exp(l_avg); + + const float s = 100.; //ISO arithmetic speed + const float k = 12.5; + float exposure_iso100 = log2((l_avg * s) / k); + + const float q = .65; + float l_max = (78. / (q * s)) * pow(2., exposure_iso100); + + return 1. / l_max; +} + +void OutputLuminance(float4 out_value) { + float prev = g_AutoExposureTexture[uint2(0, 0)].y; + float result = out_value.r; + + result = clamp( + result, + log(.125 * exp2(g_PushConstants.m_min_luminance_ev)), + log(.125 * exp2(g_PushConstants.m_max_luminance_ev))); + + if (prev < 1.e8) // Compare Lavg, so small or negative values + { + result = prev + (result - prev) * (1. - exp(-GetDeltaTime())); + } + g_AutoExposureTexture[uint2(0, 0)] = float2(ComputeAutoExposureFromLavg(result), result); +} + +/* ---------------------------------------------------------------------------------------------- */ +/* SPD implementation */ +/* ---------------------------------------------------------------------------------------------- */ + +#define A_GPU +#define A_HLSL +#include "amd/ffx_a.h" + +/* ---------------------------------------- Shared memory --------------------------------------- */ + +groupshared AU1 g_SpdCounter; +groupshared AF4 g_SpdIntermediate[16][16]; + +/* ------------------------------------ Image load-store ops ------------------------------------ */ + +AF4 SpdLoadSourceImage(ASU2 p, AU1 slice){ + return AF4(log(max(1.e-6, XYZ_to_Luma(g_ColorImage[p].rgb))), 0., 0., 0.); +} + +AF4 SpdLoad(ASU2 p, AU1 slice) { + return float4(g_ImgMip6[p], 0., 0., 0.); +} + +void SpdStore(ASU2 p, AF4 value, AU1 mip, AU1 slice) { + if(mip == 5) { + g_ImgMip6[p].r = value; + } + else if(mip == GetNumMips() - 1 && all(p == ASU2(0, 0))) { + OutputLuminance(value); + } +} + +/* -------------------------------- Shared memory load-store ops -------------------------------- */ + +AF4 SpdLoadIntermediate(ASU1 x, ASU1 y) { + return g_SpdIntermediate[x][y]; +} + +void SpdStoreIntermediate(ASU1 x, ASU1 y, AF4 value) { + g_SpdIntermediate[x][y] = value; +} + +/* ------------------------------------- Atomic counter ops ------------------------------------- */ + +void SpdIncreaseAtomicCounter(AU1 slice) { + InterlockedAdd(g_SpdAtomicCounter[0], 1, g_SpdCounter); +} + +AU1 SpdGetAtomicCounter() { + return g_SpdCounter; +} + +void SpdResetAtomicCounter(AU1 slice) { + g_SpdAtomicCounter[0] = 0; +} + +/* ------------------------------------- Reduction function ------------------------------------- */ + +AF4 SpdReduce4(AF4 v0, AF4 v1, AF4 v2, AF4 v3) { + return (v0 + v1 + v2 + v3) * .25; +} + +/* ----------------------------------------- SPD wrapper ---------------------------------------- */ + +#include "amd/ffx_spd.h" + +[numthreads(256,1,1)] +void CS_ComputeAvgLuminance(uint3 WorkGroupId : SV_GroupID, uint LocalThreadIndex : SV_GroupIndex) { + SpdDownsample( + WorkGroupId.xy, + LocalThreadIndex, + GetNumMips(), + GetNumWorkGroups(), + 0); +}; diff --git a/manul/shaders/manul/draw_constants.hlsli b/manul/shaders/manul/draw_constants.hlsli new file mode 100644 index 00000000..7fe93e35 --- /dev/null +++ b/manul/shaders/manul/draw_constants.hlsli @@ -0,0 +1,31 @@ +#ifndef DRAW_CONSTANTS_HLSLI +#define DRAW_CONSTANTS_HLSLI + +struct VertexPushConstants { + float4x3 m_ModelView; + float4x3 m_ModelViewHistory; + float3 m_Diffuse; + float m_AlphaThreshold; + float m_AlphaMult; + float m_SelfIllum; +}; + +#ifdef SPIRV + +[[vk::push_constant]] ConstantBuffer g_DrawConstants; + +#else + +cbuffer g_Const : register(b1) { VertexPushConstants g_DrawConstants; } + +#endif + +#endif + +float4x3 GetModelView() { + return g_DrawConstants.m_ModelView; +} + +float4x3 GetModelViewHistory() { + return g_DrawConstants.m_ModelViewHistory; +} diff --git a/manul/shaders/manul/draw_constants_shadow.hlsli b/manul/shaders/manul/draw_constants_shadow.hlsli new file mode 100644 index 00000000..c506b1ae --- /dev/null +++ b/manul/shaders/manul/draw_constants_shadow.hlsli @@ -0,0 +1,19 @@ +#ifndef DRAW_CONSTANTS_SHADOW_HLSLI +#define DRAW_CONSTANTS_SHADOW_HLSLI + +struct VertexPushConstantsShadow { + float4x4 m_ModelViewProjection; + float m_AlphaThreshold; +}; + +#ifdef SPIRV + +[[vk::push_constant]] ConstantBuffer g_DrawConstants; + +#else + +cbuffer g_Const : register(b1) { VertexPushConstantsShadow g_DrawConstants; } + +#endif + +#endif diff --git a/manul/shaders/manul/forward_plus/forward_plus.hlsl b/manul/shaders/manul/forward_plus/forward_plus.hlsl new file mode 100644 index 00000000..c886b7f0 --- /dev/null +++ b/manul/shaders/manul/forward_plus/forward_plus.hlsl @@ -0,0 +1,436 @@ +// Shamelessly stolen from https://www.3dgep.com/forward-plus/ + +#include "light.hlsli" +#include "primitives.hlsli" + +#define FORWARD_PLUS_TILE_SIZE 8 +#define FORWARD_PLUS_LIGHTS_PER_TILE 64 + +struct ComputeShaderInput +{ + uint3 groupID : SV_GroupID; + uint3 groupThreadID : SV_GroupThreadID; + uint3 dispatchThreadID : SV_DispatchThreadID; + uint groupIndex : SV_GroupIndex; +}; + +/* ---------------------------------------------------------------------------------------------- */ +/* Constant Buffers */ +/* ---------------------------------------------------------------------------------------------- */ + +// Global variables +cbuffer DispatchParams : register(b4) +{ + // Number of groups dispatched. (This parameter is not available as an HLSL system value!) + uint3 numThreadGroups; + + // Total number of threads dispatched. (Also not available as an HLSL system value!) + // Note: This value may be less than the actual number of threads executed + // if the screen size is not evenly divisible by the block size. + uint3 numThreads; +}; + +// Parameters required to convert screen space coordinates to view space. +cbuffer ScreenToViewParams : register( b3 ) +{ + float4x4 InverseProjection; + float4x4 WorldToViewSpace; + float2 ScreenDimensions; + uint NumLights; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* Shader Resources */ +/* ---------------------------------------------------------------------------------------------- */ + +// The depth from the screen space texture. +Texture2D DepthTextureVS : register(t3); + +StructuredBuffer Lights : register(t8); + +// Precomputed frustums for the grid. +StructuredBuffer in_Frustums : register(t9); + +/* ---------------------------------------------------------------------------------------------- */ +/* Unordered Access views */ +/* ---------------------------------------------------------------------------------------------- */ + +// View space frustums for the grid cells. +RWStructuredBuffer out_Frustums: register(u0); + +// Global counter for current index into the light index list. +// "o_" prefix indicates light lists for opaque geometry while +// "t_" prefix indicates light lists for transparent geometry. +globallycoherent RWStructuredBuffer o_LightIndexCounter: register(u1); +globallycoherent RWStructuredBuffer t_LightIndexCounter: register(u2); + +// Light index lists and light grids. +globallycoherent RWStructuredBuffer o_LightIndexList : register(u3); +globallycoherent RWStructuredBuffer t_LightIndexList : register(u4); +globallycoherent RWTexture2D o_LightGrid : register(u5); +globallycoherent RWTexture2D t_LightGrid : register(u6); + +/* ---------------------------------------------------------------------------------------------- */ +/* Group shared globals */ +/* ---------------------------------------------------------------------------------------------- */ + +groupshared uint uMinDepth; +groupshared uint uMaxDepth; +groupshared Frustum GroupFrustum; + +// Opaque geometry light lists. +groupshared uint o_LightCount; +groupshared uint o_LightIndexStartOffset; +groupshared uint o_LightList[FORWARD_PLUS_LIGHTS_PER_TILE]; + +// Transparent geometry light lists. +groupshared uint t_LightCount; +groupshared uint t_LightIndexStartOffset; +groupshared uint t_LightList[FORWARD_PLUS_LIGHTS_PER_TILE]; + +/* ---------------------------------------------------------------------------------------------- */ +/* Utility functions */ +/* ---------------------------------------------------------------------------------------------- */ + +// Add the light to the visible light list for opaque geometry. +void o_AppendLight(uint lightIndex); + +// Add the light to the visible light list for transparent geometry. +void t_AppendLight(uint lightIndex); + +// Convert clip space coordinates to view space +float4 ClipToView(float4 clip); + +// Convert screen space coordinates to view space. +float4 ScreenToView(float4 screen); + +/* ---------------------------------------------------------------------------------------------- */ +/* Frustum computation */ +/* ---------------------------------------------------------------------------------------------- */ + +[numthreads(FORWARD_PLUS_TILE_SIZE, FORWARD_PLUS_TILE_SIZE, 1)] +void CS_ComputeFrustums(in ComputeShaderInput input) { + // View space eye position is always at the origin. + const float3 eyePos = (float3)0.; + + // Compute the 4 corner points on the far clipping plane to use as the + // frustum vertices. + float4 screenSpace[5]; + // Top left point + screenSpace[0] = float4(input.dispatchThreadID.xy * FORWARD_PLUS_TILE_SIZE, -1.0f, 1.0f); + // Top right point + screenSpace[1] = float4(float2(input.dispatchThreadID.x + 1, input.dispatchThreadID.y) * FORWARD_PLUS_TILE_SIZE, -1.0f, 1.0f); + // Bottom left point + screenSpace[2] = float4(float2(input.dispatchThreadID.x, input.dispatchThreadID.y + 1) * FORWARD_PLUS_TILE_SIZE, -1.0f, 1.0f); + // Bottom right point + screenSpace[3] = float4(float2(input.dispatchThreadID.x + 1, input.dispatchThreadID.y + 1) * FORWARD_PLUS_TILE_SIZE, -1.0f, 1.0f); + // View ray + screenSpace[4] = float4(float2(input.dispatchThreadID.x + .5, input.dispatchThreadID.y + .5) * FORWARD_PLUS_TILE_SIZE, -1.0f, 1.0f); + + float3 viewSpace[5]; + // Now convert the screen space points to view space + for (int i = 0; i < 5; i++) + { + viewSpace[i] = -ScreenToView(screenSpace[i]).xyz; + } + + // Now build the frustum planes from the view space points + Frustum frustum; + + // Left plane + frustum.planes[0] = ComputePlane(eyePos, viewSpace[2], viewSpace[0]); + // Right plane + frustum.planes[1] = ComputePlane(eyePos, viewSpace[1], viewSpace[3]); + // Top plane + frustum.planes[2] = ComputePlane(eyePos, viewSpace[0], viewSpace[1]); + // Bottom plane + frustum.planes[3] = ComputePlane(eyePos, viewSpace[3], viewSpace[2]); + + frustum.m_view_ray = normalize(viewSpace[4]); + float cos_frustum_angle = dot(normalize(viewSpace[0]), normalize(viewSpace[4])); + float sin_frustum_angle = sqrt(1. - saturate(cos_frustum_angle * cos_frustum_angle)); + frustum.m_tan_frustum_angle = sin_frustum_angle / cos_frustum_angle; + //frustum.cone = ComputeConeFrustum((float3)0., normalize(viewSpace[4]), abs()); + + // Store the computed frustum in global memory (if our thread ID is in bounds of the grid). + if (input.dispatchThreadID.x < numThreads.x && input.dispatchThreadID.y < numThreads.y) + { + uint index = input.dispatchThreadID.x + (input.dispatchThreadID.y * numThreads.x); + out_Frustums[index] = frustum; + } +} + +/* ---------------------------------------------------------------------------------------------- */ +/* Light culling */ +/* ---------------------------------------------------------------------------------------------- */ + +float sdSphere( float3 pos, float s ) +{ + return length(pos)-s; +} + +float sdSolidAngle(float3 pos, float2 c, float ra) +{ + float2 p = float2( length(pos.xz), pos.y ); + //float2 p = float2( length(pos.xz), pos.y ); + float l = length(p) - ra; + float m = length(p - c*clamp(dot(p,c),0.0,ra) ); + return max(l,m*sign(c.y*p.x-c.x*p.y)); +} + +float sdSolidAngleOutside(float3 pos, float2 c, float ra) +{ + float2 p = float2( length(pos.xz), pos.y ); + //float2 p = float2( length(pos.xz), pos.y ); + float l = length(p) - ra; + float m = length(p - c*clamp(dot(p,c),0.0,ra) ); + return max(l,-m*sign(c.y*p.x-c.x*p.y)); +} + + +[numthreads(FORWARD_PLUS_TILE_SIZE, FORWARD_PLUS_TILE_SIZE, 1)] +void CS_CullLights(in ComputeShaderInput input) { + // Calculate min & max depth in threadgroup / tile. + int2 texCoord = input.dispatchThreadID.xy; + float fDepth = DepthTextureVS[texCoord].r; + + uint uDepth = asuint(fDepth); + + if (!input.groupIndex) // Avoid contention by other threads in the group. + { + uMinDepth = 0xffffffff; + uMaxDepth = 0; + o_LightCount = 0; + t_LightCount = 0; + GroupFrustum = in_Frustums[input.groupID.x + (input.groupID.y * numThreadGroups.x)]; + } + + GroupMemoryBarrierWithGroupSync(); + + InterlockedMin(uMinDepth, uDepth); + InterlockedMax(uMaxDepth, uDepth); + + GroupMemoryBarrierWithGroupSync(); + + float fMinDepth = asfloat(uMaxDepth); + float fMaxDepth = asfloat(uMinDepth); + + // Convert depth values to view space. + float minDepthVS = ClipToView(float4(0, 0, fMinDepth, 1)).z; + float maxDepthVS = ClipToView(float4(0, 0, fMaxDepth, 1)).z; + float nearClipVS = ClipToView(float4(0, 0, 1, 1)).z; + + // Clipping plane for minimum depth value + // (used for testing lights within the bounds of opaque geometry). + Plane minPlane = { float3(0, 0, -1), -minDepthVS }; + Plane farPlane = { float3(0, 0, 1), maxDepthVS }; + Plane nearPlane = { float3(0, 0, -1), -nearClipVS }; + + for (uint i = input.groupIndex; i < NumLights; i += FORWARD_PLUS_TILE_SIZE * FORWARD_PLUS_TILE_SIZE) { + Light light = UnpackLight(Lights[i]); + float3 lightPosVS = mul(WorldToViewSpace, float4(light.m_origin, 1.)).xyz; + Sphere sphere = { lightPosVS, light.m_radius }; + float tan_angle = GroupFrustum.m_tan_frustum_angle; + float3 rd = GroupFrustum.m_view_ray; + float dist = nearClipVS / rd.z; + float maxDist = maxDepthVS / rd.z; + float minDistOpaque = minDepthVS / rd.z; + bool intersection = false; + bool is_opaque = false; + + if(light.m_cos_outer < -1.5) { + while(dist < maxDist) { + float3 ro = dist * rd; + float distance = sdSphere(ro - lightPosVS, light.m_radius); + if(distance <= dist * tan_angle) { + intersection = true; + is_opaque = dist > minDistOpaque; + break; + } + dist += distance; + } + + if(!is_opaque) { + dist = minDistOpaque - 1.e-2; + while(dist < maxDist) { + float3 ro = dist * rd; + float distance = sdSphere(ro - lightPosVS, light.m_radius); + if(distance <= dist * tan_angle) { + is_opaque = true; + break; + } + dist += distance; + } + } + } else { + float3 lightDirVS = mul((float3x3)WorldToViewSpace, light.m_direction); + float3 tang = select(lightDirVS.z < .999, float3(0., 0., 1.), float3(0., 1., 0.)); + tang = normalize(tang - lightDirVS * dot(tang, lightDirVS)); + float3 bitang = cross(lightDirVS, tang); + + float3x3 transform = {tang, lightDirVS, bitang}; + + float2 angle = {sqrt(1. - light.m_cos_outer * light.m_cos_outer), light.m_cos_outer}; + while(dist < maxDist) { + float3 ro = dist * rd; + float distance = sdSolidAngle(mul(transform, ro - lightPosVS), angle, light.m_radius); + if(distance <= dist * tan_angle) { + intersection = true; + is_opaque = dist > minDistOpaque; + break; + } + dist += distance; + } + + if(!is_opaque) { + dist = minDistOpaque - 1.e-2; + while(dist < maxDist) { + float3 ro = dist * rd; + float distance = sdSolidAngle(mul(transform, ro - lightPosVS), angle, light.m_radius); + if(distance <= dist * tan_angle) { + is_opaque = true; + break; + } + dist += distance; + } + } + + } + + + if(intersection) { + t_AppendLight(i); + + if (is_opaque) + { + // Add light to light list for opaque geometry. + o_AppendLight(i); + } + } + + //continue; +// +// + //if(!DoQueryInfiniteCone(sphere, GroupFrustum.cone)) continue; + //if (dot(light.m_color, (float3)1.)) { + // float cosOuterCone = light.m_cos_outer; +// + // if (cosOuterCone == 0. || true) { // Point light + // if(SphereInsidePlane(sphere, nearPlane) || SphereInsidePlane(sphere, farPlane)) + // continue; +// + // //if (SphereInsideFrustum(sphere, GroupFrustum, nearClipVS, maxDepthVS)) + // //{ + // // Add light to light list for transparent geometry. + // t_AppendLight(i); +// + // if (!SphereInsidePlane(sphere, minPlane)) + // { + // // Add light to light list for opaque geometry. + // o_AppendLight(i); + // } + // //} + // } + // else { // Spot light + // float sinOuterCone = sqrt(saturate(1. - cosOuterCone * cosOuterCone)); + // float tanOuterCone = sinOuterCone / cosOuterCone; + // float3 lightDirVS = mul((float3x3)WorldToViewSpace, light.m_direction); + // float coneRadius = tanOuterCone * light.m_radius; + // Cone cone = { lightPosVS, light.m_radius, lightDirVS, coneRadius }; + // //if (SphereInsideFrustum(sphere, GroupFrustum, nearClipVS, maxDepthVS) && + // // ConeInsideFrustum(cone, GroupFrustum, nearClipVS, maxDepthVS)) + // //{ + // // Add light to light list for transparent geometry. + // t_AppendLight(i); +// + // //if (!SphereInsidePlane(sphere, minPlane) + // // && !ConeInsidePlane(cone, minPlane)) + // //{ + // // Add light to light list for opaque geometry. + // o_AppendLight(i); + // //} + // //} + // } + //} + } + + // Wait till all threads in group have caught up. + GroupMemoryBarrierWithGroupSync(); + + // Update global memory with visible light buffer. + // First update the light grid (only thread 0 in group needs to do this) + if (!input.groupIndex) + { + // Update light grid for opaque geometry. + InterlockedAdd(o_LightIndexCounter[0], o_LightCount, o_LightIndexStartOffset); + o_LightGrid[input.groupID.xy] = uint2(o_LightIndexStartOffset, o_LightCount); + + // Update light grid for transparent geometry. + InterlockedAdd(t_LightIndexCounter[0], t_LightCount, t_LightIndexStartOffset); + t_LightGrid[input.groupID.xy] = uint2(t_LightIndexStartOffset, t_LightCount); + } + + GroupMemoryBarrierWithGroupSync(); + + // Now update the light index list (all threads). + // For opaque geometry. + for (uint i = input.groupIndex; i < o_LightCount; i += FORWARD_PLUS_TILE_SIZE * FORWARD_PLUS_TILE_SIZE) + { + o_LightIndexList[o_LightIndexStartOffset + i] = o_LightList[i]; + } + + // For transparent geometry. + for (uint i = input.groupIndex; i < t_LightCount; i += FORWARD_PLUS_TILE_SIZE * FORWARD_PLUS_TILE_SIZE) + { + t_LightIndexList[t_LightIndexStartOffset + i] = t_LightList[i]; + } +} + +/* ---------------------------------------------------------------------------------------------- */ +/* Function implementations */ +/* ---------------------------------------------------------------------------------------------- */ + +// Add the light to the visible light list for opaque geometry. +void o_AppendLight(uint lightIndex) +{ + uint index; // Index into the visible lights array. + InterlockedAdd(o_LightCount, 1, index); + if (index < FORWARD_PLUS_LIGHTS_PER_TILE) + { + o_LightList[index] = lightIndex; + } +} + +// Add the light to the visible light list for transparent geometry. +void t_AppendLight(uint lightIndex) +{ + uint index; // Index into the visible lights array. + InterlockedAdd(t_LightCount, 1, index); + if (index < FORWARD_PLUS_LIGHTS_PER_TILE) + { + t_LightList[index] = lightIndex; + } +} + +// Convert clip space coordinates to view space +float4 ClipToView( float4 clip ) +{ + // View space position. + float4 view = mul( InverseProjection, clip ); + // Perspective projection. + view = view / view.w; + + return view; +} + +// Convert screen space coordinates to view space. +float4 ScreenToView( float4 screen ) +{ + // Convert to normalized texture coordinates + float2 texCoord = screen.xy / ScreenDimensions; + + // Convert to clip space + float4 clip = float4( float2( texCoord.x, 1.0f - texCoord.y ) * 2.0f - 1.0f, screen.z, screen.w ); + + return ClipToView( clip ); +} \ No newline at end of file diff --git a/manul/shaders/manul/forward_plus/light.hlsli b/manul/shaders/manul/forward_plus/light.hlsli new file mode 100644 index 00000000..b5f6d8e0 --- /dev/null +++ b/manul/shaders/manul/forward_plus/light.hlsli @@ -0,0 +1,34 @@ +#ifndef FORWARD_PLUS_LIGHT_HLSLI +#define FORWARD_PLUS_LIGHT_HLSLI + +struct Light { + float3 m_origin; + float m_radius; + float3 m_direction; + float3 m_color; + float m_cos_inner; + float m_cos_outer; +}; + +struct PackedLight { + float3 m_origin; + float m_radius; + uint4 m_packed_data; +}; + +Light UnpackLight(in PackedLight packed_light) { + Light light; + light.m_origin = packed_light.m_origin; + light.m_radius = packed_light.m_radius; + light.m_direction.x = f16tof32(packed_light.m_packed_data[0]); + light.m_direction.y = f16tof32(packed_light.m_packed_data[0] >> 16); + light.m_direction.z = f16tof32(packed_light.m_packed_data[1]); + light.m_cos_inner = f16tof32(packed_light.m_packed_data[1] >> 16); + light.m_color.x = f16tof32(packed_light.m_packed_data[2]); + light.m_color.y = f16tof32(packed_light.m_packed_data[2] >> 16); + light.m_color.z = f16tof32(packed_light.m_packed_data[3]); + light.m_cos_outer = f16tof32(packed_light.m_packed_data[3] >> 16); + return light; +} + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/forward_plus/primitives.hlsli b/manul/shaders/manul/forward_plus/primitives.hlsli new file mode 100644 index 00000000..472c1629 --- /dev/null +++ b/manul/shaders/manul/forward_plus/primitives.hlsli @@ -0,0 +1,259 @@ +// Partially stolen from https://www.3dgep.com/forward-plus/ + +#ifndef FORWARD_PLUS_PRIMITIVES_HLSLI +#define FORWARD_PLUS_PRIMITIVES_HLSLI + +/* ---------------------------------------------------------------------------------------------- */ +/* Plane */ +/* ---------------------------------------------------------------------------------------------- */ + +struct Plane +{ + float3 N; // Plane normal. + float d; // Distance to origin. +}; + +// Compute a plane from 3 noncollinear points that form a triangle. +// This equation assumes a right-handed (counter-clockwise winding order) +// coordinate system to determine the direction of the plane normal. +Plane ComputePlane( float3 p0, float3 p1, float3 p2 ) +{ + Plane plane; + + float3 v0 = p1 - p0; + float3 v2 = p2 - p0; + + plane.N = normalize( cross( v0, v2 ) ); + + // Compute the distance to the origin using p0. + plane.d = dot( plane.N, p0 ); + + return plane; +} + +// Check to see if a point is fully behind (inside the negative halfspace of) a plane. +bool PointInsidePlane( float3 p, Plane plane ) +{ + return dot( plane.N, p ) - plane.d < 0; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* Frustum */ +/* ---------------------------------------------------------------------------------------------- */ + +struct ConeFrustum +{ + float3 m_origin; + float m_cos_angle; + float3 m_direction; + float m_sin_angle; + float m_cos_angle_sqr; + float m_inv_sin_angle; +}; + +ConeFrustum ComputeConeFrustum(in float3 origin, in float3 direction, in float cos_angle) { + ConeFrustum cone; + cone.m_origin = origin; + cone.m_direction = direction; + cone.m_cos_angle = cos_angle; + cone.m_cos_angle_sqr = cos_angle * cos_angle; + cone.m_sin_angle = sqrt(1. - saturate(cone.m_cos_angle_sqr)); + cone.m_inv_sin_angle = 1. / cone.m_sin_angle; + return cone; +} + +// Four planes of a view frustum (in view space). +// The planes are: +// * Left, +// * Right, +// * Top, +// * Bottom. +// The back and/or front planes can be computed from depth values in the +// light culling compute shader. +struct Frustum +{ + Plane planes[4]; // left, right, top, bottom frustum planes. + float3 m_view_ray; + float m_tan_frustum_angle; +}; + +/* ---------------------------------------------------------------------------------------------- */ +/* Sphere */ +/* ---------------------------------------------------------------------------------------------- */ + +struct Sphere +{ + float3 m_origin; // Center point. + float m_radius; // Radius. +}; + +// Check to see if a sphere is fully behind (inside the negative halfspace of) a plane. +// Source: Real-time collision detection, Christer Ericson (2005) +bool SphereInsidePlane( Sphere sphere, Plane plane ) +{ + return dot( plane.N, sphere.m_origin ) - plane.d < -sphere.m_radius; +} + +// Check to see of a light is partially contained within the frustum. +bool SphereInsideFrustum( Sphere sphere, Frustum frustum, float zNear, float zFar ) +{ + bool result = true; + + // First check depth + // Note: Here, the view vector points in the -Z axis so the + // far depth value will be approaching -infinity. + if ( sphere.m_origin.z - sphere.m_radius > zNear || sphere.m_origin.z + sphere.m_radius < zFar ) + { + result = false; + } + + // Then check frustum planes + for ( int i = 0; i < 4 && result; ++i ) + { + if ( SphereInsidePlane( sphere, frustum.planes[i] ) ) + { + result = false; + } + } + + return result; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* Cone */ +/* ---------------------------------------------------------------------------------------------- */ + +struct Cone +{ + float3 T; // Cone tip. + float h; // Height of the cone. + float3 d; // Direction of the cone. + float r; // bottom radius of the cone. +}; + +// Check to see if a cone if fully behind (inside the negative halfspace of) a plane. +// Source: Real-time collision detection, Christer Ericson (2005) +bool ConeInsidePlane( Cone cone, Plane plane ) +{ + // Compute the farthest point on the end of the cone to the positive space of the plane. + float3 m = cross( cross( plane.N, cone.d ), cone.d ); + float3 Q = cone.T + cone.d * cone.h - m * cone.r; + + // The cone is in the negative halfspace of the plane if both + // the tip of the cone and the farthest point on the end of the cone to the + // positive halfspace of the plane are both inside the negative halfspace + // of the plane. + return PointInsidePlane( cone.T, plane ) && PointInsidePlane( Q, plane ); +} + +bool ConeInsideFrustum( Cone cone, Frustum frustum, float zNear, float zFar ) +{ + bool result = true; + + Plane nearPlane = { float3( 0, 0, -1 ), -zNear }; + Plane farPlane = { float3( 0, 0, 1 ), zFar }; + + // First check the near and far clipping planes. + if ( ConeInsidePlane( cone, nearPlane ) || ConeInsidePlane( cone, farPlane ) ) + { + result = false; + } + + // Then check frustum planes + for ( int i = 0; i < 4 && result; ++i ) + { + if ( ConeInsidePlane( cone, frustum.planes[i] ) ) + { + result = false; + } + } + + return result; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* AABB */ +/* ---------------------------------------------------------------------------------------------- */ + +struct Aabb { + float3 min; + float3 max; +}; + +float3 GetAabbCorner( Aabb aabb, float4x4 transform, uint index ) { + float3 t = float3( + ( index & 1 ) ? 1. : 0., + ( index & 2 ) ? 1. : 0., + ( index & 4 ) ? 1. : 0. ); + return mul( + transform, + float4( ( 1. - t ) * aabb.min + t * aabb.max, 1. ) ).xyz; +} + +bool AabbInsidePlane( Aabb aabb, float4x4 transform, Plane plane ) { + bool result = true; + + for( uint i = 0; i < 8 && result; ++i ) { + result = PointInsidePlane( GetAabbCorner( aabb, transform, i ), plane ); + } + + return result; +} + +bool AabbInsideFrustum( Aabb aabb, float4x4 transform, Frustum frustum, float zNear, float zFar ) +{ + bool result = true; + + Plane nearPlane = { float3( 0, 0, -1 ), -zNear }; + Plane farPlane = { float3( 0, 0, 1 ), zFar }; + + // First check the near and far clipping planes. + if ( AabbInsidePlane( aabb, transform, nearPlane ) || AabbInsidePlane( aabb, transform, farPlane ) ) + { + result = false; + } + + // Then check frustum planes + for ( int i = 0; i < 4 && result; ++i ) + { + if ( AabbInsidePlane( aabb, transform, frustum.planes[i] ) ) + { + result = false; + } + } + + return result; +} + +bool DoQueryInfiniteCone(in Sphere sphere, in ConeFrustum cone) +{ + float3 U = cone.m_origin - (sphere.m_radius * cone.m_inv_sin_angle) * cone.m_direction; + float3 CmU = sphere.m_origin - U; + float AdCmU = dot(cone.m_direction, CmU); + if (AdCmU > 0.) + { + float sqrLengthCmU = dot(CmU, CmU); + if (AdCmU * AdCmU >= sqrLengthCmU * cone.m_cos_angle_sqr) + { + float3 CmV = sphere.m_origin - cone.m_origin; + float AdCmV = dot(cone.m_direction, CmV); + if (AdCmV < -sphere.m_radius) + { + return false; + } + + float rSinAngle = sphere.m_radius * cone.m_sin_angle; + if (AdCmV >= -rSinAngle) + { + return true; + } + + float sqrLengthCmV = dot(CmV, CmV); + return sqrLengthCmV <= sphere.m_radius * sphere.m_radius; + } + } + + return false; +} + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/gbuffer_contact_shadows.hlsli b/manul/shaders/manul/gbuffer_contact_shadows.hlsli new file mode 100644 index 00000000..e706ab41 --- /dev/null +++ b/manul/shaders/manul/gbuffer_contact_shadows.hlsli @@ -0,0 +1,10 @@ +#ifndef GBUFFER_CONTACT_SHADOWS_HLSLI +#define GBUFFER_CONTACT_SHADOWS_HLSLI +Texture2D g_ContactShadows : register(t12); + +float GetContactShadows(in uint2 pixel_position) { + return g_ContactShadows[pixel_position]; +} + +#endif + diff --git a/manul/shaders/manul/gbuffer_lights.hlsl b/manul/shaders/manul/gbuffer_lights.hlsl new file mode 100644 index 00000000..35e9b727 --- /dev/null +++ b/manul/shaders/manul/gbuffer_lights.hlsl @@ -0,0 +1,106 @@ +#include "math.hlsli" + +#include "lighting_functions.hlsli" + +#include "color_transform.hlsli" + +#include "manul/gbuffer_ssao.hlsli" + +struct ViewConstants { + float4x4 m_inverse_model_view; + float4x4 m_inverse_projection; +}; + +struct PushConstantsSpotLight { + float3 m_origin; + float m_radius_sqr; + float3 m_direction; + float m_cos_inner_cone; + float3 m_color; + float m_cos_outer_cone; +}; + +cbuffer g_ViewConst : register(b0) { ViewConstants g_View; } + +#ifdef SPIRV +[[vk::push_constant]] ConstantBuffer g_SpotLight; +#else +cbuffer g_SpotLightConst : register(b1) { PushConstantsSpotLight g_SpotLight; } +#endif + +RWTexture2D g_OutDiffuse : register(u0); + +Texture2D g_GbufferDepth : register(t0); +Texture2D g_GbufferNormal : register(t1); +Texture2D g_GbufferParams : register(t2); +Texture2D g_GbufferColor : register(t3); + +float2 PixelToCS(in float2 pixel, in float2 size) { + return ((pixel + .5) / size - .5) * float2(2., -2.); +} + +float3 ReconstructPos(in float2 cs, in float depth) { + float4 ndc = float4(cs, depth, 1.); + ndc = mul(g_View.m_inverse_projection, ndc); + return ndc.xyz / ndc.w; +} + +[numthreads(8, 8, 1)] +void CS_Clear(uint3 PixCoord : SV_DispatchThreadID, uint3 GroupID : SV_GroupID, uint GroupIndex : SV_GroupIndex) { + uint2 pixel = PixCoord.xy; + g_OutDiffuse[pixel].rgb = 0.; +} + +[numthreads(8, 8, 1)] +void CS_Spotlight(uint3 PixCoord : SV_DispatchThreadID, uint3 GroupID : SV_GroupID, uint GroupIndex : SV_GroupIndex) { + uint2 gbuffer_dimensions; + uint2 pixel = PixCoord.xy; + + g_OutDiffuse.GetDimensions(gbuffer_dimensions.x, gbuffer_dimensions.y); + float3 position = ReconstructPos(PixelToCS(PixCoord.xy, gbuffer_dimensions), g_GbufferDepth[pixel]); + float3 view = mul((float3x3)g_View.m_inverse_model_view, position); + + float3 L_offset = g_SpotLight.m_origin - view; + float3 L = normalize(L_offset); + if(dot(L_offset, L_offset) > g_SpotLight.m_radius_sqr) return; + + float3 albedo = g_GbufferColor[pixel]; + float4 params = g_GbufferParams[pixel]; + float3 normal = UnpackNormalXYZ(g_GbufferNormal[pixel]); + + SurfaceData surface_data; + surface_data.m_Albedo = albedo; + surface_data.m_Alpha = 1.; + surface_data.m_Metalness = params.r; + surface_data.m_Roughness = params.g; +#ifdef GBUFFER_SSAO_HLSLI + surface_data.m_DiffuseOcclusion = min(params.b, GetSSAO(pixel)); +#else + surface_data.m_DiffuseOcclusion = params.b; +#endif + surface_data.m_Normal = mul((float3x3)g_View.m_inverse_model_view, normal); + surface_data.m_View = -normalize(view); + surface_data.m_Reflect = reflect(-surface_data.m_View, surface_data.m_Normal); + surface_data.m_NdotV = max(dot(surface_data.m_Normal, surface_data.m_View), 0.); + surface_data.m_SpecularF0 = float3(.04, .04, .04) * params.a * 2.; + surface_data.m_SpecularF0 = lerp(surface_data.m_SpecularF0, surface_data.m_Albedo, surface_data.m_Metalness); + surface_data.m_SpecularF = FresnelSchlickRoughness(surface_data.m_NdotV, surface_data.m_SpecularF0, surface_data.m_Roughness); + surface_data.m_HorizonFading = 1.6; + surface_data.m_SpecularOcclusion = ComputeSpecOcclusion(surface_data.m_NdotV, surface_data.m_DiffuseOcclusion, surface_data.m_Roughness); + + float3 lit = float3(0., 0., 0.); + + DirectionalLight directionalLight; + directionalLight.m_LightVector = L; + directionalLight.m_Color = max(REC709_to_XYZ(g_SpotLight.m_color), 0.); + ApplyDirectionalLight(lit, directionalLight, surface_data, 1.); + + + float cone = saturate((dot(L, -g_SpotLight.m_direction) - g_SpotLight.m_cos_inner_cone) / (g_SpotLight.m_cos_outer_cone - g_SpotLight.m_cos_inner_cone)); + float attenuation = 1. / dot(L_offset, L_offset) * (1. - cone); + + g_OutDiffuse[pixel].rgb += lit * attenuation; +} + + + diff --git a/manul/shaders/manul/gbuffer_ssao.hlsli b/manul/shaders/manul/gbuffer_ssao.hlsli new file mode 100644 index 00000000..ad84a45b --- /dev/null +++ b/manul/shaders/manul/gbuffer_ssao.hlsli @@ -0,0 +1,27 @@ +#ifndef GBUFFER_SSAO_HLSLI +#define GBUFFER_SSAO_HLSLI +Texture2D g_SSAO : register(t5); + +float4 R8G8B8A8_UNORM_to_FLOAT4( uint packedInput ) +{ + float4 unpackedOutput; + unpackedOutput.x = (float)( packedInput & 0x000000ff ) / 255; + unpackedOutput.y = (float)( ( ( packedInput >> 8 ) & 0x000000ff ) ) / 255; + unpackedOutput.z = (float)( ( ( packedInput >> 16 ) & 0x000000ff ) ) / 255; + unpackedOutput.w = (float)( packedInput >> 24 ) / 255; + return unpackedOutput; +} + +void DecodeVisibilityBentNormal( const uint packedValue, out float visibility, out float3 bentNormal ) +{ + float4 decoded = R8G8B8A8_UNORM_to_FLOAT4( packedValue ); + bentNormal = decoded.xyz * 2.0.xxx - 1.0.xxx; // could normalize - don't want to since it's done so many times, better to do it at the final step only + visibility = decoded.w; +} + +float GetSSAO(in uint2 pixel_position) { + return g_SSAO[pixel_position]; +} + +#endif + diff --git a/manul/shaders/manul/geo_line.hlsl b/manul/shaders/manul/geo_line.hlsl new file mode 100644 index 00000000..e69de29b diff --git a/manul/shaders/manul/lighting.hlsli b/manul/shaders/manul/lighting.hlsli new file mode 100644 index 00000000..1e8db7ce --- /dev/null +++ b/manul/shaders/manul/lighting.hlsli @@ -0,0 +1,124 @@ +#ifndef LIGHTING_HLSLI +#define LIGHTING_HLSLI + +#include "math.hlsli" + +#include "material_common.hlsli" + +#include "view_data.hlsli" + +#include "lighting_functions.hlsli" + +#include "forward_plus/light.hlsli" + +SamplerState g_SamplerLinearClamp : register(s8); +TextureCube g_DiffuseEnvmap : register(t8); +TextureCube g_SpecularEnvmap : register(t9); +Texture2D g_BrdfLUT : register(t10); + +Texture2D g_LightGrid : register(t16); +StructuredBuffer g_LightIndexBuffer : register(t17); +StructuredBuffer g_LightBuffer : register(t18); + +#define LIGHTING_NEEDS_PIXELPOSITION 1 + +#if LIGHTING_NEEDS_PIXELPOSITION +void ApplyMaterialLighting(out float4 lit, in MaterialData material, in uint2 pixel_position); +#else +void ApplyMaterialLighting(out float4 lit, in MaterialData material); +#endif + +void ApplyIBL(inout float3 lit, in SurfaceData material); +void ApplyForwardPlus(inout float3 lit, in SurfaceData material, in float3 position, in uint2 pixel_position); + +#if LIGHTING_NEEDS_PIXELPOSITION +void ApplyMaterialLighting(out float4 lit, in MaterialData material, in uint2 pixel_position) +#else +void ApplyMaterialLighting(out float4 lit, in MaterialData material) +#endif +{ + // Camera-centric world position + float3 view = mul((float3x3)g_InverseModelView, material.m_Position); + + // Convert material to surface data + SurfaceData surface_data; + surface_data.m_Albedo = clamp(material.m_MaterialAlbedoAlpha.rgb, .02, .9); + surface_data.m_Alpha = material.m_MaterialAlbedoAlpha.a; + surface_data.m_Metalness = material.m_MaterialParams.r; + surface_data.m_Roughness = material.m_MaterialParams.g; +#ifdef GBUFFER_SSAO_HLSLI + surface_data.m_DiffuseOcclusion = min(material.m_MaterialParams.b, GetSSAO(pixel_position)); +#else + surface_data.m_DiffuseOcclusion = material.m_MaterialParams.b; +#endif + surface_data.m_Normal = mul((float3x3)g_InverseModelView, material.m_MaterialNormal); + surface_data.m_View = -normalize(view); + surface_data.m_Reflect = reflect(-surface_data.m_View, surface_data.m_Normal); + surface_data.m_NdotV = max(dot(surface_data.m_Normal, surface_data.m_View), 0.); + surface_data.m_SpecularF0 = float3(.04, .04, .04) * material.m_MaterialParams.a * 2.; + surface_data.m_SpecularF0 = lerp(surface_data.m_SpecularF0, surface_data.m_Albedo, surface_data.m_Metalness); + surface_data.m_SpecularF = FresnelSchlickRoughness(surface_data.m_NdotV, surface_data.m_SpecularF0, surface_data.m_Roughness); + surface_data.m_HorizonFading = 1.6; + surface_data.m_SpecularOcclusion = ComputeSpecOcclusion(surface_data.m_NdotV, surface_data.m_DiffuseOcclusion, surface_data.m_Roughness); + + lit.rgb = material.m_MaterialEmission * surface_data.m_Alpha; + lit.a = 1. - (saturate((1. - surface_data.m_Alpha) * lerp(1. - dot(surface_data.m_SpecularF, 1./3.), 0., surface_data.m_Metalness))); + + float shadow = 1.; +#ifdef SHADOW_HLSLI + shadow = GetShadow(view, material); +#endif + +#ifdef GBUFFER_CONTACT_SHADOWS_HLSLI + shadow = min(shadow, GetContactShadows(pixel_position)); +#endif + +// Apply IBL cubemap + ApplyIBL(lit.rgb, surface_data); + +// Apply directional light + DirectionalLight directionalLight; + directionalLight.m_LightVector = g_LightDir.xyz; + directionalLight.m_Color = g_LightColor.rgb; + ApplyDirectionalLight(lit.rgb, directionalLight, surface_data, shadow); + ApplyForwardPlus(lit.rgb, surface_data, view, pixel_position); +} + +void ApplyIBL(inout float3 lit, in SurfaceData material) { + float3 kS = material.m_SpecularF; + float3 kD = 1. - kS; + kD = lerp(kD, 0., material.m_Metalness); + + float2 brdf = g_BrdfLUT.SampleLevel(g_SamplerLinearClamp, float2(material.m_NdotV, material.m_Roughness), 0.); + + lit += material.m_Albedo * kD * g_DiffuseEnvmap.SampleLevel(g_SamplerLinearClamp, material.m_Normal, 0.) * material.m_DiffuseOcclusion * material.m_Alpha; + lit += g_SpecularEnvmap.SampleLevel(g_SamplerLinearClamp, material.m_Reflect, sqrt(material.m_Roughness) * 5.) * (material.m_SpecularF * brdf.xxx + brdf.yyy) * material.m_SpecularOcclusion * HorizonFading(material.m_NdotV, material.m_HorizonFading); +} + + +void ApplyForwardPlus(inout float3 lit, in SurfaceData material, in float3 position, in uint2 pixel_position) { + uint2 light_range = g_LightGrid[pixel_position / 8]; + for(int i = 0; i < light_range.y; ++i) { + Light light = UnpackLight(g_LightBuffer[g_LightIndexBuffer[light_range.x + i]]); + float3 L_offset = light.m_origin - position; + float L_dist_sqr = dot(L_offset, L_offset); + float3 L_dir = normalize(L_offset); + //lit += light.m_color; + if(L_dist_sqr > light.m_radius * light.m_radius) continue; + + float cos_point_deviation = dot(-L_dir, light.m_direction); + if(light.m_cos_outer > -1.5 && cos_point_deviation < light.m_cos_outer) continue; + + + float cone = light.m_cos_outer > -1.5 ? saturate((cos_point_deviation - light.m_cos_inner) / (light.m_cos_outer - light.m_cos_inner)) : 0.; + float attenuation = 1. / dot(L_offset, L_offset) * (1. - cone); + + DirectionalLight directionalLight; + directionalLight.m_LightVector = L_dir; + directionalLight.m_Color = light.m_color * attenuation; + + ApplyDirectionalLight(lit.rgb, directionalLight, material, 1.); + } +} + +#endif diff --git a/manul/shaders/manul/lighting_functions.hlsli b/manul/shaders/manul/lighting_functions.hlsli new file mode 100644 index 00000000..723e45f6 --- /dev/null +++ b/manul/shaders/manul/lighting_functions.hlsli @@ -0,0 +1,122 @@ +#ifndef LIGHTING_FUNCTIONS_HLSLI +#define LIGHTING_FUNCTIONS_HLSLI + +struct SurfaceData { + float3 m_Albedo; + float m_Alpha; + float m_Metalness; + float m_Roughness; + float m_DiffuseOcclusion; + float m_SpecularOcclusion; + float3 m_View; + float3 m_Normal; + float3 m_Reflect; + float m_NdotV; + float3 m_SpecularF0; + float3 m_SpecularF; + float m_HorizonFading; +}; + +struct DirectionalLight { + float3 m_LightVector; + float3 m_Color; +}; + +void ApplyDirectionalLight(inout float3 lit, in DirectionalLight light, in SurfaceData material, in float shadow); + +float3 FresnelSchlickRoughness(float cosTheta, float3 F0, float roughness); +float FresnelSchlickRoughness(float cosTheta, float F0, float roughness); +float DistributionGGX(float3 N, float3 H, float roughness); +float GeometrySchlickGGX(float NdotV, float roughness); +float GeometrySmith(float3 N, float3 V, float3 L, float roughness); +float ComputeSpecOcclusion(float NdotV, float AO, float roughness); +float HorizonFading(float NdotL, float horizonFade); + +void ApplyDirectionalLight(inout float3 lit, in DirectionalLight light, in SurfaceData material, in float shadow) { + float3 H = normalize(material.m_View + light.m_LightVector); + float3 radiance = light.m_Color; + + float NdotL = max(dot(material.m_Normal, light.m_LightVector), 0.); + + // Cook-Torrance BRDF + float NDF = DistributionGGX(material.m_Normal, H, material.m_Roughness); + float G = GeometrySmith(material.m_Normal, material.m_View, light.m_LightVector, material.m_Roughness); + + float3 numerator = NDF * G * material.m_SpecularF; + float denominator = 4. * material.m_NdotV * NdotL + 1e-4; // + 0.0001 to prevent divide by zero + float3 specular = numerator / denominator; + + // kS is equal to Fresnel + float3 kS = material.m_SpecularF; + // for energy conservation, the diffuse and specular light can't + // be above 1.0 (unless the surface emits light); to preserve this + // relationship the diffuse component (kD) should equal 1.0 - kS. + float3 kD = 1. - kS; + // multiply kD by the inverse metalness such that only non-metals + // have diffuse lighting, or a linear blend if partly metal (pure metals + // have no diffuse light). + kD = lerp(kD, 0., material.m_Metalness); + + + // add to outgoing radiance Lo + //lit += shadow * (kD * material.m_Albedo * ONE_OVER_PI * material.m_DiffuseOcclusion * material.m_Alpha + specular * material.m_SpecularOcclusion * HorizonFading(NdotL, material.m_HorizonFading)) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again + lit += shadow * (kD * material.m_Albedo * ONE_OVER_PI * material.m_Alpha + specular * HorizonFading(NdotL, material.m_HorizonFading)) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again +} + +float FresnelSchlickRoughness(float cosTheta, float F0, float roughness) +{ + return F0 + (max(1. - roughness, F0) - F0) * pow(saturate(1. - cosTheta), 5.); +} + +float3 FresnelSchlickRoughness(float cosTheta, float3 F0, float roughness) +{ + return F0 + (max((float3)(1. - roughness), F0) - F0) * pow(saturate(1. - cosTheta), 5.); +} + +float DistributionGGX(float3 N, float3 H, float roughness) +{ + float a = roughness*roughness; + float a2 = a*a; + float NdotH = max(dot(N, H), 0.); + float NdotH2 = NdotH*NdotH; + + float num = a2; + float denom = (NdotH2 * (a2 - 1.) + 1.); + denom = PI * denom * denom; + + return num / denom; +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.); + float k = (r*r) / 8.; + + float num = NdotV; + float denom = NdotV * (1. - k) + k; + + return num / denom; +} + +float GeometrySmith(float3 N, float3 V, float3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.); + float NdotL = max(dot(N, L), 0.); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +float ComputeSpecOcclusion(float NdotV, float AO, float roughness) +{ + return saturate(pow(NdotV + AO, exp2(-16. * roughness - 1.)) - 1. + AO); +} + +float HorizonFading(float NdotL, float horizonFade) +{ + float horiz = saturate(1.0 + horizonFade * NdotL); + return horiz * horiz; +} + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/line.hlsl b/manul/shaders/manul/line.hlsl new file mode 100644 index 00000000..94f8dcf4 --- /dev/null +++ b/manul/shaders/manul/line.hlsl @@ -0,0 +1,113 @@ +#include "color_transform.hlsli" + +struct VertexInput { + float3 m_Position : Position; + float3 m_Normal : Normal; + float2 m_TexCoord : TexCoord; + float4 m_Tangent : Tangent; +}; + +struct VertexOutput { + float3 m_Position : Position; +}; + +struct PixelInput { + float3 m_Position : Position; + float3 m_Normal : Normal; + float4 m_PositionSV : SV_Position; + float4 m_PositionCS : PositionCS; + float4 m_HistoryPositionCS : HistoryPositionCS; +}; + +struct PixelOutput { + float4 m_Color : SV_Target0; + float4 m_Emission : SV_Target1; + float4 m_Params : SV_Target2; + float4 m_Normal : SV_Target3; + float2 m_Motion : SV_Target4; +}; + +cbuffer VertexConstants : register(b0) { + float4x4 g_JitteredProjection; + float4x4 g_Projection; + float4x4 g_ProjectionHistory; +}; + +struct LinePushConstants { + float4x3 m_ModelView; + float4x3 m_ModelViewHistory; + float3 m_Color; + float m_LineWeight; + float m_Metalness; + float m_Roughness; +}; + +#ifdef SPIRV +[[vk::push_constant]] ConstantBuffer g_DrawConstants; +#else +cbuffer g_Const : register(b1) { LinePushConstants g_DrawConstants; } +#endif + +VertexOutput vtx_main(in VertexInput vs_in) { + VertexOutput result; + result.m_Position = vs_in.m_Position; + return result; +} + +#define NUM_LINE_VERTS 4 + +static const float3 g_VertexNormals[NUM_LINE_VERTS] = { + float3(-1., 0., 0.), + float3(0., 1., 0.), + float3(1., 0., 0.), + float3(0., -1., 0.) }; + +PixelInput ComputePoint(in float3 position, in float3 normal); + +[maxvertexcount(NUM_LINE_VERTS * 6)] +void geo_main(line VertexOutput gs_in[2], inout TriangleStream gs_out) { + float3 normal = normalize(gs_in[1].m_Position - gs_in[0].m_Position); + float3 up = normal.z < 0.99984769515 ? float3(0., 0., 1.) : float3(0., 1., 0.); + float3 tangent = normalize(cross(normal, up)); + up = cross(tangent, normal); + + float3x3 offset_matrix = float3x3(tangent, up, normal); + for(int i = 0; i < NUM_LINE_VERTS; ++i) { + + float3 normal = mul(g_VertexNormals[i], offset_matrix); + float3 next_normal = mul(g_VertexNormals[(i + 1) % NUM_LINE_VERTS], offset_matrix); + + float3 offset = normal * .5 * g_DrawConstants.m_LineWeight; + float3 next_offset = next_normal * .5 * g_DrawConstants.m_LineWeight; + + gs_out.Append(ComputePoint(gs_in[0].m_Position + offset, normal)); + gs_out.Append(ComputePoint(gs_in[0].m_Position + next_offset, next_normal)); + gs_out.Append(ComputePoint(gs_in[1].m_Position + offset, normal)); + gs_out.Append(ComputePoint(gs_in[1].m_Position + next_offset, next_normal)); + gs_out.RestartStrip(); + } +}; + +PixelOutput pix_main(in PixelInput ps_in) { + PixelOutput result; + result.m_Color.rgb = REC709_to_XYZ(g_DrawConstants.m_Color); + result.m_Color.a = 1.; + result.m_Emission = 0.; + result.m_Params = float4(g_DrawConstants.m_Metalness, g_DrawConstants.m_Roughness, 1., .5); + result.m_Normal.rgb = ps_in.m_Normal * .5 + .5; + result.m_Normal.a = 1.; + result.m_Motion = (ps_in.m_HistoryPositionCS.xy / ps_in.m_HistoryPositionCS.w) - (ps_in.m_PositionCS.xy / ps_in.m_PositionCS.w); + result.m_Motion = result.m_Motion * float2(.5, -.5); + return result; +} + +PixelInput ComputePoint(in float3 position, in float3 normal) { + PixelInput result; + result.m_Position = mul(float4(position, 1.), g_DrawConstants.m_ModelView).xyz; + result.m_Normal = mul(float4(normal, 0.), g_DrawConstants.m_ModelView).xyz; + result.m_PositionSV = mul(g_JitteredProjection, float4(result.m_Position, 1.)); + result.m_PositionCS = mul(g_Projection, float4(result.m_Position, 1.)); + float3 history_position = mul(float4(position, 1.), g_DrawConstants.m_ModelViewHistory); + result.m_HistoryPositionCS = mul(g_ProjectionHistory, float4(history_position, 1.)); + return result; +} diff --git a/manul/shaders/manul/macros.hlsli b/manul/shaders/manul/macros.hlsli new file mode 100644 index 00000000..16af0190 --- /dev/null +++ b/manul/shaders/manul/macros.hlsli @@ -0,0 +1 @@ +#define DECLARE_PUSH_CONSTANTS(sig, slot) \ No newline at end of file diff --git a/manul/shaders/manul/material.hlsli b/manul/shaders/manul/material.hlsli new file mode 100644 index 00000000..5cac890a --- /dev/null +++ b/manul/shaders/manul/material.hlsli @@ -0,0 +1,102 @@ +#ifndef MATERIAL_HLSLI +#define MATERIAL_HLSLI + +#define MOTION_VECTORS (1 << 0) +#define FORWARD_LIGHTING (1 << 1) + +#define PASS_GBUFFER (MOTION_VECTORS) +#define PASS_FORWARD (FORWARD_LIGHTING) +#define PASS_CUBEMAP (0) + +struct PixelOutput { +#if PASS & FORWARD_LIGHTING + float4 m_Color : SV_Target0; + float m_CompositionMask : SV_Target1; +#if PASS & MOTION_VECTORS + float2 m_Motion : SV_Target2; +#endif +#else + float4 m_Color : SV_Target0; + float4 m_Emission : SV_Target1; + float4 m_Params : SV_Target2; + float4 m_Normal : SV_Target3; +#if PASS & MOTION_VECTORS + float2 m_Motion : SV_Target4; +#endif +#endif +}; + +struct PixelInput { + float3 m_Position : Position; + float3 m_Normal : Normal; + float2 m_TexCoord : TexCoord; + float4 m_Tangent : Tangent; + float4 m_PositionSV : SV_Position; + float4 m_PositionCS : PositionCS; +#if PASS & MOTION_VECTORS + float4 m_HistoryPositionCS : HistoryPositionCS; +#endif +}; + +#include "draw_constants.hlsli" + +#include "material_common.hlsli" + +#if PASS & FORWARD_LIGHTING +#include "shadow.hlsli" +#include "lighting.hlsli" +#include "sky.hlsli" + +Texture2D g_GbufferDepth : register(t12); +#endif + +void MaterialPass(inout MaterialData material); + +PixelOutput main(in PixelInput ps_in) { + float2 uv = (ps_in.m_PositionCS.xy/ps_in.m_PositionCS.w) * float2(.5, -.5) + .5; + + MaterialData material; + material.m_Position = ps_in.m_Position; + material.m_Normal = ps_in.m_Normal; + material.m_Tangent = ps_in.m_Tangent.xyz; + material.m_Bitangent = ps_in.m_Tangent.w * cross(ps_in.m_Normal, ps_in.m_Tangent.xyz); + material.m_TexCoord = ps_in.m_TexCoord; + material.m_PixelCoord = ps_in.m_PositionSV.xy; + material.m_PositionNDC = ps_in.m_PositionCS / ps_in.m_PositionCS.w; + material.m_MaterialAlbedoAlpha = float4(1., 1., 1., 1.); + material.m_MaterialEmission = float3(0., 0., 0.); + material.m_MaterialParams = float4(0., .5, 1., .5); // Metalness.Roughness.Occlusion.Specular + material.m_MaterialNormal = float3(0., 0., 1.); + MaterialPass(material); + material.m_MaterialAlbedoAlpha.rgb = saturate(material.m_MaterialAlbedoAlpha.rgb); + material.m_MaterialEmission = max(material.m_MaterialEmission, 0.); + + PixelOutput ps_out; + +#if PASS & FORWARD_LIGHTING +#if LIGHTING_NEEDS_PIXELPOSITION + // Should not really happen? + ApplyMaterialLighting(ps_out.m_Color, material, (uint2)ps_in.m_PositionSV.xy); +#else + ApplyMaterialLighting(ps_out.m_Color, material); +#endif + float3 view = mul((float3x3)g_InverseModelView, material.m_Position); + ApplyAerialPerspective(ps_out.m_Color.rgb, ps_out.m_Color.a, normalize(view), g_LightDir, length(material.m_Position)/2500.); + //ps_out.m_Color.rgb += CalcAtmosphere(normalize(view), g_LightDir, g_Altitude, length(view)) * material.m_MaterialAlbedoAlpha.a; + ps_out.m_CompositionMask = ps_out.m_Color.a > 0.; +#else + ps_out.m_Color.rgb = material.m_MaterialAlbedoAlpha.rgb; + ps_out.m_Emission.rgb = material.m_MaterialEmission; + ps_out.m_Params = material.m_MaterialParams; + ps_out.m_Normal.rgb = material.m_MaterialNormal * .5 + .5; +#endif + +#if PASS & MOTION_VECTORS + ps_out.m_Motion = (ps_in.m_HistoryPositionCS.xy / ps_in.m_HistoryPositionCS.w) - (ps_in.m_PositionCS.xy / ps_in.m_PositionCS.w); + ps_out.m_Motion = ps_out.m_Motion * float2(.5, -.5); +#endif + + return ps_out; +} + +#endif diff --git a/manul/shaders/manul/material_common.hlsli b/manul/shaders/manul/material_common.hlsli new file mode 100644 index 00000000..3784d0c3 --- /dev/null +++ b/manul/shaders/manul/material_common.hlsli @@ -0,0 +1,20 @@ +#ifndef MATERIAL_COMMON_HLSLI +#define MATERIAL_COMMON_HLSLI + +struct MaterialData { + float3 m_Position; + float3 m_PositionDDX; + float3 m_PositionDDY; + float3 m_Tangent; + float3 m_Bitangent; + float3 m_Normal; + float2 m_TexCoord; + uint2 m_PixelCoord; + float4 m_PositionNDC; + float4 m_MaterialAlbedoAlpha; + float3 m_MaterialEmission; + float4 m_MaterialParams; // Metalness.Roughness.Occlusion.Specular + float3 m_MaterialNormal; +}; + +#endif diff --git a/manul/shaders/manul/math.hlsli b/manul/shaders/manul/math.hlsli new file mode 100644 index 00000000..eaacdcd0 --- /dev/null +++ b/manul/shaders/manul/math.hlsli @@ -0,0 +1,28 @@ +#ifndef MATH_HLSLI +#define MATH_HLSLI + +#define PI 3.1415926535897932384626433832795 +#define TWO_PI 6.283185307179586476925286766559 +#define PI_OVER_TWO 1.5707963267948966192313216916398 +#define PI_OVER_THREE 1.0471975511965977461542144610932 +#define PI_OVER_SIX 0.52359877559829887307710723054658 +#define ONE_OVER_PI 0.31830988618379067153776752674503 +#define TWO_OVER_PI 0.63661977236758134307553505349006 +#define ONE_OVER_TWO_PI 0.15915494309189533576888376337251 + +float3 UnpackNormalXY(in float2 normal) { + float3 result; + result.xy = 2. * (normal - .5); + result.z = sqrt(saturate(1. - dot(result.xy, result.xy))); + return result; +} + +float3 UnpackNormalXYZ(in float3 normal) { + return normalize(normal - .5); +} + +float InvLerp(float a, float b, float v) { + return (v - a) / (b - a); +} + +#endif diff --git a/manul/shaders/manul/shadow.hlsli b/manul/shaders/manul/shadow.hlsli new file mode 100644 index 00000000..4f11e85e --- /dev/null +++ b/manul/shaders/manul/shadow.hlsli @@ -0,0 +1,87 @@ +#ifndef SHADOW_HLSLI +#define SHADOW_HLSLI + +#include "view_data.hlsli" +#include "material_common.hlsli" + +#define CASCADE_COUNT 8 + +cbuffer ShadowProjections : register(b11) { + float4x4 g_CascadeProjections[CASCADE_COUNT]; + float4 g_CascadePlanes[(CASCADE_COUNT + 3) / 4]; +} + +SamplerComparisonState g_ShadowSampler : register(s11); +Texture2DArray g_ShadowMap : register(t11); + +float3 PosToShadow(in float4x4 cascade, in float3 pos) { + float4 shadow_view = mul(cascade, float4(pos, 1.)); + shadow_view /= shadow_view.w; + shadow_view.xy = shadow_view.xy * float2(.5, -.5) + .5; + return shadow_view.xyz; +} + +bool CalculateShadowView(in float4x4 cascade, in float plane, in MaterialData data, out float3 shadow_view, out float3 shadow_view_ddx, out float3 shadow_view_ddy) { +#ifdef DEFERRED_LIGHTING_PASS + if(dot(data.m_Position, data.m_Position) > plane) return false; + shadow_view = PosToShadow(cascade, data.m_Position); + shadow_view_ddx = PosToShadow(cascade, data.m_PositionDDX) - shadow_view; + shadow_view_ddy = PosToShadow(cascade, data.m_PositionDDY) - shadow_view; + return true; +#else + if(dot(data.m_Position, data.m_Position) > plane) return false; + shadow_view = PosToShadow(cascade, data.m_Position); + //if(any(shadow_view <= 0. || shadow_view >= 1.)) return false; + //shadow_view_ddx = PosToShadow(cascade, data.m_Position + ddx(data.m_Position)) - shadow_view; + //shadow_view_ddy = PosToShadow(cascade, data.m_Position + ddy(data.m_Position)) - shadow_view; + return true; +#endif +} + +float GetShadow(in float3 view, in MaterialData data) { + float shadow = 1.; + uint3 shadowmap_size; + g_ShadowMap.GetDimensions(shadowmap_size.x, shadowmap_size.y, shadowmap_size.z); + for(int i = 0; i < shadowmap_size.z; ++i) { + float3 shadowView, vShadowTexDDX, vShadowTexDDY; + if(CalculateShadowView(mul(g_CascadeProjections[i], g_InverseModelView), g_CascadePlanes[i / 4][i % 4], data, shadowView, vShadowTexDDX, vShadowTexDDY)) { + shadow = 0.; +#ifdef DEFERRED_LIGHTING_PASS + float2x2 screenToShadow = float2x2(vShadowTexDDX.xy, vShadowTexDDY.xy); + float fDeterminant = determinant(screenToShadow); + float fInvDeterminant = 1.0f / fDeterminant; + + float2x2 shadowToScreen = float2x2 ( + screenToShadow._22 * fInvDeterminant, + screenToShadow._12 * -fInvDeterminant, + screenToShadow._21 * -fInvDeterminant, + screenToShadow._11 * fInvDeterminant ); + float2 vRightTexelDepthRatio = mul( float2(1./shadowmap_size.x, 0.), + shadowToScreen ); + float2 vUpTexelDepthRatio = mul( float2(0., 1./shadowmap_size.y), + shadowToScreen ); + float fUpTexelDepthDelta = + vUpTexelDepthRatio.x * vShadowTexDDX.z + + vUpTexelDepthRatio.y * vShadowTexDDY.z; + float fRightTexelDepthDelta = + vRightTexelDepthRatio.x * vShadowTexDDX.z + + vRightTexelDepthRatio.y * vShadowTexDDY.z; +#endif + [unroll] + for (int y = -1; y <= 1; ++y) { + [unroll] + for (int x = -1; x <= 1; ++x) { +#ifdef DEFERRED_LIGHTING_PASS + shadow += g_ShadowMap.SampleCmpLevelZero(g_ShadowSampler, float3(shadowView.xy, float(i)), shadowView.z + x * fRightTexelDepthDelta + y * fUpTexelDepthDelta, int2(x, y)) / 9.; +#else + shadow += g_ShadowMap.SampleCmpLevelZero(g_ShadowSampler, float3(shadowView.xy, float(i)), shadowView.z, int2(x, y)) / 9.; +#endif + } + } + break; + } + } + return shadow; +} + +#endif diff --git a/manul/shaders/manul/sky copy.hlsli b/manul/shaders/manul/sky copy.hlsli new file mode 100644 index 00000000..ad4f8d0e --- /dev/null +++ b/manul/shaders/manul/sky copy.hlsli @@ -0,0 +1,429 @@ +#ifndef SKY_HLSLI +#define SKY_HLSLI + +#include "math.hlsli" + +#define SKY_INF 1.#INF + +static const float3x3 CAS2RGB = float3x3( + 1.6218, -0.4493, 0.0325, + -0.0374, 1.0598, -0.0742, + -0.0283, -0.1119, 1.0491); + +static const float3x3 RGB2CAS = float3x3( + 6.2267e-1, 2.6392e-1, -6.2375e-4, + 2.3324e-2, 9.6056e-1, 6.7215e-2, + 1.9285e-2, 1.0958e-1, 9.6035e-1); + +struct Atmosphere +{ + float3 sunDirection; + float atmosphereRadius; + float earthRadius; + float Hr; + float Hm; + float3 betaR; + float3 betaM; + float3 irradiance; +}; + +void swap(inout float a, inout float b) +{ + float temp = a; + a = b; + b = temp; +} + +bool solveQuadratic(in float a, in float b, in float c, out float x1, out float x2) +{ + if (b == 0) + { + // Handle special case where the the two vector ray.dir and V are perpendicular + // with V = ray.orig - sphere.centre + if (a == 0) + return false; + x1 = 0; + x2 = sqrt(-c / a); + return true; + } + float discr = b * b - 4 * a * c; + + if (discr < 0) + return false; + + float q = (b < 0.f) ? -0.5f * (b - sqrt(discr)) : -0.5f * (b + sqrt(discr)); + x1 = q / a; + x2 = c / q; + + return true; +} + +bool solveQuadratic(in float a, in float b, in float c) +{ + if (b == 0) + { + // Handle special case where the the two vector ray.dir and V are perpendicular + // with V = ray.orig - sphere.centre + return a != 0; + } + + return b * b - 4 * a * c >= 0; +} + +bool RaySphereIntersect(in float3 orig, in float3 dir, in float radius, out float t0, out float t1) +{ + // They ray dir is normalized so A = 1 + float A = dot(dir, dir); + float B = 2 * dot(dir, orig); + float C = dot(orig, orig) - radius * radius; + + if (!solveQuadratic(A, B, C, t0, t1)) + return false; + + if (t0 > t1) + swap(t0, t1); + + return true; +} + +bool RaySphereIntersect(in float3 orig, in float3 dir, in float radius) +{ + // They ray dir is normalized so A = 1 + float A = dot(dir, dir); + float B = 2 * dot(dir, orig); + float C = dot(orig, orig) - radius * radius; + + return solveQuadratic(A, B, C); +} + +float2x3 ComputeIncidentLight(in Atmosphere atmosphere, in float3 orig, in float3 dir, float tmin, float tmax) +{ + float t0, t1; + if (!RaySphereIntersect(orig, dir, atmosphere.atmosphereRadius, t0, t1) || t1 < 0) + return 0; + if (t0 > tmin && t0 > 0) + tmin = t0; + if (t1 < tmax) + tmax = t1; + uint numSamples = 16; + uint numSamplesLight = 8; + float segmentLength = (tmax - tmin) / numSamples; + float tCurrent = tmin; + float3 sumR = 0.; + float3 sumM = 0.; //mie and rayleigh contribution + float opticalDepthR = 0, opticalDepthM = 0; + float mu = dot(dir, atmosphere.sunDirection); //mu in the paper which is the cosine of the angle between the sun direction and the ray direction + float phaseR = 3.f / (16.f * PI) * (1 + mu * mu); + float g = 0.76f; + float phaseM = 3.f / (8.f * PI) * ((1.f - g * g) * (1.f + mu * mu)) / ((2.f + g * g) * pow(1.f + g * g - 2.f * g * mu, 1.5f)); + for (uint i = 0; i < numSamples; ++i) + { + float3 samplePosition = orig + (tCurrent + segmentLength * 0.5f) * dir; + float height = length(samplePosition) - atmosphere.earthRadius; + // compute optical depth for light + float hr = exp(-height / atmosphere.Hr) * segmentLength; + float hm = exp(-height / atmosphere.Hm) * segmentLength; + opticalDepthR += hr; + opticalDepthM += hm; + // light optical depth + float t0Light, t1Light; + RaySphereIntersect(samplePosition, atmosphere.sunDirection, atmosphere.atmosphereRadius, t0Light, t1Light); + float segmentLengthLight = t1Light / numSamplesLight, tCurrentLight = 0; + float opticalDepthLightR = 0, opticalDepthLightM = 0; + uint j; + for (j = 0; j < numSamplesLight; ++j) + { + float3 samplePositionLight = samplePosition + (tCurrentLight + segmentLengthLight * 0.5f) * atmosphere.sunDirection; + float heightLight = length(samplePositionLight) - atmosphere.earthRadius; + if (heightLight < 0) + break; + opticalDepthLightR += exp(-heightLight / atmosphere.Hr) * segmentLengthLight; + opticalDepthLightM += exp(-heightLight / atmosphere.Hm) * segmentLengthLight; + tCurrentLight += segmentLengthLight; + } + if (j == numSamplesLight) + { + float3 tau = atmosphere.betaR * (opticalDepthR + opticalDepthLightR) + atmosphere.betaM * 1.1f * (opticalDepthM + opticalDepthLightM); + float3 attenuation = exp(-tau); + sumR += attenuation * hr; + sumM += attenuation * hm; + } + tCurrent += segmentLength; + } + + // We use a magic number here for the intensity of the sun (20). We will make it more + // scientific in a future revision of this lesson/code + float3 tau = atmosphere.betaR * opticalDepthR + atmosphere.betaM * 1.1f * opticalDepthM; + return float2x3((sumR * atmosphere.betaR * phaseR + sumM * atmosphere.betaM * phaseM) * 20, exp(-tau)); +} + +//#define MIE_G 0.76 +//#define SQR_G (MIE_G * MIE_G) +//#define RAYLEIGH_SCALE 8e3 /* Rayleigh scale height (m). */ +//#define MIE_SCALE 1.2e3 /* Mie scale height (m). */ + +//float phase_rayleigh(float mu) +//{ + //return 3.0f / (16.0f * PI) * (1.0f + mu * mu); +//} + +//float phase_mie(float mu) +//{ + //return (3.0f * (1.0f - SQR_G) * (1.0f + mu * mu)) / + //(8.0f * PI * (2.0f + SQR_G) * pow((1.0f + SQR_G - 2.0f * MIE_G * mu), 1.5)); +//} + +//float density_rayleigh(float height) +//{ + //return exp(-height / RAYLEIGH_SCALE); +//} + +//float density_mie(float height) +//{ + //return exp(-height / MIE_SCALE); +//} + +//float density_ozone(float height) +//{ + //float den = 0.0f; + //if (height >= 10000.0f && height < 25000.0f) + //{ + //den = 1.0f / 15000.0f * height - 2.0f / 3.0f; + //} + //else if (height >= 25000 && height < 40000) + //{ + //den = -(1.0f / 15000.0f * height - 8.0f / 3.0f); + //} + //return den; +//} + +///* Parameters for optical depth quadrature. + //* See the comment in ray_optical_depth for more detail. + //* Computed using sympy and following Python code: + //* # from sympy.integrals.quadrature import gauss_laguerre + //* # from sympy import exp + //* # x, w = gauss_laguerre(8, 50) + //* # xend = 25 + //* # print([(xi / xend).evalf(10) for xi in x]) + //* # print([(wi * exp(xi) / xend).evalf(10) for xi, wi in zip(x, w)]) + //*/ +//static const int quadrature_steps = 8; +//static const float quadrature_nodes[] = +//{ + //0.006811185292f, + //0.03614807107f, + //0.09004346519f, + //0.1706680068f, + //0.2818362161f, + //0.4303406404f, + //0.6296271457f, + //0.9145252695f +//}; +//static const float quadrature_weights[] = +//{ + //0.01750893642f, + //0.04135477391f, + //0.06678839063f, + //0.09507698807f, + //0.1283416365f, + //0.1707430204f, + //0.2327233347f, + //0.3562490486f +//}; + +//float3 ray_optical_depth(in Atmosphere atmosphere, float3 ray_origin) +//{ + ///* This function computes the optical depth along a ray. + //* Instead of using classic ray marching, the code is based on Gauss-Laguerre quadrature, + //* which is designed to compute the integral of f(x)*exp(-x) from 0 to infinity. + //* This works well here, since the optical depth along the ray tends to decrease exponentially. + //* By setting f(x) = g(x) exp(x), the exponentials cancel out and we get the integral of g(x). + //* The nodes and weights used here are the standard n=6 Gauss-Laguerre values, except that + //* the exp(x) scaling factor is already included in the weights. + //* The parametrization along the ray is scaled so that the last quadrature node is still within + //* the atmosphere. */ + //float t0, t1; + //float3 ray_dir = atmosphere.sunDirection; + //RaySphereIntersect(ray_origin, ray_dir, atmosphere.atmosphereRadius, t0, t1); + //float3 ray_end = ray_dir * t1; + //float ray_length = distance(ray_origin, ray_end); + + //float3 segment = ray_length * ray_dir; + + ///* instead of tracking the transmission spectrum across all wavelengths directly, + //* we use the fact that the density always has the same spectrum for each type of + //* scattering, so we split the density into a constant spectrum and a factor and + //* only track the factors */ + //float3 optical_depth = float3(0.0f, 0.0f, 0.0f); + + //for (int i = 0; i < quadrature_steps; i++) + //{ + //float3 P = ray_origin + quadrature_nodes[i] * segment; + + ///* height above sea level */ + //float height = length(P) - atmosphere.earthRadius; + + //float3 density =float3( + //density_rayleigh(height), density_mie(height), density_ozone(height)); + //optical_depth += density * quadrature_weights[i]; + //} + + //return optical_depth * ray_length; +//} + +//float3 ComputeIncidentLight(in Atmosphere atmosphere, in float3 orig, in float3 dir, float tmin, float tmax) +//{ + //float t0, t1; + //if (!RaySphereIntersect(orig, dir, atmosphere.atmosphereRadius, t0, t1) || t1 < 0) + //return 0; + //if (t0 > tmin && t0 > 0) + //tmin = t0; + //if (t1 < tmax) + //tmax = t1; + //uint numSamples = 16; + //uint numSamplesLight = 8; + //float segmentLength = (tmax - tmin) / numSamples; + //float tCurrent = tmin; + //float mu = dot(dir, atmosphere.sunDirection); //mu in the paper which is the cosine of the angle between the sun direction and the ray direction + //float3 opticalDepth = float3(0., 0., 0.); + //float3 densityScale = float3(1., 1., 1.); + //float3 phaseFunction = float3(phase_rayleigh(mu), phase_mie(mu), 0.0f); + //float3 spectrum = float3(0., 0., 0.); + //for (uint i = 0; i < numSamples; ++i) + //{ + //float3 samplePosition = orig + (tCurrent + segmentLength * 0.5f) * dir; + //float height = length(samplePosition) - atmosphere.earthRadius; + //float3 density = densityScale * float3(density_rayleigh(height), + //density_mie(height), + //density_ozone(height)); + //opticalDepth += segmentLength * density; + + ////if (!RaySphereIntersect(samplePosition, atmosphere.sunDirection, atmosphere.earthRadius)) + ////{ + //float3 light_optical_depth = densityScale * ray_optical_depth(atmosphere, samplePosition); + //float3 total_optical_depth = opticalDepth + light_optical_depth; + + //for (int wl = 0; wl < 3; ++wl) + //{ + //float3 extinction_density = total_optical_depth * float3(atmosphere.betaR[wl], + //1.11f * atmosphere.betaM[wl], + //0. /*ozone_coeff[wl]*/); + //float attenuation = exp(-dot(extinction_density, float3(1., 1., 1.))); + + //float3 scattering_density = density * float3(atmosphere.betaR[wl], atmosphere.betaM[wl], 0.0f); + + ///* the total inscattered radiance from one segment is: + //* Tr(A<->B) * Tr(B<->C) * sigma_s * phase * L * segment_length + //* + //* These terms are: + //* Tr(A<->B): Transmission from start to scattering position (tracked in optical_depth) + //* Tr(B<->C): Transmission from scattering position to light (computed in + //* ray_optical_depth) sigma_s: Scattering density phase: Phase function of the scattering + //* type (Rayleigh or Mie) L: Radiance coming from the light source segment_length: The + //* length of the segment + //* + //* The code here is just that, with a bit of additional optimization to not store full + //* spectra for the optical depth + //*/ + //spectrum[wl] += attenuation * dot(phaseFunction * scattering_density, float3(1., 1., 1.)) * + //atmosphere.irradiance[wl] * 7. * segmentLength; + //} + + ////} + + //// compute optical depth for light + ////float hr = exp(-height / atmosphere.Hr) * segmentLength; + ////float hm = exp(-height / atmosphere.Hm) * segmentLength; + ////opticalDepthR += hr; + ////opticalDepthM += hm; + ////// light optical depth + ////float t0Light, t1Light; + ////RaySphereIntersect(samplePosition, atmosphere.sunDirection, atmosphere.atmosphereRadius, t0Light, t1Light); + ////float segmentLengthLight = t1Light / numSamplesLight, tCurrentLight = 0; + ////float opticalDepthLightR = 0, opticalDepthLightM = 0; + ////uint j; + ////for (j = 0; j < numSamplesLight; ++j) + ////{ + //// float3 samplePositionLight = samplePosition + (tCurrentLight + segmentLengthLight * 0.5f) * atmosphere.sunDirection; + //// float heightLight = length(samplePositionLight) - atmosphere.earthRadius; + //// if (heightLight < 0) + //// break; + //// opticalDepthLightR += exp(-heightLight / atmosphere.Hr) * segmentLengthLight; + //// opticalDepthLightM += exp(-heightLight / atmosphere.Hm) * segmentLengthLight; + //// tCurrentLight += segmentLengthLight; + ////} + ////if (j == numSamplesLight) + ////{ + //// float3 tau = atmosphere.betaR * (opticalDepthR + opticalDepthLightR) + atmosphere.betaM * 1.1f * (opticalDepthM + opticalDepthLightM); + //// float3 attenuation = float3(exp(-tau.x), exp(-tau.y), exp(-tau.z)); + //// sumR += attenuation * hr; + //// sumM += attenuation * hm; + ////} + //tCurrent += segmentLength; + //} + //return spectrum; + //// We use a magic number here for the intensity of the sun (20). We will make it more + //// scientific in a future revision of this lesson/code + ////return (sumR * atmosphere.betaR * phaseR + sumM * atmosphere.betaM * phaseM) * 20; +//} + +void CalcAtmosphere(inout float3 color, in float3 viewDir, in float3 sunDir, in float altitude, in float far, in float3 sunColor) +{ + //float3x3 RGB2CAS = float3x3(1., 0., 0., 0., 1., 0., 0., 0., 1.); + //float3x3 CAS2RGB = float3x3(1., 0., 0., 0., 1., 0., 0., 0., 1.); + Atmosphere atmosphere; + atmosphere.earthRadius = 6360.e3; + atmosphere.atmosphereRadius = 6420.e3; + atmosphere.Hr = 7994.; + atmosphere.Hm = 1200.; + atmosphere.betaR = float3(5.8e-6, 13.5e-6, 33.1e-6); + atmosphere.betaR = float3(7.2865e-6, 1.2863e-5, 2.7408e-5); + //atmosphere.betaR = float3(8.658882115850087e-6, 1.5119838839850446e-5, 3.15880085046994e-5); + atmosphere.betaM = (float3) 21.e-6; + //atmosphere.irradiance = float3(1.4662835232339013, 1.7557753193321481, 1.7149614362178376); + + if (isinf(far)) + { + color = 1.e-7; + } + else + { + color = mul(RGB2CAS, color); + } + + atmosphere.sunDirection = sunDir; + float2x3 atm = ComputeIncidentLight(atmosphere, float3(0., atmosphere.earthRadius + altitude, 0.), viewDir, 0., far); + color = atm[0] + color * atm[1]; + + color = mul(CAS2RGB, color); + //light = light.x * float3(0.933600, 0.358317, 0.000000) + light.y * float3(0.413697, 0.910408, 0.003489) + light.z * float3(0.178639, 0.025820, 0.983576); + //light = light.x * float3(0.899398, 0.437131, 0.000009) + light.y * float3(0.256233, 0.966471, 0.016686) + light.z * float3(0.175352, 0.029461, 0.984065); + //light = max(0., light.x * float3(3.240970, -0.969244, 0.055630) + light.y * float3(-1.537383, 1.875968, -0.203977) + light.z * float3(-0.498611, 0.041555, 1.056972)); + //return max(0., mul(float3x3( + // 1.6218, -0.4493, 0.0325, + //-0.0374, 1.0598, -0.0742, + //-0.0283, -0.1119, 1.0491), light)); + //float3x3 transform = float3x3(1.6218, -.4493, .0325, -.0374, 1.0598, -.0742, -.0283, -.0119, 1.0491); + //return mul(light, transform); + //return max(0., light.x * float3(2.512071, -0.057897, -0.043816) + light.y * float3(-0.622965, 1.469301, -0.155101) + light.z * float3(0.063069, -0.143869, 2.035035)); + //return light; + //return (light.x * float3(1., 0., 0.) + light.y * float3(0.64, 1., 0.) + light.z * float3(0., 0., 1.)) / 1.64; + //return (light.x * float3(0.12544465, 0., 0.) + light.y * float3(0., 1.4467391, 0.) + //+ light.z * float3(0.22230228, 0., 1.86127603)); + //return float3(dot(light, float3(0.64, + //0.33, + //0.03)), dot(light, float3(0.30, + //0.60, + //0.10)), + //dot(light, float3(0.15, + //0.06, + //0.79))); + +} + +void Sky(inout float3 color, in float3 viewDir, in float3 sunDir, in float altitude) +{ + CalcAtmosphere(color, viewDir, sunDir, altitude, 1.#INF, 1.); +} + +#endif diff --git a/manul/shaders/manul/sky.hlsli b/manul/shaders/manul/sky.hlsli new file mode 100644 index 00000000..bbc2eb0a --- /dev/null +++ b/manul/shaders/manul/sky.hlsli @@ -0,0 +1,107 @@ +#ifndef SKY_HLSLI +#define SKY_HLSLI + +#include "math.hlsli" + +#include "sky_common.hlsli" + +Texture2D g_Sky : register(t13); +Texture2DArray g_AerialPerspectiveLut : register(t14); +SamplerState g_SkySampler : register(s13); + +#define SKY_INF 1. #INF + +float3 CalcSphereNormal(in float3 viewDir, in float3 sphereDir, in float cosAngularSize); + +void CalcSun(inout float3 color, in float3 viewDir, in float3 sunDir, in float altitude) +{ + float3 ray_origin = float3(0.0, EARTH_RADIUS + max(altitude, 1.), 0.0); + + float ground_dist = ray_sphere_intersection(ray_origin, viewDir, EARTH_RADIUS); + if (ground_dist < 0.0) + { + if (dot(viewDir, sunDir) > 0.99998869014) + { + color = linear_srgb_from_spectral_samples(sun_spectral_irradiance); + } + } +} + +void CalcMoon(inout float3 color, in float3 viewDir, in float3 moonDir, in float3 sunDir, in float altitude) +{ + float3 ray_origin = float3(0.0, EARTH_RADIUS + max(altitude, 1.), 0.0); + + float ground_dist = ray_sphere_intersection(ray_origin, viewDir, EARTH_RADIUS); + if (ground_dist < 0.0) + { + float3 normal = CalcSphereNormal(viewDir, moonDir, 0.99998869014); + if (dot(normal, normal) > 0.) + { + color = .07 * max(dot(normal, sunDir), 0.) * linear_srgb_from_spectral_samples(sun_spectral_irradiance); + } + } +} + +float3 CalcSphereNormal(in float3 viewDir, in float3 sphereDir, in float cosAngularSize) +{ + float sqrSinAngularSize = 1. - cosAngularSize * cosAngularSize; + float tca = dot(sphereDir, viewDir); + float d2 = dot(sphereDir, sphereDir) - tca * tca; + if (d2 > sqrSinAngularSize) + return (float3)(0.); + float thc = sqrt(sqrSinAngularSize - d2); + float t = tca - thc; + + if (t < 0.) + return (float3)(0.); + + float3 p = viewDir * t; + return normalize(p - sphereDir); +} + +float2 CalcEquirectangularCoords(in float3 viewDir, in float3 sunDir, in float2 size) +{ + float2 uv_scale = float2(1., 1. - 1. / size.y); + + // float lon = acos(clamp(dot(normalize(sunDir.xz), normalize(viewDir.xz)), -1., 1.)); + float lon = atan2(-viewDir.z, -viewDir.x); + float lat = asin(viewDir.y); + + return float2(InvLerp(-PI, PI, lon), InvLerp(PI_OVER_TWO, -PI_OVER_TWO, lat)) * uv_scale; +} + +float2 CalcEquirectangularCoordsBottomClipped(in float3 viewDir, in float3 sunDir, in float2 size) +{ + float2 uv_scale = float2(1., 1. - 1. / size.y); + + // float lon = acos(clamp(dot(normalize(sunDir.xz), normalize(viewDir.xz)), -1., 1.)); + float lon = atan2(-viewDir.z, -viewDir.x); + float lat = asin(viewDir.y); + + return float2(InvLerp(-PI, PI, lon), InvLerp(PI_OVER_TWO, -.125 * PI_OVER_TWO, lat)) * uv_scale; +} + +void CalcAtmosphere(inout float3 color, in float3 viewDir, in float3 sunDir) +{ + uint2 size; + g_Sky.GetDimensions(size.x, size.y); + + float4 sky = g_Sky.SampleLevel(g_SkySampler, CalcEquirectangularCoordsBottomClipped(viewDir, sunDir, size), 0.); + color = color * sky.a + sky.rgb; +} + +void ApplyAerialPerspective(inout float3 color, in float alpha, in float3 viewDir, in float3 sunDir, in float depth) +{ + uint3 size; + g_AerialPerspectiveLut.GetDimensions(size.x, size.y, size.z); + float slice = sqrt(depth) * size.z; + float slice_factor = frac(slice); + slice -= 1.; + float2 uv = CalcEquirectangularCoords(viewDir, sunDir, size.xy); + float4 sample_near = slice > 0. ? g_AerialPerspectiveLut.SampleLevel(g_SkySampler, float3(uv, floor(slice)), 0.) : float4(0., 0., 0., 1.); + float4 sample_far = g_AerialPerspectiveLut.SampleLevel(g_SkySampler, float3(uv, ceil(slice)), 0.); + float4 aerial = lerp(sample_near, sample_far, slice_factor); + color = color * aerial.a + aerial.rgb * alpha; +} + +#endif diff --git a/manul/shaders/manul/sky1.hlsli b/manul/shaders/manul/sky1.hlsli new file mode 100644 index 00000000..0d354b28 --- /dev/null +++ b/manul/shaders/manul/sky1.hlsli @@ -0,0 +1,630 @@ +#ifndef SKY_HLSLI +#define SKY_HLSLI + +#include "math.hlsli" + +// Public Domain under http://unlicense.org, see link for details. + +// A Nishita93-style single scattering atmosphere approximation. +// This can serve as a (not quite drop-in) replacement for e.g. +// https://github.com/wwwtyro/glsl-atmosphere +// Inherently provides both the sky model and the aerial perspective +// in the same code. +// Works for both surface and space views. + +// Consider a spherical planet of radius R, centered at origin. +// Assuming, for the purposes of scattering computations, a 2-component +// (Rayleigh and Mie particles) atmosphere with position-dependent +// relative (i.e. dimensionless) densities Wr(r), Wm(r), the optical +// depth on the (segment of) ray ro+t*rd is: +// D(ro,rd,l,h) = ∫ Ber*Wr(ro+t*rd)+Bem*Wm(ro+t*rd) dt on [l;h] +// where Ber and Bem are volume-extinction coefficients (in units of m^-1): +// Ber=Bsr+Bar +// Bem=Bsm+Bam +// Bar, Bam - Rayleigh and Mie volume-absorption coefficients, respectively +// Bsr, Bsm - Rayleigh and Mie volume-scattering coefficients, respectively +// at W=1. +// NOTE: the volume-scattering, etc., coefficients are typically +// denoted β in literature, and are written as B here to keep the +// identifiers in ASCII. Similarly, the densities are typically denoted ρ. +// The integral is typically taken either on [0;+∞), or until the ray +// hits an obstacle (e.g. the planet itself). +// From this, transmittance is calculated as usual via Beer-Lambert law: +// T=exp(-D) +// Consider a light source (effectively infinitely far away) in the +// direction ld (so that the light direction is -ld). In the +// single-scattering approximation we only consider the following scenario: +// photons from the light source arrive (attenuated by the atmosphere) from +// the direction ld at some point on view ray ro+t*rd, experience scattering +// and arrive (again attenuated by the atmosphere) at viewer. The radiance, +// in W*sr^-1*m^-2, that the viewer observes in direction rd, then is: +// L = E * ∫ S(ro+t*rd)*(Pr*Bsr*Wr(ro+t*rd)+Pm*Bsm*Wm(ro+t*rd))*exp(-(D(ro,rd,0,t)+D(ro+t*rd,ld,0,+∞))) dt +// where +// E - irradiance from the light source, in W*m^-2 +// Pr, Pm - Rayleigh and Mie phase functions, dimensionless +// S(r) - shadow/obstruction (0 if a ray from r towards ld intersects an obstacle, 1 otherwise) +// The irradiance is assumed constant in vicinity of the planet, +// and the light is only coming from ld (otherwise we would need to +// integrate the incoming radiance from all possible ld; here +// we basically assume that Lin(dir)=E*delta(dir-ld)). +// The phase functions depend on rd and ld, and describe the directional +// dependence of scattering. +// If the light is not monochromatic, the quantities L, E, and B* are actually +// spectral: they depend on the wavelength (or frequency, depending on how +// you style the spectrum). The units of L and E also change accordingly (e.g. +// E in W*sr^-1*m^-3 and E in W*m^-3 for spectrum-as-a-function-of-wavelength), +// but B* is still in m^-1. +// The entire double integral (since D(...) inside are themselves integrals) +// for the single-scattering approximation, in TeX: +// L_\lambda=E_\lambda \int_0^\infty S(\vec{r}_o+t \vec{r}_d) \left( P_r(\vec{r}_d,\vec{l}_d) B_{s r \lambda} W_r(\vec{r}_o+t \vec{r}_d) + P_m(\vec{r}_d,\vec{l}_d) B_{s m \lambda} W_m(\vec{r}_o+t \vec{r}_d) \right) \exp \left( -\left( \int_0^t \left( (B_{s r \lambda}+B_{a r \lambda}) W_r(\vec{r}_o+s \vec{r}_d)+ (B_{s m \lambda}+B_{a m \lambda}) W_m(\vec{r}_o+s \vec{r}_d) \right) \, ds + \int_0^\infty \left((B_{s r \lambda}+B_{a r \lambda}) W_r(\vec{r}_o+t \vec{r}_d+s \vec{l}_d)+ (B_{s m \lambda}+B_{a m \lambda}) W_m(\vec{r}_o+t \vec{r}_d+s \vec{l}_d) \right) \, ds \right) \right) +// NOTE: this can be also expressed as a differential equation. In this +// formulation, the computation is similar to alpha-blending infinitely many +// [t;dt] elements. This blending corresponds to exp(-D(ro,rd,0,t)) part in the +// integral, which is, therefore, absent from the differential equation. +// +// Computing this integral analytically is challenging, even for simple setups, +// so it is typically tackled numerically. +// The inner integral, D(...), however, admits fairly reasonable analytical +// approximations (whether they are faster than numerical methods is a separate +// question - we can get away with surprisingly few samples for +// numerical integration). A single-component version is sufficient to explore, +// since the multi-component version (Rayleigh and Mie) is a simple sum (this +// is NOT the case for the entire integral L, which is part of the reason +// for difficulty of computing it). +// Consider density exponentially decreasing with the altitude (a simple +// version of barometric formula) +// W=exp(-h/H)=exp(-(|r|-R)/H) +// where H is the scale height (generally, Rayleigh and Mie particles have +// different scale heights). +// This, basically, reduces the integral D(...) to the Chapman function, for +// which several approximations exist. +// We can approximate the density as a quadratic (gaussian) exponent: +// W≈exp(-(r^2-R^2)/(2*R*H))=exp(-h/H-h^2/(2*R*H)) +// In this case, the single-component integral is: +// D = ∫ B*W(ro+t*rd) dt = B*sqrt(pi*R*H/2)*exp((R^2+dot(ro,rd)^2-dot(ro,ro))/(2*R*H))*erf((t+dot(ro,rd))/sqrt(2*R*H)) +// This, actually, corresponds an existing approximation of the Chapman +// function. It has the advantage of being relatively simple and fully analytic. +// For Earth (R/H≈700) the error is reported ~0.1%. +// The expression for D above is useful both as a building block for the full +// radiance integral L, and on its own: for computing the optical depth, e.g. +// for aerial perspective and irradiance (both magnitude and color!) on a surface. +// Once we have this, the rest is just numerical intergration +// to compute L. We use an effective atmosphere radius (tuning parameter, currently +// const*(Hr+Hm), which balances errors from large integration step vs discarding +// larger part of atmosphere; more samples incentivize larger effective height) to only +// integrate on a finite segment where the atmospheric density is considered high +// enough. We then potentially split the integration segment by the planet's shadow, +// to only integrate on the lit parts of the ray (an alternative, to integrate on the +// entire segment, but skip the obstructed samples, seems less attractive, especially +// if the number of samples is low), since that are the only parts that contribute +// the light in our model. The integration itself is done using rectangle rule with +// a specifically chosen offset. This was found to provide better results than +// the commonly used midpoint rule (i.e. offset=1/2), while being similarly as simple. +// Notably, same as midpoint rule, there are no samples on segment endpoints, +// so the (sub-)segments don't share samples, which simplifies the computation. +// The results may look passable with as few as 4 samples total. The default in this +// shader is 8. + +// Literature: +// https://en.wikipedia.org/wiki/Optical_depth +// https://en.wikipedia.org/wiki/Rayleigh_scattering +// https://en.wikipedia.org/wiki/Mie_scattering +// https://en.wikipedia.org/wiki/Beer%E2%80%93Lambert_law +// https://en.wikipedia.org/wiki/Sunlight +// https://en.wikipedia.org/wiki/Barometric_formula +// https://en.wikipedia.org/wiki/Scale_height +// https://en.wikipedia.org/wiki/Chapman_function +// https://pbr-book.org/4ed/Volume_Scattering +// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/simulating-sky/simulating-colors-of-the-sky.html +// http://www.thetenthplanet.de/archives/4519 +// https://zero-radiance.github.io/post/analytic-media/ +// Tomoyuki Nishita, Takao Sirai, Katsumi Tadamura, and Eihachiro Nakamae. 1993. Display of the earth taking into account atmospheric scattering. In Proceedings of the 20th annual conference on Computer graphics and interactive techniques (SIGGRAPH '93). Association for Computing Machinery, New York, NY, USA, 175–182. https://doi.org/10.1145/166117.166140 +// http://nishitalab.org/user/nis/cdrom/sig93_nis.pdf +// Eric Bruneton. 2016. A qualitative and quantitative evaluation of 8 clear sky models. IEEE transactions on visualization and computer graphics 23, 12 (2016), 2641--2655. +// https://arxiv.org/pdf/1612.04336.pdf +// Joseph T. Kider, Daniel Knowlton, Jeremy Newlin, Yining Karl Li, and Donald P. Greenberg. 2014. A framework for the experimental comparison of solar and skydome illumination. ACM Trans. Graph. 33, 6, Article 180 (November 2014), 12 pages. https://doi.org/10.1145/2661229.2661259 +// https://spectralskylight.github.io/Kider2014.pdf +// Lukas Hosek and Alexander Wilkie. 2012. An analytic model for full spectral sky-dome radiance. ACM Trans. Graph. 31, 4, Article 95 (July 2012), 9 pages. https://doi.org/10.1145/2185520.2185591 +// https://cgg.mff.cuni.cz/projects/SkylightModelling/HosekWilkie_SkylightModel_SIGGRAPH2012_Preprint.pdf +// A. J. Preetham, Peter Shirley, and Brian Smits. 1999. A practical analytic model for daylight. In Proceedings of the 26th annual conference on Computer graphics and interactive techniques (SIGGRAPH '99). ACM Press/Addison-Wesley Publishing Co., USA, 91–100. https://doi.org/10.1145/311535.311545 +// https://courses.cs.duke.edu/fall01/cps124/resources/p91-preetham.pdf +// Naty hoffman & Arcot J. Preetham, Photorealistic Real-Time Outdoor Light Scattering, GDC 2002 +// https://renderwonk.com/publications/gdm-2002/GDM_August_2002.pdf +// https://drivers.amd.com/developer/gdc/GDC02_HoffmanPreetham.pdf +// https://github.com/ebruneton/clear-sky-models/tree/master +// https://github.com/spectralskylight +// https://github.com/wwwtyro/glsl-atmosphere + +//============================================================================== +// Mathematical constants. + +static const float INF = 1e17; +static const float pi = 3.14159265358979; + +//============================================================================== +// Physical constants. + +// Astrophysical Constants +// Source: https://pdg.lbl.gov/2023/reviews/rpp2022-rev-astrophysical-constants.pdf + +static const float au = 149597870700.0; // Astronomical unit, in m. +static const float Rs = 6.957e8; // Nominal Solar equatorial radius, in m. +static const float Sa = 6.79431e-5; // Solar solid angle from 1 a.u., in sr (=2*pi*(1-sqrt(au^2-rs^2)/au)) +static const float Re = 6.3781e6; // Nominal Earth equatorial radius, in m. + +static const float Esc = 128e3; // Solar illuminance constant, in lux (https://en.wikipedia.org/wiki/Sunlight). +// NOTE: you want to compute such constants yourself (perhaps you are dealing with +// other planet and/or star), you would need the light source's spectrum (spectral +// radiance, L(λ) in W*sr^-1*m^-2*nm^-1, e.g. AM0 for Sun, see +// https://www.shadertoy.com/view/4XjGDK), its solid angle S as seen from viewer's +// position, and, if you want to work with photometric (perceptual) quantities, +// the lumious efficiency function (see http://www.cvrl.org/cie.htm and +// e.g. https://www.shadertoy.com/view/msXyDH). +// Then +// Ee = S*∫ L(λ)*dλ - irradiance (radiometric quantity, in W/m^2) +// Ev = 863*S*∫V(λ)*L(λ)*dλ - illuminance (photometric quantity, in lux) +// and the full color (using color matching functions, +// http://www.cvrl.org/cie.htm), also in lux, is: +// X = 863*S*∫x(λ)*dλ +// Y = 863*S*∫y(λ)*dλ +// Z = 863*S*∫z(λ)*dλ +// NOTE: V(λ)=y(λ). The relative, dimensionless color would be (X,Y,Z)/Ev. +// If you don't need photometric quantities (e.g. you are doing spectral +// render), then you just integrate L(λ) as is. E.g. for the AM0 solar spectrum +// this yields Ee equal to the solar constant Gsc=1361 W/m^2. The spectral +// radiance can be approximated as blackbody spectrum (see e.g. +// https://www.shadertoy.com/view/4XjGDK). + +static const float Lsun = Esc / Sa; // Solar luminance, in cd/m^2. + +//============================================================================== +// Colorspace. +// Following http://www.thetenthplanet.de/archives/4519, we adopt +// a chromatic adapation space (which we will unimaginatively call +// CAS) with the aim to make the scattering computations *independent* per +// channel, see the post for details. +// The channels in this space ~correspond to point sampling +// the spectrum at 615 nm, 535 nm, and 445 nm, respectively. + +static const float3x3 CAS2RGB = float3x3( + 1.6218, -0.4493, 0.0325, + -0.0374, 1.0598, -0.0742, + -0.0283, -0.1119, 1.0491); + +static const float3x3 RGB2CAS = float3x3( + 6.2267e-1, 2.6392e-1, -6.2375e-4, + 2.3324e-2, 9.6056e-1, 6.7215e-2, + 1.9285e-2, 1.0958e-1, 9.6035e-1); + +static const float3 CASwavelength = float3(615, 535, 445); + +// Sun color in CAS, normalized to unit luminance. +static const float3 CASsun = float3(0.9420, 1.0269, 1.0241); + +// Rayleigh volume-scattering coefficient for +// CAS wavelengths, in m^-1, as per +// https://www.shadertoy.com/view/43j3zm. +static const float3 CASrayleigh = float3(7.2865e-6, 1.2863e-5, 2.7408e-5); + +//============================================================================== + +float2 quadratic_solve(float a, float b, float c) +{ + float d = b * b - a * c; +#if 1 + return d > 0.0 ? (-b + sqrt(d) * float2(-1, +1)) / a : float2(+INF, -INF); +#else + // Expected to be more accurate. + if(!(d>0.0)) return float2(+INF,-INF); + float q=-b+(b<0.0?sqrt(d):-sqrt(d)),l=c/q,h=q/a; // NOT sign(b), in case b=0. + return float2(min(l,h),max(l,h)); +#endif +} + +//============================================================================== +// Approximations for erf and erfcx. + +#if 0 +// From https://www.shadertoy.com/view/ml3yWj +// Eabs ~ 2.8e-8 +// Erel ~ 2.8e-8 +float erf(float x) +{ + x=clamp(x,-4.0,+4.0); + float x2=x*x; + return tanh(x*(1.12837919+x2*(0.275732946+x2*(0.0408672727+x2*0.00200393011)))/(1.0+x2*(0.153282651+x2*(0.0224402472+x2*0.000285807058)))); +} + +// Erel=2.89760318e-08 +float erfcx(float x) +{ + float q=1.0+sqrt(pi)*abs(x); + float t=abs(x)>1e9?1.0:abs(x)/(1.0+abs(x)); + float p=(1.0+t*(-1.83923738+t*(0.716286914+t*(0.798943258+t*(-0.777020726+t*0.196551435)))))/(1.0+t*(-2.48331366+t*(2.67169669+t*(-1.47041333+t*(0.417875249+t*-0.0403214433))))); + float y=p/q; + return x<0.0?2.0*exp(x*x)-y:y; +} +#else +// See https://www.shadertoy.com/view/4XSGzW +// Eabs<0.0037. +float erf(float x) +{ + x = clamp(x, -3.0, +3.0); + return (1.13072 * x) / (1.0 + (x * x) * (0.357055 + (x * x) * -0.01014)); +} + +// Eabs<0.00150 +// Erel<0.01100 +float erfcx(float x) +{ + const float a = 0.4956; + float y = (1.0 + a * abs(x)) / (1.0 + abs(x) * ((2.0 / sqrt(pi) + a) + sqrt(pi) * a * abs(x))); + return x >= 0.0 ? y : 2.0 * exp(x * x) - y; +} +#endif + +//============================================================================== +// Gaussian integrals. +// Source: https://www.shadertoy.com/view/4XSGzW. + +// Compute exp(x)*(erf(z)-erf(y)). +float gauss_segment(float x, float y, float z) +{ + return y * z < 0.0 ? + exp(x) * (erf(z) - erf(y)) : + sign(y + z) * (exp(x - y * y) * erfcx(abs(y)) - exp(x - z * z) * erfcx(abs(z))); +} + +// ∫ ρ(R,H,k,r(t)) dt on [l;h] +// where +// r(t) = ro+t*rd +// ρ = k*exp(-(r^2-R^2)/(2*R*H)) = k*exp(-h/H-h^2/(2*H*R)) +// h = |r|-R +float density_integral(float R, float H, float k, float3 ro, float3 rd, float l, float h) +{ + float A = 0.5 / (R * H); + float B = dot(ro, rd) / (R * H); + float C = 0.5 * (dot(ro, ro) - R * R) / (R * H); + float W = 0.25 * B * B / A - C; + return 0.5 * k * sqrt(pi / A) * gauss_segment(W, sqrt(A) * l + 0.5 * B / sqrt(A), sqrt(A) * h + 0.5 * B / sqrt(A)); +} + +//============================================================================== +// Phase functions. +// We follow the approach from +// Johannes Jendersie and Eugene d'Eon. 2023. An Approximate Mie Scattering Function for Fog and Cloud Rendering. In ACM SIGGRAPH 2023 Talks (SIGGRAPH '23). Association for Computing Machinery, New York, NY, USA, Article 47, 1–2. https://doi.org/10.1145/3587421.3595409 +// https://research.nvidia.com/labs/rtr/approximate-mie/publications/approximate-mie.pdf +// and approximate the Mie phase function as a weighted sum of Henyey-Greenstein and Draine +// phase functions. +// NOTE: this reduces to pure Henyey-Greenstein for phase_mie(float4(g,0,0,0),x). + +// Draine phase function. +// B.T. Draine. 2003. Scattering by interstellar dust grains. I. Optical and ultraviolet. The Astrophysical Journal 598, 2 (2003), 1017. https://doi.org/10.1086/379118 +// NOTE: this reduces to Henyey-Greenstein for a=0, +// to Rayleigh for g=0, a=1 and to Cornette-Shanks for a=1. +float phase_draine(float a, float g, float x) +{ + float d = 1.0 + g * g - 2.0 * g * x; + return 1.0 / (4.0 * pi) * (1.0 - g * g) / (1.0 + a * (1.0 + 2.0 * g * g) / 3.0) * // <-- constant factor. + (1.0 + a * x * x) / (d * sqrt(d)); +} + +float phase_rayleigh(float x) +{ + return phase_draine(1.0, 0.0, x); +} + +// Parametrization by droplet size. +// Input: droplet size, in m. +// Valid range: 5e-6=2 is considerably hazier than the description suggests. +// The reason is not clear, though it may be partially explained by +// Hosek model using a simple Henyey-Greenstein phase function for Mie +// scattering. + +// Returns Mie volume-scattering coefficient (scalar, assumed +// wavelength-independent), in m^-1. +float turbidity2mie(float B550, float Hr, float Hm, float T) +{ + // NOTE: the vertical optical depth is just B*H. + return (T - 1.0) * B550 * Hr / Hm; +} + +// Using the reference value of Rayleigh volume-scattering +// coefficient at 550 nm. +// See https://www.shadertoy.com/view/43j3zm. +float turbidity2mie(float Hr, float Hm, float T) +{ + return turbidity2mie(1.149e-5, Hr, Hm, T); +} + +//============================================================================== +// Atmospheric scattering. + +// The number of samples is 2*NUM_STEPS. +static const int NUM_STEPS = 4; + +// Returns color (C, in [0]), and alpha (A, in [1]), of the computed atmospheric scattering, +// which then can be used in the typical premultiplied alpha blending: +// Cresult=C+(1-A)*Cbackground +// Aresult=A+(1-A)*Abackground +// except alpha is per-component. +// NOTE: the units are for the reference. You can use other +// set of units, as long as it is consistent (everything in +// parsecs, etc.) - the internals of this function are not +// tied to SI. +float2x3 atmosphere( + float3 ro, // Ray origin. + float3 rd, // Ray direction. + float3 ld, // Direction toward light source. + float E, // Illuminance from light source, in lux. + float R, // Planet radius, in m. + float3 Bsr, float3 Bar, // Rayleigh volume-scattering and volume-absorption coefficients, in m^-1. + float3 Bsm, float3 Bam, // Mie volume-scattering and volume-absorption coefficients, in m^-1. + float Hr, float Hm, // Rayleigh and Mie scale height, in m. + float4 M, // Mie phase function parameters. + float2 s // Initial integration segment. + ) +{ + ld = normalize(ld); + rd = normalize(rd); + float Ra = R + 7.5 * (Hr + Hm); // Effective radius of atmosphere. + float2 sp = quadratic_solve(1.0, dot(ro, rd), dot(ro, ro) - R * R); // Segment inside planet. + float2 sa = quadratic_solve(1.0, dot(ro, rd), dot(ro, ro) - Ra * Ra); // Segment inside atmosphere. + if (sp.x < sp.y && sp.y > s.x) + s.y = min(s.y, sp.x); // Clamp segment by planet. + float3 Ber = Bsr + Bar; + float3 Bem = Bsm + Bam; + // Compute alpha. Needs to happen while the segment is still only + // clamped by planet. + float3 alpha = 1.0 - exp(-( + Ber * density_integral(R, Hr, 1.0, ro, rd, s.x, s.y) + + Bem * density_integral(R, Hm, 1.0, ro, rd, s.x, s.y))); + s = float2(max(s.x, sa.x), min(s.y, sa.y)); // Clamp segment by atmosphere. + float3 po = ro - dot(ro, ld) * ld; + float3 pd = rd - dot(rd, ld) * ld; + float2 ss = quadratic_solve(dot(pd, pd), dot(po, pd), dot(po, po) - R * R); // Segment inside planet's shadow. + if (dot(rd, ld) > 0.0) + ss.y = min(ss.y, -dot(ro, ld) / dot(rd, ld)); // Shadow is only half + else + ss.x = max(ss.x, -dot(ro, ld) / dot(rd, ld)); // of the cylinder. + float2 sl = s, sh = s.y; + if (ss.x < ss.y) + sl = float2(s.x, min(s.y, ss.x)); // Possibly split segment + if (ss.x < ss.y) + sh = float2(max(s.x, ss.y), s.y); // into two by shadow. + // If there's only one real segment, split it in half, so there + // are always 2. + if (!(sl.x < sl.y)) + { + sl = float2(sh.x, 0.5 * (sh.x + sh.y)); + sh = float2(0.5 * (sh.x + sh.y), sh.y); + } + else if (!(sh.x < sh.y)) + { + sh = float2(0.5 * (sl.x + sl.y), sl.y); + sl = float2(sl.x, 0.5 * (sl.x + sl.y)); + } + float3 Cr = 0; + float3 Cm = 0; + float Pr = phase_rayleigh(dot(rd, ld)); + float Pm = phase_mie(M, dot(rd, ld)); + // Integrate both segments. + //s = sl; + for (int k = 0; k < 1; ++k) + { + if (!(s.x < s.y)) + continue; + // Find the step size, and estimate the optimized offset. + float dt = (s.y - s.x) / float(NUM_STEPS); + float3 B = Ber + Bem; + float X = max(max(B.x, B.y), B.z) * dt; + // The offset is chosen to provide exact answer + // for integrating exp(-(X/dt)*t) on [0;dt]. + float offset = (X < 0.25 ? 0.5 - X / 24.0 : log(X / (1.0 - exp(-X))) / X); + if (k == 1) + offset = 1.0 - offset; + for (int i = 0; i < NUM_STEPS; ++i) + { + float t = s.x + (float(i) + offset) * dt; + float3 r = ro + rd * t; + float h = (dot(r, r) - R * R) / (2.0 * R); + float Dr = density_integral(R, Hr, 1.0, ro, rd, 0.0, t); // + density_integral(R, Hr, 1.0, r, ld, 0.0, INF); + float Dm = density_integral(R, Hm, 1.0, ro, rd, 0.0, t);// + density_integral(R, Hm, 1.0, r, ld, 0.0, INF); + float3 a = exp(-(Ber * Dr + Bem * Dm)); + Cr += exp(-h / Hr) * a * dt; + Cm += exp(-h / Hm) * a * dt; + } + s = sh; + } + return float2x3(E * (Pr * Bsr * Cr + Pm * Bsm * Cm), alpha); +} + +//============================================================================== +// Main image. + +//void mainImage(out float4 fragColor, in float2 fragCoord) +//{ +// float F = 1.5; +// float R = Re; +// float3 ro = float3(0, R + 80.0, 5.0); +// float3 rd = normalize(float3((2.0 * fragCoord - iResolution.xy) / iResolution.y, -F)); +// float3 md = normalize(float3((2.0 * iMouse.xy - iResolution.xy) / iResolution.y, -F)); +// float3 ld = normalize(float3(1, sin(0.5 * iTime) + 0.875, -2)); +// if (length(iMouse.xy) > 16.0) +// ld = md; +// float Hr = 7.994e3, Hm = 1.2e3; +// if (texture(iChannel0, float2(49.5, 0.5) / float2(256, 3)).x > 0.0) +// ro.z = sqrt(R * Hr); +// if (texture(iChannel0, float2(50.5, 0.5) / float2(256, 3)).x > 0.0) +// ro.z = R / 16.0; +// if (texture(iChannel0, float2(51.5, 0.5) / float2(256, 3)).x > 0.0) +// ro.z = R / 8.0; +// if (texture(iChannel0, float2(52.5, 0.5) / float2(256, 3)).x > 0.0) +// ro.z = 4.0 * R; +// float T = 1.375; // Turbidity. +// float D = 17e-6; // Droplet size, in m. +// float3 Bsr = CASrayleigh, Bar = float3(0); +// float3 Bsm = float3(turbidity2mie(Hr, Hm, T)), Bam = float3(0); // NOTE: glsl-atmosphere uses Bsm=21e-6 +// float4 M = phase_params_mie(D); +// // From https://en.wikipedia.org/wiki/Orders_of_magnitude_(illuminance)#Luminance +// // 5e3 - Typical photographic scene in full sunlight +// // 7e3 - Average clear sky +// float Lref = 7e3; // Scale luminance, in cd/m^2. +// // NOTE: we work entirely in CAS, and only convert to RGB right +// // before the tonemapping. +// float3 col = float3(1e-7); // Airglow. +// float2 s = quadratic_solve(1.0, dot(ro, rd), dot(ro, ro) - R * R); +// // Render Sun. +// if (true) +// col += Lsun * CASsun * smoothstep(1.0 - (0.5 * Rs * Rs / (au * au)), 1.0, dot(rd, ld)); +// else +// col += Lsun * CASsun * exp(-(1.0 - dot(rd, ld)) / (2.0 * Rs * Rs / (au * au))); +// // Render planet. +// if (s.x < s.y && s.y > 0.0) +// { +// float3 a = exp(-(Bsr + Bar) * density_integral(R, Hr, 1.0, ro, rd, 0.0, s.x) + (Bsm + Bam) * density_integral(R, Hm, 1.0, ro, rd, 0.0, s.x)); +// col = Esc / pi * CASsun * a * (1.0 / 128.0 + max(dot(ld, normalize(ro + s.x * rd)), 0.0)) * (RGB2CAS * float3(0.125, 0.25, 0.0625)); +// } +// float2x3 m = atmosphere(ro, normalize(rd), normalize(ld), Esc, R, Bsr, Bar, Bsm, Bam, Hr, Hm, M, float2(0.0, INF)); +// col = CASsun * m[0] + col * (1.0 - m[1]); +// col = CAS2RGB * col; +// // Apply exposure. +// col = 1.0 - exp(-col / Lref); +// col = lerp(12.92 * col, 1.055 * pow(col, float3(1.0 / 2.4)) - 0.055, step(0.0031308, col)); // sRGB +// +// float3x3 t = float3x3(0., 0., 0., 0., 0., 0., 0.1, 0.5, 0.3); +// +// fragColor = float4(col, 1.0); +// //fragColor.rgb = t * float3(0., 0., 1.); +//} + +void CalcAtmosphere(inout float3 color, in float3 viewDir, in float3 sunDir, in float altitude, in float far, in float3 sunColor) +{ + float R = Re; + float3 ro = float3(0, R + 80.0, 5.0); + float3 rd = normalize(viewDir); + float3 ld = normalize(sunDir); + float Hr = 7.994e3, Hm = 1.2e3; + float T = 1.375; // Turbidity. + float D = 17e-6; // Droplet size, in m. + float3 Bsr = CASrayleigh, Bar = 0; + float3 Bsm = turbidity2mie(Hr, Hm, T), Bam = 0; // NOTE: glsl-atmosphere uses Bsm=21e-6 + float4 M = phase_params_mie(D); + // From https://en.wikipedia.org/wiki/Orders_of_magnitude_(illuminance)#Luminance + // 5e3 - Typical photographic scene in full sunlight + // 7e3 - Average clear sky + float Lref = 7e3; // Scale luminance, in cd/m^2. + // NOTE: we work entirely in CAS, and only convert to RGB right + // before the tonemapping. + float3 col = 1e-7; // Airglow. + float2 s = quadratic_solve(1.0, dot(ro, rd), dot(ro, ro) - R * R); + // Render Sun. + //if (true) + // col += Lsun * CASsun * smoothstep(1.0 - (0.5 * Rs * Rs / (au * au)), 1.0, dot(rd, ld)); + //else + // col += Lsun * CASsun * exp(-(1.0 - dot(rd, ld)) / (2.0 * Rs * Rs / (au * au))); + // Render planet. + if (far < INF) + { + col = mul(RGB2CAS, color); + } + else + { + far = INF; + } + //if (s.x < s.y && s.y > 0.0) + //{ + // float3 a = exp(-(Bsr + Bar) * density_integral(R, Hr, 1.0, ro, rd, 0.0, s.x) + (Bsm + Bam) * density_integral(R, Hm, 1.0, ro, rd, 0.0, s.x)); + // col = Esc / pi * CASsun * a * (1.0 / 128.0 + max(dot(ld, normalize(ro + s.x * rd)), 0.0)) * (mul(RGB2CAS, float3(0.125, 0.25, 0.0625))); + //} + float2x3 m = atmosphere(ro, normalize(rd), normalize(ld), 4., R, Bsr, Bar, Bsm, Bam, Hr, Hm, M, float2(0.0, far)); + col = sunColor * m[0] + col * (1.0 - m[1]); + col = mul(CAS2RGB, col); + // Apply exposure. + //col = 1.0 - exp(-col / Lref); + color = col; + //col = lerp(12.92 * col, 1.055 * pow(col, float3(1.0 / 2.4)) - 0.055, step(0.0031308, col)); // sRGB + // + //float3x3 t = float3x3(0., 0., 0., 0., 0., 0., 0.1, 0.5, 0.3); + //Atmosphere atmosphere; + //atmosphere.earthRadius = 6360.e3; + //atmosphere.atmosphereRadius = 6420.e3; + //atmosphere.Hr = 7994.; + //atmosphere.Hm = 1200.; + //atmosphere.betaR = float3(5.8e-6, 13.5e-6, 33.1e-6); + //atmosphere.betaR = float3(7.2865e-6, 1.2863e-5, 2.7408e-5); + ////atmosphere.betaR = float3(8.658882115850087e-6, 1.5119838839850446e-5, 3.15880085046994e-5); + //atmosphere.betaM = (float3) 21.e-6; + ////atmosphere.irradiance = float3(1.4662835232339013, 1.7557753193321481, 1.7149614362178376); + // + //atmosphere.sunDirection = sunDir; + //float3 light = ComputeIncidentLight(atmosphere, float3(0., atmosphere.earthRadius + 2500. + altitude, 0.), viewDir, 0., far); + ////light = light.x * float3(0.933600, 0.358317, 0.000000) + light.y * float3(0.413697, 0.910408, 0.003489) + light.z * float3(0.178639, 0.025820, 0.983576); + ////light = light.x * float3(0.899398, 0.437131, 0.000009) + light.y * float3(0.256233, 0.966471, 0.016686) + light.z * float3(0.175352, 0.029461, 0.984065); + ////light = max(0., light.x * float3(3.240970, -0.969244, 0.055630) + light.y * float3(-1.537383, 1.875968, -0.203977) + light.z * float3(-0.498611, 0.041555, 1.056972)); + //return max(0., mul(float3x3( + // 1.6218, -0.4493, 0.0325, + //-0.0374, 1.0598, -0.0742, + //-0.0283, -0.1119, 1.0491), light)); + //float3x3 transform = float3x3(1.6218, -.4493, .0325, -.0374, 1.0598, -.0742, -.0283, -.0119, 1.0491); + //return mul(light, transform); + //return max(0., light.x * float3(2.512071, -0.057897, -0.043816) + light.y * float3(-0.622965, 1.469301, -0.155101) + light.z * float3(0.063069, -0.143869, 2.035035)); + //return light; + //return (light.x * float3(1., 0., 0.) + light.y * float3(0.64, 1., 0.) + light.z * float3(0., 0., 1.)) / 1.64; + //return (light.x * float3(0.12544465, 0., 0.) + light.y * float3(0., 1.4467391, 0.) + //+ light.z * float3(0.22230228, 0., 1.86127603)); + //return float3(dot(light, float3(0.64, + //0.33, + //0.03)), dot(light, float3(0.30, + //0.60, + //0.10)), + //dot(light, float3(0.15, + //0.06, + //0.79))); + +} + +void Sky(inout float3 color, in float3 viewDir, in float3 sunDir, in float altitude) +{ + CalcAtmosphere(color, viewDir, sunDir, altitude, INF, 1.); + +} + +#endif diff --git a/manul/shaders/manul/sky_aerial_lut.hlsl b/manul/shaders/manul/sky_aerial_lut.hlsl new file mode 100644 index 00000000..e5104f8a --- /dev/null +++ b/manul/shaders/manul/sky_aerial_lut.hlsl @@ -0,0 +1,116 @@ +#include "sky_common.hlsli" + +#include "sky_inscattering.hlsli" + +cbuffer DispatchConstants : register(b0) +{ + float4x4 g_InverseView; + float4x4 g_InverseProjection; + float3 g_SunDir; + float g_Altitude; + float3 g_MoonDir; + float g_MaxDepth; +} + +RWTexture2DArray g_AerialLut : register(u0); +RWTexture2D g_Sky : register(u1); +Texture2D g_TransmittanceLut : register(t13); +SamplerState g_TransmittanceLutSampler : register(s13); + +[numthreads(8, 8, 1)] void CS_AerialLUT(uint3 PixCoord : SV_DispatchThreadID) +{ + uint3 texture_size; + g_AerialLut.GetDimensions(texture_size.x, texture_size.y, texture_size.z); + + float moon_phase = acos(dot(g_SunDir, g_MoonDir)) * ONE_OVER_PI; + + uint2 pix_coord = PixCoord.xy; + float start_depth = 0.; + float4 L = 0.; + float4 M = 0.; + float4 transmittance = 1.; + float4 transmittance_m = 1.; + + float4 position_ndc = float4(((pix_coord + 0.5) / texture_size.xy - .5) * float2(2., -2.), 1., 1.); + position_ndc = mul(g_InverseProjection, position_ndc); + position_ndc /= position_ndc.w; + + float lat = lerp(PI_OVER_TWO, -PI_OVER_TWO, pix_coord.y / (texture_size.y - 1.)); + float lon = lerp(0., TWO_PI, pix_coord.x / float(texture_size.x)); + float3 ray_dir; + ray_dir.y = sin(lat); + ray_dir.x = cos(lon) * cos(lat); + ray_dir.z = sin(lon) * cos(lat); + + float3 sun_dir; + sun_dir.y = g_SunDir.y; + sun_dir.x = sqrt(1 - sun_dir.y * sun_dir.y); + sun_dir.z = 0.; + // float3 ray_dir = mul((float3x3)g_InverseView, normalize(position_ndc.xyz)); + + float3 ray_origin = float3(0.0, EARTH_RADIUS + max(g_Altitude, 1.), 0.0); + + float t_d = g_MaxDepth; + + float atmos_dist = ray_sphere_intersection(ray_origin, ray_dir, ATMOSPHERE_RADIUS); + float ground_dist = ray_sphere_intersection(ray_origin, ray_dir, EARTH_RADIUS); + // We are inside the atmosphere + if (ground_dist < 0.0) + { + // No ground collision, use the distance to the outer atmosphere + t_d = min(t_d, atmos_dist); + } + else + { + // We have a collision with the ground, use the distance to it + t_d = min(t_d, ground_dist); + } + + float cos_theta = dot(-ray_dir, g_SunDir); + float cos_theta_moon = dot(-ray_dir, g_MoonDir); + float molecular_phase = molecular_phase_function(cos_theta); + float aerosol_phase = aerosol_phase_function(cos_theta); + float molecular_phase_moon = molecular_phase_function(cos_theta_moon); + float aerosol_phase_moon = aerosol_phase_function(cos_theta_moon); + + for (uint i = 0; i < texture_size.z; ++i) + { + float t = (i + 1.) / (float)texture_size.z; + float end_depth = min(t_d, t * t * g_MaxDepth); + compute_inscattering(g_TransmittanceLut, g_TransmittanceLutSampler, molecular_phase, aerosol_phase, 5, ray_origin, ray_dir, start_depth, end_depth, g_SunDir, L, transmittance); + compute_inscattering(g_TransmittanceLut, g_TransmittanceLutSampler, molecular_phase_moon, aerosol_phase_moon, 5, ray_origin, ray_dir, start_depth, end_depth, g_MoonDir, M, transmittance_m); + g_AerialLut[uint3(pix_coord, i)] = float4(linear_srgb_from_spectral_samples(L + .07 * moon_phase * M) * exp2(EXPOSURE), dot(transmittance, .25)); + start_depth = end_depth; + } +} + +[numthreads(8, 8, 1)] void CS_Sky(uint3 PixCoord : SV_DispatchThreadID) +{ + + uint2 texture_size; + g_Sky.GetDimensions(texture_size.x, texture_size.y); + + float moon_phase = acos(dot(g_SunDir, g_MoonDir)) * ONE_OVER_PI; + + uint2 pix_coord = PixCoord.xy; + + float lat = lerp(PI_OVER_TWO, -.125 * PI_OVER_TWO, pix_coord.y / (texture_size.y - 1.)); + float lon = lerp(0., TWO_PI, pix_coord.x / float(texture_size.x-1.)); + float3 ray_dir; + ray_dir.y = sin(lat); + ray_dir.x = cos(lon) * cos(lat); + ray_dir.z = sin(lon) * cos(lat); + + float3 sun_dir; + sun_dir.y = g_SunDir.y; + sun_dir.x = sqrt(1 - sun_dir.y * sun_dir.y); + sun_dir.z = 0.; + + float4 position_ndc = float4(((pix_coord + 0.5) / texture_size.xy - .5) * float2(2., -2.), 1., 1.); + position_ndc = mul(g_InverseProjection, position_ndc); + position_ndc /= position_ndc.w; + // float3 ray_dir = mul((float3x3)g_InverseView, normalize(position_ndc.xyz)); + + g_Sky[pix_coord] = get_inscattering(g_TransmittanceLut, g_TransmittanceLutSampler, 32, g_Altitude, ray_dir, g_MaxDepth, 1.#INF, g_SunDir); + g_Sky[pix_coord] += .07 * moon_phase * get_inscattering(g_TransmittanceLut, g_TransmittanceLutSampler, 32, g_Altitude, ray_dir, g_MaxDepth, 1.#INF, g_MoonDir); +} diff --git a/manul/shaders/manul/sky_common.hlsli b/manul/shaders/manul/sky_common.hlsli new file mode 100644 index 00000000..c3ecd51f --- /dev/null +++ b/manul/shaders/manul/sky_common.hlsli @@ -0,0 +1,328 @@ +#ifndef SKY_COMMON_HLSLI +#define SKY_COMMON_HLSLI + +#include "color_transform.hlsli" +#include "math.hlsli" + +cbuffer SkyConstants : register(b13){ + float4 g_AerosolAbsorptionCrossSection; + float4 g_AerosolScatteringCrossSection; + float4 g_GroundAlbedo; + float g_AerosolHeightScale; + float g_AerosolTurbidity; + float g_AerosolBaseDensity; + float g_AerosolBackgroundDividedByBaseDensity; + float g_OzoneMean; + float g_FogDensity; + float g_FogHeightOffset; + float g_FogHeightScale; +} + +// Configurable parameters + +// #define ANIMATE_SUN 1 +// // 0=equirectangular, 1=fisheye, 2=projection +// #define CAMERA_TYPE 2 +// // 0=Background, 1=Desert Dust, 2=Maritime Clean, 3=Maritime Mineral, +// // 4=Polar Antarctic, 5=Polar Artic, 6=Remote Continental, 7=Rural, 8=Urban +//#define AEROSOL_TYPE 8 + +// static const float SUN_ELEVATION_DEGREES = 0.0; // 0=horizon, 90=zenith +// static const float EYE_ALTITUDE = 0.5; // km +//static const int MONTH = 0; // 0-11, January to December +//static const float AEROSOL_TURBIDITY = 1.0; +//static const float4 GROUND_ALBEDO = 0.3; +// // Ray marching steps. More steps mean better accuracy but worse performance +// static const int TRANSMITTANCE_STEPS = 32; +// static const int IN_SCATTERING_STEPS = 32; +// // Camera settings +static const float EXPOSURE = -4.0; +// // For the "projection" type camera +// static const float CAMERA_FOV = 90.0; +// static const float CAMERA_YAW = 15.0; +// static const float CAMERA_PITCH = -12.0; +// static const float CAMERA_ROLL = 0.0; + +// // Debug +// #define ENABLE_SPECTRAL 1 +// #define ENABLE_MULTIPLE_SCATTERING 1 +// #define ENABLE_AEROSOLS 1 +// #define SHOW_RELATIVE_LUMINANCE 0 +// #define TONEMAPPING_TECHNIQUE 0 // 0=ACES, 1=simple + +//----------------------------------------------------------------------------- +// Constants + +// All parameters that depend on wavelength (float4) are sampled at +// 630, 560, 490, 430 nanometers + +//static const float PI = 3.14159265358979323846; +static const float INV_PI = 0.31830988618379067154; +static const float INV_4PI = 0.25 * INV_PI; +static const float PHASE_ISOTROPIC = INV_4PI; +static const float RAYLEIGH_PHASE_SCALE = (3.0 / 16.0) * INV_PI; +static const float g = 0.8; +static const float gg = g*g; + +static const float EARTH_RADIUS = 6371.0e3; // km +static const float ATMOSPHERE_THICKNESS = 100.0e3; // km +static const float ATMOSPHERE_RADIUS = EARTH_RADIUS + ATMOSPHERE_THICKNESS; +// static const float EYE_DISTANCE_TO_EARTH_CENTER = EARTH_RADIUS + EYE_ALTITUDE; +// static const float SUN_ZENITH_COS_ANGLE = cos(radians(90.0 - SUN_ELEVATION_DEGREES)); +// static const float3 SUN_DIR = float3(-sqrt(1.0 - SUN_ZENITH_COS_ANGLE*SUN_ZENITH_COS_ANGLE), 0.0, SUN_ZENITH_COS_ANGLE); + +// Extraterrestial Solar Irradiance Spectra, units W * m^-2 * nm^-1 +// https://www.nrel.gov/grid/solar-resource/spectra.html +static const float4 sun_spectral_irradiance = float4(1.679, 1.828, 1.986, 1.307); +// Rayleigh scattering coefficient at sea level, units km^-1 +// "Rayleigh-scattering calculations for the terrestrial atmosphere" +// by Anthony Bucholtz (1995). +static const float4 molecular_scattering_coefficient_base = float4(6.605e-6, 1.067e-5, 1.842e-5, 3.156e-5); +// Fog scattering/extinction cross section, units m^2 / molecules +// Mie theory results for IOR of 1.333. Particle size is a log normal +// distribution of mean diameter=15 and std deviation=0.4 +static const float4 fog_scattering_cross_section = float4(5.015e-10, 4.987e-10, 4.966e-10, 4.949e-10); +// Ozone absorption cross section, units m^2 / molecules +// "High spectral resolution ozone absorption cross-sections" +// by V. Gorshelev et al. (2014). +static const float4 ozone_absorption_cross_section = float4(3.472e-25, 3.914e-25, 1.349e-25, 11.03e-27); + +// Mean ozone concentration in Dobson for each month of the year. +// static const float ozone_mean_monthly_dobson[] = { +// 347.0, // January +// 370.0, // February +// 381.0, // March +// 384.0, // April +// 372.0, // May +// 352.0, // June +// 333.0, // July +// 317.0, // August +// 298.0, // September +// 285.0, // October +// 290.0, // November +// 315.0 // December +// }; + +/* + * Every aerosol type expects 5 parameters: + * - Scattering cross section + * - Absorption cross section + * - Base density (km^-3) + * - Background density (km^-3) + * - Height scaling parameter + * These parameters can be sent as uniforms. + * + * This model for aerosols and their corresponding parameters come from + * "A Physically-Based Spatio-Temporal Sky Model" + * by Guimera et al. (2018). + */ +// #if AEROSOL_TYPE == 0 // Background +// static const float4 aerosol_absorption_cross_section = float4(4.5517e-19, 5.9269e-19, 6.9143e-19, 8.5228e-19); +// static const float4 aerosol_scattering_cross_section = float4(1.8921e-26, 1.6951e-26, 1.7436e-26, 2.1158e-26); +// static const float aerosol_base_density = 2.584e14; +// static const float aerosol_background_density = 2e3; +// #elif AEROSOL_TYPE == 1 // Desert Dust +// static const float4 aerosol_absorption_cross_section = float4(4.6758e-16, 4.4654e-16, 4.1989e-16, 4.1493e-16); +// static const float4 aerosol_scattering_cross_section = float4(2.9144e-16, 3.1463e-16, 3.3902e-16, 3.4298e-16); +// static const float aerosol_base_density = 1.8662e15; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 2.0; +// #elif AEROSOL_TYPE == 2 // Maritime Clean +// static const float4 aerosol_absorption_cross_section = float4(6.3312e-19, 7.5567e-19, 9.2627e-19, 1.0391e-18); +// static const float4 aerosol_scattering_cross_section = float4(4.6539e-26, 2.721e-26, 4.1104e-26, 5.6249e-26); +// static const float aerosol_base_density = 2.0266e14; +// static const float aerosol_background_density = 2e6; +// static const float aerosol_height_scale = 0.9; +// #elif AEROSOL_TYPE == 3 // Maritime Mineral +// static const float4 aerosol_absorption_cross_section = float4(6.9365e-19, 7.5951e-19, 8.2423e-19, 8.9101e-19); +// static const float4 aerosol_scattering_cross_section = float4(2.3699e-19, 2.2439e-19, 2.2126e-19, 2.021e-19); +// static const float aerosol_base_density = 2.0266e14; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 2.0; +// #elif AEROSOL_TYPE == 4 // Polar Antarctic +// static const float4 aerosol_absorption_cross_section = float4(1.3399e-16, 1.3178e-16, 1.2909e-16, 1.3006e-16); +// static const float4 aerosol_scattering_cross_section = float4(1.5506e-19, 1.809e-19, 2.3069e-19, 2.5804e-19); +// static const float aerosol_base_density = 2.3864e13; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 30.0; +// #elif AEROSOL_TYPE == 5 // Polar Arctic +// static const float4 aerosol_absorption_cross_section = float4(1.0364e-16, 1.0609e-16, 1.0193e-16, 1.0092e-16); +// static const float4 aerosol_scattering_cross_section = float4(2.1609e-17, 2.2759e-17, 2.5089e-17, 2.6323e-17); +// static const float aerosol_base_density = 2.3864e13; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 30.0; +// #elif AEROSOL_TYPE == 6 // Remote Continental +// static const float4 aerosol_absorption_cross_section = float4(4.5307e-18, 5.0662e-18, 4.4877e-18, 3.7917e-18); +// static const float4 aerosol_scattering_cross_section = float4(1.8764e-18, 1.746e-18, 1.6902e-18, 1.479e-18); +// static const float aerosol_base_density = 6.103e15; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 0.73; +// #elif AEROSOL_TYPE == 7 // Rural +// static const float4 aerosol_absorption_cross_section = float4(5.0393e-23, 8.0765e-23, 1.3823e-22, 2.3383e-22); +// static const float4 aerosol_scattering_cross_section = float4(2.6004e-22, 2.4844e-22, 2.8362e-22, 2.7494e-22); +// static const float aerosol_base_density = 8.544e15; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 0.73; +// #elif AEROSOL_TYPE == 8 // Urban +// static const float4 aerosol_absorption_cross_section = float4(2.8722e-24, 4.6168e-24, 7.9706e-24, 1.3578e-23); +// static const float4 aerosol_scattering_cross_section = float4(1.5908e-22, 1.7711e-22, 2.0942e-22, 2.4033e-22); +// static const float aerosol_base_density = 1.3681e17; +// static const float aerosol_background_density = 2e3; +// static const float aerosol_height_scale = 0.73; +// #endif +//static const float aerosol_background_divided_by_base_density = aerosol_background_density / aerosol_base_density; +// static const float fog_density = 29811535.0; +// static const float fog_scale_height = .01; +// static const float fog_height_offset = 0.325; + +//----------------------------------------------------------------------------- + +/* + * Helper function to obtain the transmittance to the top of the atmosphere + * from Buffer A. + */ +float4 transmittance_from_lut(Texture2D lut, SamplerState lut_sampler, float cos_theta, float normalized_altitude) +{ + float u = saturate(cos_theta * 0.5 + 0.5); + float v = saturate(normalized_altitude); + return lut.SampleLevel(lut_sampler, float2(u, v), 0.); +} + +/* + * Returns the distance between ro and the first intersection with the sphere + * or -1.0 if there is no intersection. The sphere's origin is (0,0,0). + * -1.0 is also returned if the ray is pointing away from the sphere. + */ +float ray_sphere_intersection(float3 ro, float3 rd, float radius) +{ + float b = dot(ro, rd); + float c = dot(ro, ro) - radius*radius; + if (c > 0.0 && b > 0.0) return -1.0; + float d = b*b - c; + if (d < 0.0) return -1.0; + if (d > b*b) return (-b+sqrt(d)); + return (-b-sqrt(d)); +} + +/* + * Rayleigh phase function. + */ +float molecular_phase_function(float cos_theta) +{ + return RAYLEIGH_PHASE_SCALE * (1.0 + cos_theta*cos_theta); +} + +/* + * Henyey-Greenstrein phase function. + */ +float aerosol_phase_function(float cos_theta) +{ + float den = 1.0 + gg + 2.0 * g * cos_theta; + return INV_4PI * (1.0 - gg) / (den * sqrt(den)); +} + +float4 get_multiple_scattering(Texture2D transmittance_lut, SamplerState lut_sampler, float cos_theta, float normalized_height, float d) +{ + // Solid angle subtended by the planet from a point at d distance + // from the planet center. + float omega = 2.0 * PI * (1.0 - sqrt(max(0., d*d - EARTH_RADIUS*EARTH_RADIUS)) / d); + + float4 T_to_ground = transmittance_from_lut(transmittance_lut, lut_sampler, cos_theta, 0.0); + + float4 T_ground_to_sample = + transmittance_from_lut(transmittance_lut, lut_sampler, 1.0, 0.0) / + transmittance_from_lut(transmittance_lut, lut_sampler, 1.0, normalized_height); + + // 2nd order scattering from the ground + float4 L_ground = PHASE_ISOTROPIC * omega * (g_GroundAlbedo / PI) * T_to_ground * T_ground_to_sample * cos_theta; + + // Fit of Earth's multiple scattering coming from other points in the atmosphere + float4 L_ms = 0.02 * float4(0.217, 0.347, 0.594, 1.0) * (1.0 / (1.0 + 5.0 * exp(-17.92 * cos_theta))); + + return L_ms + L_ground; +} + +/* + * Return the molecular volume scattering coefficient (km^-1) for a given altitude + * in kilometers. + */ +float4 get_molecular_scattering_coefficient(float h) +{ + return molecular_scattering_coefficient_base * exp(-0.07771971 * pow(h, 1.16364243)); +} + +/* + * Return the molecular volume absorption coefficient (km^-1) for a given altitude + * in kilometers. + */ +float4 get_molecular_absorption_coefficient(float h) +{ + h += 1e-4; // Avoid division by 0 + float t = log(h) - 3.22261; + float density = 3.78547397e17 * (1.0 / h) * exp(-t * t * 5.55555555); + return ozone_absorption_cross_section * g_OzoneMean * density; +} + +/* + * Return the fog volume scattering coefficient (m^-1) for a given altitude in + * kilometers. + * + * Fog (or mist, depending on density) is a special kind of aerosol consisting + * of water droplets or ice crystals. Visibility is mostly dependent on fog. + */ +float4 get_fog_scattering_coefficient(float h) +{ + if (g_FogDensity > 0.0) { + return fog_scattering_cross_section * g_FogDensity + * min(1.0, exp((-h + g_FogHeightOffset) / g_FogHeightScale)); + } else { + return 0.0; + } +} + +float get_aerosol_density(float h) +{ + return g_AerosolBaseDensity * (exp(-h / g_AerosolHeightScale) + + g_AerosolBackgroundDividedByBaseDensity); +} + +/* + * Get the collision coefficients (scattering and absorption) of the + * atmospheric medium for a given point at an altitude h. + */ +void get_atmosphere_collision_coefficients(in float h, + out float4 aerosol_absorption, + out float4 aerosol_scattering, + out float4 molecular_absorption, + out float4 molecular_scattering, + out float4 fog_scattering, + out float4 extinction) +{ + h = max(h, 1.e-3); // In case height is negative + h *= 1.e-3; + float aerosol_density = get_aerosol_density(h); + aerosol_absorption = g_AerosolAbsorptionCrossSection * aerosol_density * g_AerosolTurbidity; + aerosol_scattering = g_AerosolScatteringCrossSection * aerosol_density * g_AerosolTurbidity; + molecular_absorption = get_molecular_absorption_coefficient(h); + molecular_scattering = get_molecular_scattering_coefficient(h); + fog_scattering = get_fog_scattering_coefficient(h); + extinction = aerosol_absorption + aerosol_scattering + molecular_absorption + molecular_scattering + fog_scattering; +} + +//----------------------------------------------------------------------------- +// Spectral rendering stuff + +static const float4x3 M = float4x3( + 137.672389239975, -8.632904716299537, -1.7181567391931372, + 32.549094028629234, 91.29801417199785, -12.005406444382531, + -38.91428392614275, 34.31665471469816, 29.89044807197628, + 8.572844237945445, -11.103384660054624, 117.47585277566478 +); + +float3 linear_srgb_from_spectral_samples(float4 L) +{ + return REC709_to_XYZ(mul(L, M)); +} + + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/sky_inscattering copy.hlsli b/manul/shaders/manul/sky_inscattering copy.hlsli new file mode 100644 index 00000000..11825c40 --- /dev/null +++ b/manul/shaders/manul/sky_inscattering copy.hlsli @@ -0,0 +1,95 @@ +#ifndef SKY_INSCATTERING_HLSLI +#define SKY_INSCATTERING_HLSLI + +/* + * Buffer B: Sky texture + * + * "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" + * by Sébastien Hillaire (2020). + * + * We render the sky to a texture instead of raymarching on the entire screen. + * This is not very useful in Shadertoy, but very useful for someone looking + * to implement this on a real application. + * + * It is important to note that quality decreases significantly when rendering + * space views. To avoid this, the compute_inscattering() function can be used + * directly when rendering to a fullscreen quad. + */ + +#include "sky_common.hlsli" + +void compute_inscattering(Texture2D transmittance_lut, SamplerState lut_sampler, in float molecular_phase, in float aerosol_phase, int steps, float3 ray_origin, float3 ray_dir, float t_min, float t_max, float3 sun_dir, inout float4 L_inscattering, inout float4 transmittance) +{ + float dt = (t_max - t_min) / float(steps); + + for (int i = 0; i < steps; ++i) { + float t = t_min + (float(i) + 0.5) * dt; + float3 x_t = ray_origin + ray_dir * t; + + float distance_to_earth_center = length(x_t); + float3 zenith_dir = x_t / distance_to_earth_center; + float altitude = distance_to_earth_center - EARTH_RADIUS; + float normalized_altitude = altitude / ATMOSPHERE_THICKNESS; + + float sample_cos_theta = dot(zenith_dir, sun_dir); + + float4 aerosol_absorption, aerosol_scattering; + float4 molecular_absorption, molecular_scattering; + float4 fog_scattering; + float4 extinction; + get_atmosphere_collision_coefficients( + altitude, + aerosol_absorption, aerosol_scattering, + molecular_absorption, molecular_scattering, + fog_scattering, + extinction); + + float4 transmittance_to_sun = transmittance_from_lut( + transmittance_lut, lut_sampler, sample_cos_theta, normalized_altitude); + + float4 ms = get_multiple_scattering( + transmittance_lut, lut_sampler, sample_cos_theta, normalized_altitude, + distance_to_earth_center); + + float4 S = sun_spectral_irradiance * + (molecular_scattering * (molecular_phase * transmittance_to_sun + ms) + + (aerosol_scattering + fog_scattering) * (aerosol_phase * transmittance_to_sun + ms)); + + float4 step_transmittance = exp(-dt * extinction); + + // Energy-conserving analytical integration + // "Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite" + // by Sébastien Hillaire + float4 S_factor = (1. - step_transmittance) / max(extinction, 1e-7); + float4 S_int = S * S_factor; + L_inscattering += transmittance * S_int; + transmittance *= step_transmittance; + } +} + +float4 get_inscattering(Texture2D transmittance_lut, SamplerState lut_sampler, int steps, float altitude, float3 ray_dir, float t_min, float t_max, float3 sun_dir) { + float cos_theta = dot(-ray_dir, sun_dir); + float molecular_phase = molecular_phase_function(cos_theta); + float aerosol_phase = aerosol_phase_function(cos_theta); + + float3 ray_origin = float3(0.0, EARTH_RADIUS + max(altitude, 1.), 0.0); + float atmos_dist = ray_sphere_intersection(ray_origin, ray_dir, ATMOSPHERE_RADIUS); + float ground_dist = ray_sphere_intersection(ray_origin, ray_dir, EARTH_RADIUS); + // We are inside the atmosphere + if (ground_dist < 0.0) { + // No ground collision, use the distance to the outer atmosphere + t_max = min(t_max, atmos_dist); + } else { + // We have a collision with the ground, use the distance to it + t_max = min(t_max, ground_dist); + } + + if(t_min >= t_max) return float4(0., 0., 0., 1.); + + float4 L = 0.; + float4 transmittance = 1.; + compute_inscattering(transmittance_lut, lut_sampler, molecular_phase, aerosol_phase, 32, ray_origin, ray_dir, t_min, t_max, sun_dir, L, transmittance); + return float4(linear_srgb_from_spectral_samples(L) * exp2(EXPOSURE), dot(transmittance, .25)); +} + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/sky_inscattering.hlsli b/manul/shaders/manul/sky_inscattering.hlsli new file mode 100644 index 00000000..118fd061 --- /dev/null +++ b/manul/shaders/manul/sky_inscattering.hlsli @@ -0,0 +1,94 @@ +#ifndef SKY_INSCATTERING_HLSLI +#define SKY_INSCATTERING_HLSLI + +/* + * Buffer B: Sky texture + * + * "A Scalable and Production Ready Sky and Atmosphere Rendering Technique" + * by Sébastien Hillaire (2020). + * + * We render the sky to a texture instead of raymarching on the entire screen. + * This is not very useful in Shadertoy, but very useful for someone looking + * to implement this on a real application. + * + * It is important to note that quality decreases significantly when rendering + * space views. To avoid this, the compute_inscattering() function can be used + * directly when rendering to a fullscreen quad. + */ + +#include "sky_common.hlsli" + +void compute_inscattering(Texture2D transmittance_lut, SamplerState lut_sampler, in float molecular_phase, in float aerosol_phase, int steps, float3 ray_origin, float3 ray_dir, float t_min, float t_max, float3 sun_dir, inout float4 L_inscattering, inout float4 transmittance) +{ + float dt = (t_max - t_min) / float(steps); + + for (int i = 0; i < steps; ++i) { + float t = t_min + (float(i) + 0.5) * dt; + float3 x_t = ray_origin + ray_dir * t; + + float distance_to_earth_center = length(x_t); + float3 zenith_dir = x_t / distance_to_earth_center; + float altitude = distance_to_earth_center - EARTH_RADIUS; + float normalized_altitude = altitude / ATMOSPHERE_THICKNESS; + + float sample_cos_theta = dot(zenith_dir, sun_dir); + + float4 aerosol_absorption, aerosol_scattering; + float4 molecular_absorption, molecular_scattering; + float4 fog_scattering; + float4 extinction; + get_atmosphere_collision_coefficients( + altitude, + aerosol_absorption, aerosol_scattering, + molecular_absorption, molecular_scattering, + fog_scattering, + extinction); + + float4 transmittance_to_sun = transmittance_from_lut( + transmittance_lut, lut_sampler, sample_cos_theta, normalized_altitude); + + float4 ms = get_multiple_scattering( + transmittance_lut, lut_sampler, sample_cos_theta, normalized_altitude, + distance_to_earth_center); + + float4 S = sun_spectral_irradiance * + (molecular_scattering * (molecular_phase * transmittance_to_sun + ms) + + (aerosol_scattering + fog_scattering) * (aerosol_phase * transmittance_to_sun + ms)); + + float4 step_transmittance = exp(-dt * extinction); + + // Energy-conserving analytical integration + // "Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite" + // by Sébastien Hillaire + float4 S_int = (S - S * step_transmittance) / max(extinction, 1e-7); + L_inscattering += transmittance * S_int; + transmittance *= step_transmittance; + } +} + +float4 get_inscattering(Texture2D transmittance_lut, SamplerState lut_sampler, int steps, float altitude, float3 ray_dir, float t_min, float t_max, float3 sun_dir) { + float cos_theta = dot(-ray_dir, sun_dir); + float molecular_phase = molecular_phase_function(cos_theta); + float aerosol_phase = aerosol_phase_function(cos_theta); + + float3 ray_origin = float3(0.0, EARTH_RADIUS + max(altitude, 1.), 0.0); + float atmos_dist = ray_sphere_intersection(ray_origin, ray_dir, ATMOSPHERE_RADIUS); + float ground_dist = ray_sphere_intersection(ray_origin, ray_dir, EARTH_RADIUS); + // We are inside the atmosphere + if (ground_dist < 0.0) { + // No ground collision, use the distance to the outer atmosphere + t_max = min(t_max, atmos_dist); + } else { + // We have a collision with the ground, use the distance to it + t_max = min(t_max, ground_dist); + } + + if(t_min >= t_max) return float4(0., 0., 0., 1.); + + float4 L = 0.; + float4 transmittance = 1.; + compute_inscattering(transmittance_lut, lut_sampler, molecular_phase, aerosol_phase, 32, ray_origin, ray_dir, t_min, t_max, sun_dir, L, transmittance); + return float4(linear_srgb_from_spectral_samples(L) * exp2(EXPOSURE), dot(transmittance, .25)); +} + +#endif \ No newline at end of file diff --git a/manul/shaders/manul/sky_transmittance.hlsl b/manul/shaders/manul/sky_transmittance.hlsl new file mode 100644 index 00000000..fb9f4be0 --- /dev/null +++ b/manul/shaders/manul/sky_transmittance.hlsl @@ -0,0 +1,40 @@ +#include "sky_common.hlsli" + +#define TRANSMITTANCE_STEPS 32 + +float4 main(in float2 uv : Position) : SV_Target0 +{ + float sun_cos_theta = uv.x * 2.0 - 1.0; + float3 sun_dir = float3(-sqrt(1.0 - sun_cos_theta*sun_cos_theta), 0.0, sun_cos_theta); + + float distance_to_earth_center = lerp(EARTH_RADIUS, ATMOSPHERE_RADIUS, uv.y); + float3 ray_origin = float3(0.0, 0.0, distance_to_earth_center); + + float t_d = ray_sphere_intersection(ray_origin, sun_dir, ATMOSPHERE_RADIUS); + float dt = t_d / float(TRANSMITTANCE_STEPS); + + float4 result = 0.0; + + for (int i = 0; i < TRANSMITTANCE_STEPS; ++i) { + float t = (float(i) + 0.5) * dt; + float3 x_t = ray_origin + sun_dir * t; + + float altitude = length(x_t) - EARTH_RADIUS; + + float4 aerosol_absorption, aerosol_scattering; + float4 molecular_absorption, molecular_scattering; + float4 fog_scattering; + float4 extinction; + get_atmosphere_collision_coefficients( + altitude, + aerosol_absorption, aerosol_scattering, + molecular_absorption, molecular_scattering, + fog_scattering, + extinction); + + result += extinction * dt; + } + + float4 transmittance = exp(-result); + return transmittance; +} diff --git a/manul/shaders/manul/view_data.hlsli b/manul/shaders/manul/view_data.hlsli new file mode 100644 index 00000000..7a2fd2d6 --- /dev/null +++ b/manul/shaders/manul/view_data.hlsli @@ -0,0 +1,23 @@ +#ifndef VIEW_DATA_HLSLI +#define VIEW_DATA_HLSLI + +cbuffer DrawConstants : register(b2) { + float4x4 g_InverseModelView; + float4x4 g_InverseProjection; + float3 g_LightDir; + float g_Altitude; + float3 g_LightColor; + float g_Time; +} + +float2 PixelToCS(in float2 pixel, in float2 size) { + return ((pixel + .5) / size - .5) * float2(2., -2.); +} + +float3 ReconstructPos(in float2 cs, in float depth) { + float4 ndc = float4(cs, depth, 1.); + ndc = mul(g_InverseProjection, ndc); + return ndc.xyz / ndc.w; +} + +#endif \ No newline at end of file