using Godot; using Godot.Collections; using System.Collections.Generic; namespace Rokojori { [Tool] [GlobalClass] public partial class BlurCompositorEffect:CompositorEffect { public enum BlurType { Box, Gaussian } [ExportGroup( "Blur Properties" )] [Export] public BlurType blurType = BlurType.Box; [Export( PropertyHint.Range, "1,80" )] public int blurSamples = 15; [Export( PropertyHint.Range, "1,80" )] public int blurWidth = 80; [Export( PropertyHint.Range, "0,0.5" )] public float dither = 0; [Export( PropertyHint.Range, "1,4" )] public int mipLevel = 1; [Export] public bool logDebugInfo = false; //for sensing when the backbuffers need rebuilding public Vector2I sizeCache = new Vector2I(); public int mipCache; public RenderingDevice rd; public Rid shader; public Rid pipeline; public Array backbuffers = new Array(); public RDTextureFormat backbufferFormat; public RDTextureView texview; public RDSamplerState samplerState; public Rid linearSampler; public BlurCompositorEffect() { RJLog.Log( "_Init" ); RenderingServer.CallOnRenderThread( Callable.From( InitializeComputeShader ) ); } public void InitializeComputeShader() { RJLog.Log( "InitializeComputeShader" ); rd = RenderingServer.GetRenderingDevice(); if ( rd == null ) { RJLog.Log( "Initializing failed" ); return; } RJLog.Log( "Initializing succeed, loading shader" ); //Make sure this is correctly pointing to the GLSL file RDShaderFile glslFile = ( RDShaderFile ) GD.Load( "res://addons/rokojori_action_library/Runtime/Rendering/CompositorEffects/Blur/BlurEffect.glsl" ); shader = rd.ShaderCreateFromSpirV( glslFile.GetSpirV() ); pipeline = rd.ComputePipelineCreate( shader ); samplerState = new RDSamplerState(); samplerState.MinFilter = RenderingDevice.SamplerFilter.Linear; samplerState.MagFilter = RenderingDevice.SamplerFilter.Linear; linearSampler = rd.SamplerCreate( samplerState ); RJLog.Log( "Initializing done", shader, pipeline, samplerState, linearSampler ); } public override void _Notification( int what ) { if ( what != NotificationPredelete || ! shader.IsValid || rd == null ) { return; } rd.FreeRid( shader ); rd.FreeRid( linearSampler ); foreach( var b in backbuffers ) { rd.FreeRid( ( Rid ) b ); } } public override void _RenderCallback( int effectCallbackType, RenderData renderData ) { DoPass( renderData, 1 ); // horizontal blur DoPass( renderData, 2 ); // vertical blur DoPass( renderData, 3 ); // draw buffers to screen } public void DoPass( RenderData renderData, int passNum ) { if ( rd == null ) { this.LogInfoIf( logDebugInfo, "No RD" ); return; } //get fresh scene buffers && data for this pass RenderSceneBuffersRD sceneBuffers = ( RenderSceneBuffersRD ) renderData.GetRenderSceneBuffers(); RenderSceneDataRD sceneData = ( RenderSceneDataRD ) renderData.GetRenderSceneData(); if ( sceneBuffers == null && sceneData == null ) { this.LogInfoIf( logDebugInfo, "sceneBuffers == null && sceneData == null" ); return; } var size = sceneBuffers.GetInternalSize(); if ( size.X == 0 || size.Y == 0 ) { this.LogInfoIf( logDebugInfo, "size.X == 0 || size.Y == 0" ); return; } int xGroups; int yGroups; if ( passNum == 1 || passNum == 2 ) { xGroups = ( size.X/mipLevel ) / 16 + 1; yGroups = ( size.Y/mipLevel ) / 16 + 1; } else { xGroups = size.X / 16 + 1; yGroups = size.Y / 16 + 1; } int viewCount = ( int ) sceneBuffers.GetViewCount(); if ( backbuffers.Count < viewCount * 2 || sizeCache != size || mipCache != mipLevel ) { InitBackbuffer( viewCount * 2, size ); } var packedBytes = new List(); var packedFloats = new List(); var packedInts = new List(); if ( passNum == 1 || passNum == 2 ) { packedFloats.Add( size.X/mipLevel ) ; packedFloats.Add( size.Y/mipLevel ) ; } else { packedFloats.Add( size.X ) ; packedFloats.Add( size.Y ) ; } packedFloats.Add( dither ) ; packedInts.Add( blurType == BlurType.Gaussian ? 1 : 0 ); packedInts.Add( Mathf.Min( blurSamples, blurWidth )) ; packedInts.Add( blurWidth ); packedInts.Add( passNum ); packedBytes.AddRange( Bytes.Convert( packedFloats ) ); packedBytes.AddRange( Bytes.Convert( packedInts ) ); while ( packedBytes.Count < 32 ) { packedBytes.Add( 0 ); } for ( int i = 0; i < viewCount; i++ ) { var view = i; Rid screenTex = sceneBuffers.GetColorLayer( (uint)view ); Rid screenImageUniformSet = new Rid(); Rid backbufferUniformSet1 = new Rid(); Rid backbufferUniformSet2 = new Rid(); if ( passNum == 1 ) { backbufferUniformSet1 = CreateImageUniformSet( (Rid) backbuffers[view] ); screenImageUniformSet = CreateSamplerUniformSet( screenTex ); } else if ( passNum == 2 ) { backbufferUniformSet2 = CreateImageUniformSet( (Rid) backbuffers[ viewCount + view ] ); backbufferUniformSet1 = CreateSamplerUniformSet( (Rid) backbuffers[view] ); } else if ( passNum == 3 ) { backbufferUniformSet2 = CreateImageUniformSet( screenTex ); screenImageUniformSet = CreateSamplerUniformSet( (Rid) backbuffers[viewCount + view] ); } int computeList = (int) rd.ComputeListBegin(); rd.ComputeListBindComputePipeline( computeList, pipeline ); if ( passNum == 1 ) { rd.ComputeListBindUniformSet( computeList, backbufferUniformSet1, 0 ); rd.ComputeListBindUniformSet( computeList, screenImageUniformSet, 1 ); } else if ( passNum == 2 ) { rd.ComputeListBindUniformSet( computeList, backbufferUniformSet2, 0 ); rd.ComputeListBindUniformSet( computeList, backbufferUniformSet1, 1 ); } else if ( passNum == 3 ) { rd.ComputeListBindUniformSet( computeList, screenImageUniformSet, 1 ); rd.ComputeListBindUniformSet( computeList, backbufferUniformSet2, 0 ); } rd.ComputeListSetPushConstant( computeList, packedBytes.ToArray(), (uint) packedBytes.Count ); rd.ComputeListDispatch( computeList, (uint) xGroups, (uint)yGroups, 1 ); rd.ComputeListEnd(); } } Rid CreateImageUniformSet( Rid image ) { var uniform = new RDUniform(); uniform.UniformType = RenderingDevice.UniformType.Image; uniform.Binding = 0; uniform.AddId( image ); return UniformSetCacheRD.GetCache( shader, 0, new Array(){uniform} ); } Rid CreateSamplerUniformSet( Rid texture ) { var uniform = new RDUniform(); uniform.UniformType = RenderingDevice.UniformType.SamplerWithTexture; uniform.Binding = 0; uniform.AddId( linearSampler ); uniform.AddId( texture ) ; return UniformSetCacheRD.GetCache( shader, 1, new Array(){uniform} ); } public void InitBackbuffer( int count, Vector2I size ) { //remember to properly free the buffers else the memory leak will blow up your pc foreach( var b in backbuffers ) { rd.FreeRid( ( Rid ) b ); } backbuffers.Clear(); if ( backbufferFormat == null ) { backbufferFormat = new RDTextureFormat(); //there's loads of formats to choose from. This one is RGBA 16bit float with values 0.0 - 1.0 } backbufferFormat.Format = RenderingDevice.DataFormat.R16G16B16A16Unorm; backbufferFormat.Width = ( uint ) ( size.X / mipLevel ); backbufferFormat.Height = ( uint ) ( size.Y / mipLevel ); backbufferFormat.UsageBits = RenderingDevice.TextureUsageBits.StorageBit | RenderingDevice.TextureUsageBits.SamplingBit; if ( texview == null ) { texview = new RDTextureView(); } for ( int i = 0; i < count; i++ ) { backbuffers.Add( rd.TextureCreate( backbufferFormat, texview ) ); } mipCache = mipLevel; sizeCache.X = size.X; sizeCache.Y = size.Y; } } }