diff --git a/Makefile b/Makefile
index a654009..bbb0418 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,8 @@ DEMO = \
 	 imgui_impl_sdl.o \
 	 utilities.o \
 	 texture.o \
-	 rendertarget.o
+	 rendertarget.o \
+	 bloom.o
 
 DEMO_DEPS = $(DEMO:%.o=.%.d)
 
diff --git a/bloom.cc b/bloom.cc
new file mode 100644
index 0000000..edf83b1
--- /dev/null
+++ b/bloom.cc
@@ -0,0 +1,127 @@
+#include "externals.hh"
+#include "utilities.hh"
+#include "texture.hh"
+#include "rendertarget.hh"
+#include "bloom.hh"
+
+
+T_BloomPass::T_BloomPass(
+		__rd__ const uint32_t width ,
+		__rd__ const uint32_t height ,
+		__rw__ T_FilesWatcher& watcher ,
+		__rw__ T_Texture& input )
+	: input_( input ) ,
+		spHighpass_( GL_FRAGMENT_SHADER , watcher ) ,
+		spBlur_( GL_FRAGMENT_SHADER , watcher ) ,
+		spCombine_( GL_FRAGMENT_SHADER , watcher ) ,
+		//
+		txInput_( width , height , E_TexType::RGB16F ) ,
+		rtInput_( T_RendertargetSetup( ).add( txInput_ , 0 ).create( ) ) ,
+		//
+		txBlur0_( width , height , E_TexType::RGB16F , BloomLevels ) ,
+		txBlur1_( width , height , E_TexType::RGB16F , BloomLevels )
+{
+	txInput_.wrap( E_TexWrap::CLAMP_BORDER ).samplingMode( E_TexSampling::LINEAR );
+	txBlur0_.wrap( E_TexWrap::CLAMP_EDGE ).samplingMode( E_TexSampling::LINEAR );
+	txBlur1_.wrap( E_TexWrap::CLAMP_EDGE ).samplingMode( E_TexSampling::LINEAR );
+
+	for ( int i = 0 ; i < 5 ; i ++ ) {
+		rtBlur0_.push_back( T_RendertargetSetup( ).add( txBlur0_ , i ).create( ) );
+		rtBlur1_.push_back( T_RendertargetSetup( ).add( txBlur1_ , i ).create( ) );
+	}
+
+	spHighpass_.addFile( "bloom-highpass.glsl" );
+	spBlur_.addFile( "blur-pass.glsl" );
+	spCombine_.addFile( "bloom-combine.glsl" );
+
+	spHighpass_.load( );
+	spBlur_.load( );
+	spCombine_.load( );
+}
+
+void T_BloomPass::render( )
+{
+	if ( spHighpass_.activate( ) ) {
+		enum {
+			U_TEXTURE		= 0 ,
+			U_LOD			= 1 ,
+			U_INPUT_SIZE		= 2 ,
+			U_PARAMS		= 3 ,
+		};
+		rtInput_.activate( );
+		T_TextureBinding tb( 0 );
+		tb.set( U_TEXTURE , input_ );
+		tb.bind( );
+		glUniform1i( U_LOD , 0 );
+		glUniform2f( U_INPUT_SIZE ,
+				input_.width( ) ,
+				input_.height( ) );
+		glUniform3f( U_PARAMS , 1.6 , 1.2 , .95 );
+		glRectf( -1, -1 , 1 , 1 );
+	}
+
+	if ( spBlur_.activate( ) ) {
+		enum {
+			U_TEXTURE		= 0 ,
+			U_OUTPUT_SIZE		= 1 ,
+			U_INPUT_SIZE		= 2 ,
+			U_SOURCE_LOD		= 3 ,
+			U_DIRECTION		= 4 ,
+			U_WEIGHTS		= 5 ,
+		};
+		glUniform4f( U_WEIGHTS , 0.324 , 0.232 , 0.0855 , 0.0205 );
+		for ( int i = 0 ; i < 5 ; i ++ ) {
+			// IB		RMO	B0	B1	B0	B1	B0
+			// OB		B0/0	B1/0	B0/1	B1/1	B0/2	B1/2
+			// SLOD:	0	0	0	1 	1	2
+			// IW:		W	W	W	W/2	W/2	W/4
+			// OW:		W	W	W/2	W/2	W/4	W/4
+			T_TextureBinding tb( 0 );
+			uint32_t w , h;
+			if ( i == 0 ) {
+				tb.set( U_TEXTURE , input_ );
+				w = input_.width( );
+				h = input_.height( );
+			} else {
+				tb.set( U_TEXTURE , txBlur1_ );
+				w = txBlur1_.width( ) >> ( i - 1 );
+				h = txBlur1_.height( ) >> ( i - 1 );
+			}
+
+			const uint32_t slod = i > 0 ? ( i - 1 ) : 0;
+			rtBlur0_[ i ].activate( );
+			glUniform2f( U_OUTPUT_SIZE , rtBlur0_[ i ].width( ) ,
+					rtBlur0_[ i ].height( ) );
+			glUniform1i( U_SOURCE_LOD , slod );
+			glUniform2f( U_INPUT_SIZE , w , h );
+
+			glUniform2f( U_DIRECTION , 1 , 0 );
+			tb.bind( );
+			glRectf( -1 , -1 , 1 , 1 );
+
+			rtBlur1_[ i ].activate( );
+			tb.set( U_TEXTURE , txBlur0_ );
+			tb.bind( );
+			glUniform2f( U_DIRECTION , 0 , 1 );
+			glUniform1i( U_SOURCE_LOD , i );
+			glUniform2f( U_INPUT_SIZE ,
+					rtBlur0_[ i ].width( ) ,
+					rtBlur0_[ i ].height( ) );
+			glRectf( -1 , -1 , 1 , 1 );
+		}
+	}
+
+	T_Rendertarget::MainOutput( );
+	glClearColor( 1 , 0 , 1 , 1 );
+	glClear( GL_COLOR_BUFFER_BIT );
+	if ( spCombine_.activate( ) ) {
+		T_TextureBinding main( 0 ) , blur( 1 );
+		main.set( 0 , input_ );
+		main.bind( );
+		blur.set( 1 , txBlur1_ );
+		blur.bind( );
+		glUniform2f( 2 , input_.width( ) , input_.height( ) );
+		glRectf( -1, -1 , 1 , 1 );
+		glBindTexture( GL_TEXTURE_2D , 0 );
+	}
+}
diff --git a/bloom.hh b/bloom.hh
new file mode 100644
index 0000000..6fa0d5d
--- /dev/null
+++ b/bloom.hh
@@ -0,0 +1,40 @@
+#pragma once
+#ifndef REAL_BUILD
+# define REAL_BUILD
+# include "externals.hh"
+# include "texture.hh"
+# include "rendertarget.hh"
+# include "utilities.hh"
+# undef REAL_BUILD
+#endif
+
+
+struct T_BloomPass
+{
+	static constexpr uint32_t BloomLevels = 5;
+
+	T_BloomPass( ) = delete;
+	T_BloomPass( T_BloomPass const& ) = delete;
+	T_BloomPass( T_BloomPass&& ) = delete;
+
+	T_BloomPass( __rd__ const uint32_t width ,
+			__rd__ const uint32_t height ,
+			__rw__ T_FilesWatcher& watcher ,
+			__rw__ T_Texture& input );
+
+	void render( );
+
+
+    private:
+	T_Texture& input_;
+
+	T_ShaderProgram spHighpass_;
+	T_ShaderProgram spBlur_;
+	T_ShaderProgram spCombine_;
+
+	T_Texture txInput_;
+	T_Rendertarget rtInput_;
+
+	T_Texture txBlur0_ , txBlur1_;
+	std::vector< T_Rendertarget > rtBlur0_ , rtBlur1_;
+};
diff --git a/main.cc b/main.cc
index eb45d02..252dcfa 100644
--- a/main.cc
+++ b/main.cc
@@ -4,6 +4,7 @@
 #include "utilities.hh"
 #include "texture.hh"
 #include "rendertarget.hh"
+#include "bloom.hh"
 
 
 /*= T_Main ===================================================================*/
@@ -30,18 +31,11 @@ struct T_Main
 	T_FilesWatcher watcher;
 	std::unique_ptr< T_ShaderProgram > spRaymarch;
 	std::unique_ptr< T_ShaderProgram > spCopy;
-	std::unique_ptr< T_ShaderProgram > spBloomFilter;
-	std::unique_ptr< T_ShaderProgram > spBlur;
-	std::unique_ptr< T_ShaderProgram > spBloomCombine;
 
 	std::unique_ptr< T_Texture > txRaymarchOutput;
 	std::unique_ptr< T_Rendertarget > rtRaymarchOutput;
 
-	std::unique_ptr< T_Texture > txBloomInput;
-	std::unique_ptr< T_Rendertarget > rtBloomInput;
-
-	std::unique_ptr< T_Texture > txBlur0 , txBlur1;
-	std::vector< std::unique_ptr< T_Rendertarget > > rtBlur0 , rtBlur1;
+	std::unique_ptr< T_BloomPass > bloomPass;
 
 	void startIteration( );
 	void handleCapture( );
@@ -80,22 +74,8 @@ T_Main::T_Main( )
 	rtRaymarchOutput = std::make_unique < T_Rendertarget >(
 			T_RendertargetSetup( ).add( *txRaymarchOutput ).create( ) );
 
-	txBloomInput = std::make_unique< T_Texture >(
-			1280 , 720 , E_TexType::RGB16F );
-	txBloomInput->wrap( E_TexWrap::CLAMP_BORDER ).samplingMode( E_TexSampling::LINEAR );
-	rtBloomInput = std::make_unique < T_Rendertarget >(
-			T_RendertargetSetup( ).add( *txBloomInput ).create( ) );
-
-	txBlur0 = std::make_unique< T_Texture >( 1280 , 720 , E_TexType::RGB16F , 5 );
-	txBlur0->wrap( E_TexWrap::CLAMP_EDGE ).samplingMode( E_TexSampling::LINEAR );
-	txBlur1 = std::make_unique< T_Texture >( 1280 , 720 , E_TexType::RGB16F , 5 );
-	txBlur1->wrap( E_TexWrap::CLAMP_EDGE ).samplingMode( E_TexSampling::LINEAR );
-	for ( int i = 0 ; i < 5 ; i ++ ) {
-		rtBlur0.push_back( std::make_unique< T_Rendertarget >(
-					T_RendertargetSetup( ).add( *txBlur0 , i ).create( ) ) );
-		rtBlur1.push_back( std::make_unique< T_Rendertarget >(
-					T_RendertargetSetup( ).add( *txBlur1 , i ).create( ) ) );
-	}
+	bloomPass = std::make_unique< T_BloomPass >(
+			1280 , 720 , watcher , *txRaymarchOutput );
 }
 
 void T_Main::mainLoop( )
@@ -221,100 +201,7 @@ void T_Main::render( )
 
 		glRectf( -1, -1 , 1 , 1 );
 	}
-
-	if ( spBloomFilter->activate( ) ) {
-		enum {
-			U_TEXTURE		= 0 ,
-			U_LOD			= 1 ,
-			U_INPUT_SIZE		= 2 ,
-			U_PARAMS		= 3 ,
-		};
-		rtBloomInput->activate( );
-		T_TextureBinding tb( 0 );
-		tb.set( U_TEXTURE , *txRaymarchOutput );
-		tb.bind( );
-		glUniform1i( U_LOD , 0 );
-		glUniform2f( U_INPUT_SIZE ,
-				txRaymarchOutput->width( ) ,
-				txRaymarchOutput->height( ) );
-		glUniform3f( U_PARAMS , 1.6 , 1.2 , .95 );
-		glRectf( -1, -1 , 1 , 1 );
-	}
-
-	if ( spBlur->activate( ) ) {
-		enum {
-			U_TEXTURE		= 0 ,
-			U_OUTPUT_SIZE		= 1 ,
-			U_INPUT_SIZE		= 2 ,
-			U_SOURCE_LOD		= 3 ,
-			U_DIRECTION		= 4 ,
-			U_WEIGHTS		= 5 ,
-		};
-		glUniform4f( U_WEIGHTS , 0.324 , 0.232 , 0.0855 , 0.0205 );
-		for ( int i = 0 ; i < 5 ; i ++ ) {
-			// IB		RMO	B0	B1	B0	B1	B0
-			// OB		B0/0	B1/0	B0/1	B1/1	B0/2	B1/2
-			// SLOD:	0	0	0	1 	1	2
-			// IW:		W	W	W	W/2	W/2	W/4
-			// OW:		W	W	W/2	W/2	W/4	W/4
-			T_TextureBinding tb( 0 );
-			uint32_t w , h;
-			if ( i == 0 ) {
-				tb.set( U_TEXTURE , *txBloomInput );
-				w = txRaymarchOutput->width( );
-				h = txRaymarchOutput->height( );
-			} else {
-				tb.set( U_TEXTURE , *txBlur1 );
-				w = txBlur1->width( ) >> ( i - 1 );
-				h = txBlur1->height( ) >> ( i - 1 );
-			}
-
-			const uint32_t slod = i > 0 ? ( i - 1 ) : 0;
-			rtBlur0[ i ]->activate( );
-#ifdef INTRUSIVE_TRACES
-			printf( "BLUR %d/H IT %p SLOD %d IS %dx%d OS %dx%d\n" ,
-					i , &(*( i == 0 ? txBloomInput : txBlur1 ) ) ,
-					slod , w , h , rtBlur0[ i ]->width( ) , rtBlur0[ i ]->height( ) );
-#endif
-			glUniform2f( U_OUTPUT_SIZE , rtBlur0[ i ]->width( ) , rtBlur0[ i ]->height( ) );
-			glUniform1i( U_SOURCE_LOD , slod );
-			glUniform2f( U_INPUT_SIZE , w , h );
-
-			glUniform2f( U_DIRECTION , 1 , 0 );
-			tb.bind( );
-			glRectf( -1 , -1 , 1 , 1 );
-
-			rtBlur1[ i ]->activate( );
-			tb.set( U_TEXTURE , *txBlur0 );
-			tb.bind( );
-			glUniform2f( U_DIRECTION , 0 , 1 );
-			glUniform1i( U_SOURCE_LOD , i );
-			glUniform2f( U_INPUT_SIZE ,
-					rtBlur0[ i ]->width( ) ,
-					rtBlur0[ i ]->height( ) );
-			glRectf( -1 , -1 , 1 , 1 );
-#ifdef INTRUSIVE_TRACES
-			printf( "BLUR %d/V IT %p SLOD %d IS %dx%d OS %dx%d\n" ,
-					i , &(*( txBlur0 ) ) ,
-					i , rtBlur0[ i ]->width( ) , rtBlur0[ i ]->height( ) ,
-					rtBlur0[ i ]->width( ) , rtBlur0[ i ]->height( ) );
-#endif
-		}
-	}
-
-	T_Rendertarget::MainOutput( );
-	glClearColor( 1 , 0 , 1 , 1 );
-	glClear( GL_COLOR_BUFFER_BIT );
-	if ( spBloomCombine->activate( ) ) {
-		T_TextureBinding main( 0 ) , blur( 1 );
-		main.set( 0 , *txRaymarchOutput );
-		main.bind( );
-		blur.set( 1 , *txBlur1 );
-		blur.bind( );
-		glUniform2f( 2 , dspSize.x , dspSize.y );
-		glRectf( -1, -1 , 1 , 1 );
-		glBindTexture( GL_TEXTURE_2D , 0 );
-	}
+	bloomPass->render( );
 
 	glUseProgram( 0 );
 	ImGui::Render( );
@@ -334,18 +221,6 @@ void T_Main::initProgram( )
 	spCopy = std::make_unique< T_ShaderProgram >( GL_FRAGMENT_SHADER , watcher );
 	spCopy->addFile( "copy.glsl" );
 	spCopy->load( );
-
-	spBloomFilter = std::make_unique< T_ShaderProgram >( GL_FRAGMENT_SHADER , watcher );
-	spBloomFilter->addFile( "bloom-highpass.glsl" );
-	spBloomFilter->load( );
-
-	spBlur = std::make_unique< T_ShaderProgram >( GL_FRAGMENT_SHADER , watcher );
-	spBlur->addFile( "blur-pass.glsl" );
-	spBlur->load( );
-
-	spBloomCombine = std::make_unique< T_ShaderProgram >( GL_FRAGMENT_SHADER , watcher );
-	spBloomCombine->addFile( "bloom-combine.glsl" );
-	spBloomCombine->load( );
 }