From c4218efc641fa608e147fe1cae7f4435d0045912 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Fri, 6 Oct 2017 14:29:01 +0200
Subject: [PATCH] Output debugger (can display intermediary buffers)

---
 Makefile                          |   1 +
 TODO                              |   2 -
 bloom.cc                          |   2 +
 combine.cc                        |   2 +
 demo.cc                           |   9 ++
 dof.cc                            |   5 +
 globals.cc                        |   4 +
 globals.hh                        |   3 +
 main.cc                           |  30 +++-
 odbg.cc                           | 238 ++++++++++++++++++++++++++++++
 odbg.hh                           |  85 +++++++++++
 raymarcher.cc                     |   3 +
 shaders/debug/alpha-channel.glsl  |  18 +++
 shaders/debug/copy.glsl           |  17 +++
 shaders/debug/depth-linear.glsl   |  20 +++
 shaders/debug/depth-reinhart.glsl |  18 +++
 shaders/debug/reinhart.glsl       |  18 +++
 shaders/scene.f.glsl              |   4 +-
 shaders/unused/copy.glsl          |  20 ---
 texture.cc                        |  12 +-
 texture.hh                        |   8 +
 21 files changed, 488 insertions(+), 31 deletions(-)
 create mode 100644 odbg.cc
 create mode 100644 odbg.hh
 create mode 100644 shaders/debug/alpha-channel.glsl
 create mode 100644 shaders/debug/copy.glsl
 create mode 100644 shaders/debug/depth-linear.glsl
 create mode 100644 shaders/debug/depth-reinhart.glsl
 create mode 100644 shaders/debug/reinhart.glsl
 delete mode 100644 shaders/unused/copy.glsl

diff --git a/Makefile b/Makefile
index 29beb7c..3a01709 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,7 @@ DEMO = \
 	 globals.cc \
 	 profiling.cc \
 	 shaders.cc \
+	 odbg.cc \
 	 \
 	 demo.cc \
 	 \
diff --git a/TODO b/TODO
index 5ca0921..f2f5f34 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,4 @@
 Rendering:
-* Improved light sources
 * Secondary rays
 * Shadows
 
@@ -14,6 +13,5 @@ Technical:
 * Curves for value control
 
 UI:
-* Display intermediary buffers
 * General overhaul (e.g. use tabs)
 * Better controls for color grading
diff --git a/bloom.cc b/bloom.cc
index 0eeab43..6791373 100644
--- a/bloom.cc
+++ b/bloom.cc
@@ -2,6 +2,7 @@
 #include "bloom.hh"
 #include "profiling.hh"
 #include "globals.hh"
+#include "odbg.hh"
 
 namespace {
 	static const std::string Name_( "BLOOOOM!" );
@@ -24,6 +25,7 @@ T_BloomPass::T_BloomPass(
 {
 	txBlur0_.wrap( E_TexWrap::CLAMP_EDGE ).samplingMode( E_TexSampling::LINEAR );
 	txBlur1_.wrap( E_TexWrap::CLAMP_EDGE ).samplingMode( E_TexSampling::LINEAR );
+	Globals::ODbg( ).registerTexture( txBlur0_ , E_ODbgMode::HDR , "Bloom" );
 
 	for ( auto i = 0u ; i < BloomLevels ; i ++ ) {
 		rtBlur0_.push_back( T_RendertargetSetup( ).add( txBlur0_ , i ).create( ) );
diff --git a/combine.cc b/combine.cc
index 759114d..a1262df 100644
--- a/combine.cc
+++ b/combine.cc
@@ -2,6 +2,7 @@
 #include "combine.hh"
 #include "bloom.hh"
 #include "profiling.hh"
+#include "odbg.hh"
 #include "globals.hh"
 
 namespace {
@@ -27,6 +28,7 @@ T_CombinePass::T_CombinePass(
 		cgGain_{ 1 , 1 , 1 } ,
 		cgGamma_{ 0 , 0 , 0 }
 {
+	Globals::ODbg( ).registerTexture( txOutput_ , E_ODbgMode::LDR_ALPHA , "Combined" );
 	program_ = Globals::Shaders( ).pipeline({
 			"fullscreen.v.glsl" , "combine.f.glsl" });
 }
diff --git a/demo.cc b/demo.cc
index e6b861c..5146bff 100644
--- a/demo.cc
+++ b/demo.cc
@@ -25,11 +25,20 @@ bool T_Demo::initialise( )
 
 void T_Demo::makeUI( )
 {
+	auto const& dspSize( ImGui::GetIO( ).DisplaySize );
+	ImGui::SetNextWindowSize( ImVec2( 300 , dspSize.y - 150 ) ,
+			ImGuiSetCond_Once );
+	ImGui::SetNextWindowPos( ImVec2( 0 , 150 ) ,
+			ImGuiSetCond_Once );
+	ImGui::Begin( "Demo controls" );
+
 	raymarcher->makeUI( );
 	dof->makeUI( );
 	bloom->makeUI( );
 	combine->makeUI( );
 	fxaa->makeUI( );
+
+	ImGui::End( );
 }
 
 void T_Demo::render( )
diff --git a/dof.cc b/dof.cc
index 4b69bc9..50d8479 100644
--- a/dof.cc
+++ b/dof.cc
@@ -2,6 +2,8 @@
 #include "dof.hh"
 #include "profiling.hh"
 #include "globals.hh"
+#include "odbg.hh"
+
 
 namespace {
 	static const std::string Name_( "DoF" );
@@ -26,6 +28,9 @@ T_DoFPass::T_DoFPass(
 {
 	txPass1_.wrap( E_TexWrap::CLAMP_EDGE );
 
+	Globals::ODbg( ).registerTexture( txPass1_ , E_ODbgMode::HDR , "DoF 1st pass" );
+	Globals::ODbg( ).registerTexture( txOutput_ , E_ODbgMode::HDR , "DoF output" );
+
 	spPass1_ = Globals::Shaders( ).pipeline({
 			"fullscreen.v.glsl" , "dof-pass1.f.glsl" });
 	spPass2_ = Globals::Shaders( ).pipeline({
diff --git a/globals.cc b/globals.cc
index 172e455..b776ba1 100644
--- a/globals.cc
+++ b/globals.cc
@@ -6,6 +6,7 @@
 #include "texture.hh"
 #include "shaders.hh"
 #include "window.hh"
+#include "odbg.hh"
 
 
 std::unique_ptr< T_FilesWatcher > Globals::watcher_;
@@ -13,6 +14,7 @@ std::unique_ptr< T_Window > Globals::window_;
 std::unique_ptr< T_Profiler > Globals::profiler_;
 std::unique_ptr< T_TextureManager > Globals::textures_;
 std::unique_ptr< T_ShaderManager > Globals::shaders_;
+std::unique_ptr< T_OutputDebugger > Globals::odbg_;
 
 
 void Globals::Init( )
@@ -22,10 +24,12 @@ void Globals::Init( )
 	profiler_ = std::make_unique< T_Profiler >( );
 	textures_ = std::make_unique< T_TextureManager >( );
 	shaders_ = std::make_unique< T_ShaderManager >( );
+	odbg_ = std::make_unique< T_OutputDebugger >( );
 }
 
 void Globals::Shutdown( )
 {
+	odbg_.reset( );
 	shaders_.reset( );
 	textures_.reset( );
 	profiler_.reset( );
diff --git a/globals.hh b/globals.hh
index 19f516a..4b567e4 100644
--- a/globals.hh
+++ b/globals.hh
@@ -9,6 +9,7 @@ struct T_FilesWatcher;
 struct T_Profiler;
 struct T_TextureManager;
 struct T_ShaderManager;
+struct T_OutputDebugger;
 
 
 struct Globals
@@ -21,6 +22,7 @@ struct Globals
 	static T_Profiler& Profiler( )		{ return *profiler_; }
 	static T_TextureManager& Textures( )	{ return *textures_; }
 	static T_ShaderManager& Shaders( )	{ return *shaders_; }
+	static T_OutputDebugger& ODbg( )	{ return *odbg_; }
 
     private:
 	static std::unique_ptr< T_FilesWatcher > watcher_;
@@ -28,4 +30,5 @@ struct Globals
 	static std::unique_ptr< T_Profiler > profiler_;
 	static std::unique_ptr< T_ShaderManager > shaders_;
 	static std::unique_ptr< T_TextureManager > textures_;
+	static std::unique_ptr< T_OutputDebugger > odbg_;
 };
diff --git a/main.cc b/main.cc
index 723b5cb..54ca28e 100644
--- a/main.cc
+++ b/main.cc
@@ -6,6 +6,7 @@
 #include "profiling.hh"
 #include "window.hh"
 #include "shaders.hh"
+#include "odbg.hh"
 
 
 /*= T_Main ===================================================================*/
@@ -28,6 +29,7 @@ struct T_Main
 	uint32_t stopResize = 0;
 	ImVec2 prevSize;
 
+	bool demoCtrl_ = true;
 	std::unique_ptr< T_Demo > demo;
 
 	void initDemo( );
@@ -168,18 +170,24 @@ void T_Main::handleCapture( )
 
 void T_Main::makeUI( )
 {
-	auto const& dspSize( ImGui::GetIO( ).DisplaySize );
-	ImGui::SetNextWindowSize( ImVec2( 300 , dspSize.y ) ,
+	ImGui::SetNextWindowSize( ImVec2( 300 , 150 ) ,
 			ImGuiSetCond_Once );
 	ImGui::SetNextWindowPos( ImVec2( ) , ImGuiSetCond_Once );
-	ImGui::Begin( "Yay! Demo!" );
+	ImGui::Begin( "Tools" );
+
+	ImGui::Checkbox( "Demo controls" , &demoCtrl_ );
 	ImGui::Checkbox( "Profiler" , &Globals::Profiler( ).uiEnabled( ) );
-	if ( demo ) {
-		demo->makeUI( );
-	}
+	ImGui::Checkbox( "Output debugger" ,
+			&Globals::ODbg( ).uiEnabled( ) );
+
 	ImGui::End( );
 
+	if ( demo && demoCtrl_ ) {
+		demo->makeUI( );
+	}
+
 	Globals::Profiler( ).makeUI( );
+	Globals::ODbg( ).makeUI( );
 }
 
 void T_Main::render( )
@@ -188,12 +196,20 @@ void T_Main::render( )
 		Globals::Profiler( ).start( "Render" );
 		demo->render( );
 		glFinish( ); Globals::Profiler( ).end( "Render" );
+
+		Globals::Profiler( ).start( "Debug" );
+		T_Rendertarget::MainOutput( );
+		if ( Globals::ODbg( ).isActive( ) ) {
+			Globals::ODbg( ).debugOutput( );
+		}
+		glFinish( ); Globals::Profiler( ).end( "Debug" );
+
 	} else {
 		T_Rendertarget::MainOutput( );
 		glClearColor( 0 , 0 , 0 , 1 );
 		glClear( GL_COLOR_BUFFER_BIT );
 	}
-	T_Rendertarget::MainOutput( );
+
 	glUseProgram( 0 );
 	glBindProgramPipeline( 0 );
 	Globals::Textures( ).reset( );
diff --git a/odbg.cc b/odbg.cc
new file mode 100644
index 0000000..e544b96
--- /dev/null
+++ b/odbg.cc
@@ -0,0 +1,238 @@
+#include "externals.hh"
+#include "odbg.hh"
+#include "globals.hh"
+
+namespace {
+
+	const std::string NormalOutput_ = "(DISABLED)";
+
+}
+
+
+/*= T_OutputDebugger =========================================================*/
+
+T_OutputDebugger::T_OutputDebugger( )
+	: selectorItems_( nullptr )
+{
+	registerSubmode( E_ODbgMode::LDR , "Colors" , "copy" );
+	registerSubmode( E_ODbgMode::LDR_ALPHA , "Colors" , "copy" );
+	registerSubmode( E_ODbgMode::LDR_ALPHA , "Alpha channel" , "alpha-channel" );
+	registerSubmode( E_ODbgMode::HDR , "Colors (raw)" , "copy" );
+	registerSubmode( E_ODbgMode::HDR , "Colors (Reinhart)" , "reinhart" );
+	registerSubmode( E_ODbgMode::DEPTH , "Depth (linear / 10)" , "depth-linear" ,
+			[]( uint32_t id ) { glProgramUniform1f( id , 3 , .1 ); } );
+	registerSubmode( E_ODbgMode::DEPTH , "Depth (linear / 100)" , "depth-linear" ,
+			[]( uint32_t id ) { glProgramUniform1f( id , 3 , .01 ); } );
+	registerSubmode( E_ODbgMode::DEPTH , "Depth (Reinhart)" , "depth-reinhart" );
+	initSubmodeCombo( );
+}
+
+T_OutputDebugger::~T_OutputDebugger( )
+{
+	clearSelectorItems( );
+	for ( auto ptr : smCombo_ ) {
+		delete[] ptr;
+	}
+}
+
+void T_OutputDebugger::makeUI( )
+{
+	if ( !enabled_ ) {
+		return;
+	}
+	auto const& dspSize( ImGui::GetIO( ).DisplaySize );
+	ImGui::SetNextWindowSize( ImVec2( 300 , dspSize.y - 150 ) ,
+			ImGuiSetCond_Once );
+	ImGui::SetNextWindowPos( ImVec2( 0 , 150 ) ,
+			ImGuiSetCond_Once );
+	ImGui::Begin( "Output debugger" );
+
+	// Main selector
+	if ( selectorItems_ == nullptr ) {
+		makeSelectorItems( );
+	}
+	int csi = -1;
+	for ( auto i = 0u ; i < selectorMapping_.size( ) ; i ++ ) {
+		if ( selectorMapping_[ i ] == selected_ ) {
+			csi = i;
+			break;
+		}
+	}
+	assert( csi != -1 );
+	if ( ImGui::Combo( "Output" , &csi , selectorItems_ ) ) {
+		selected_ = selectorMapping_[ csi ];
+	}
+	if ( selected_ == -1 ) {
+		ImGui::End( );
+		return;
+	}
+
+	// LOD selector
+	auto& t( outputs_[ selected_ ] );
+	if ( t.levels > 1 ) {
+		char lodCombo[ t.levels * 4 + 1 ];
+		char* ptr = lodCombo;
+		for ( auto i = 0u ; i < t.levels ; i ++ ) {
+			ptr += 1 + snprintf( ptr , sizeof( lodCombo ) - ( ptr - lodCombo ) , "%d" , i );
+		}
+		*ptr = 0;
+		ImGui::Combo( "Level" , &t.lod , lodCombo );
+	}
+
+	// Submode selector
+	ImGui::Combo( "Display" , &t.submode , smCombo_[ int( t.mode ) ] );
+
+	ImGui::End( );
+}
+
+void T_OutputDebugger::registerTexture(
+		__rw__ T_Texture& texture ,
+		__rd__ const E_ODbgMode mode ,
+		__rd__ std::string const& name )
+{
+	texture.debugIndex_ = registerTexture(
+			texture.id_ , texture.levels_ , mode , name );
+}
+
+void T_OutputDebugger::debugOutput( )
+{
+	assert( selected_ != -1 );
+
+	glClearColor( 0 , 0 , 0 , 1 );
+	glClear( GL_COLOR_BUFFER_BIT );
+
+	auto const& info( outputs_[ selected_ ] );
+	auto const& sm( submodes_[ int( info.mode ) ][ info.submode ] );
+	if ( !sm.pipeline.valid( ) ) {
+		return;
+	}
+
+	auto& tm( Globals::Textures( ) );
+	glBindTextureUnit( 0 , info.id );
+	glBindSampler( 0 , tm.sampler( "nearest-border" )->id( ) );
+
+	auto const& dspSize( ImGui::GetIO( ).DisplaySize );
+	auto pid( sm.pipeline.program( E_ShaderType::FRAGMENT ) );
+	sm.pipeline.enable( );
+	glProgramUniform1i( pid , 0 , 0 );
+	glProgramUniform1i( pid , 1 , info.lod );
+	glProgramUniform2f( pid , 2 , dspSize.x , dspSize.y );
+	if ( sm.setup ) {
+		sm.setup( pid );
+	}
+
+	glDrawArrays( GL_TRIANGLE_STRIP , 0 , 4 );
+}
+
+/*----------------------------------------------------------------------------*/
+
+void T_OutputDebugger::registerSubmode(
+		__rd__ const E_ODbgMode mode ,
+		__rd__ std::string const& name ,
+		__rd__ std::string const& shader ,
+		__rd__ F_SubmodeSetup setup )
+{
+	assert( mode != E_ODbgMode::__COUNT__ );
+	submodes_[ int( mode ) ].push_back( T_Submode_{
+		name ,
+		Globals::Shaders( ).pipeline({
+			"fullscreen.v.glsl" , "debug/" + shader + ".glsl" }) ,
+		setup
+	} );
+}
+
+void T_OutputDebugger::initSubmodeCombo( )
+{
+	for ( auto sm = 0u ; sm < int( E_ODbgMode::__COUNT__ ) ; sm ++ ) {
+		size_t reqSize = 1;
+		for ( auto const& m : submodes_[ sm ] ) {
+			reqSize += 1 + m.name.length( );
+		}
+		assert( reqSize > 1 );
+
+		smCombo_[ sm ] = new char[ reqSize ];
+		char* ptr = smCombo_[ sm ];
+		for ( auto const& m : submodes_[ sm ] ) {
+			strcpy( ptr , m.name.c_str( ) );
+			ptr += 1 + m.name.length( );
+		}
+		*ptr = 0;
+	}
+}
+
+/*----------------------------------------------------------------------------*/
+
+int32_t T_OutputDebugger::registerTexture(
+		__rd__ const GLuint id ,
+		__rd__ const uint32_t levels ,
+		__rd__ const E_ODbgMode mode ,
+		__rd__ std::string const& name )
+{
+	assert( mode != E_ODbgMode::__COUNT__ );
+	assert( id != 0 );
+	assert( name.length( ) );
+
+	nRegistered_ ++;
+	const auto n( outputs_.size( ) );
+	for ( auto i = 0u ; i < n ; i ++ ) {
+		if ( outputs_[ i ].id == 0 ) {
+			outputs_[ i ] = T_Texture_{ id , levels , mode , name , 0 , 0 };
+			return i;
+		}
+	}
+	outputs_.push_back( T_Texture_{ id , levels , mode , name , 0 , 0 } );
+	return n;
+}
+
+void T_OutputDebugger::unregisterTexture(
+		__rd__ const uint32_t index )
+{
+	assert( index < outputs_.size( ) );
+	assert( outputs_[ index ].id != 0 );
+	assert( nRegistered_ > 0 );
+	outputs_[ index ].id = 0;
+	if ( selected_ >= 0 && index == uint32_t( selected_ ) ) {
+		selected_ = -1;
+	}
+}
+
+/*----------------------------------------------------------------------------*/
+
+void T_OutputDebugger::clearSelectorItems( )
+{
+	if ( selectorItems_ != nullptr ) {
+		delete[] selectorItems_;
+		selectorItems_ = nullptr;
+		selectorMapping_.clear( );
+	}
+}
+
+void T_OutputDebugger::makeSelectorItems( )
+{
+	size_t requiredSize = 2 + NormalOutput_.length( );
+	uint32_t nrLeft = nRegistered_, i = 0;
+	selectorMapping_.push_back( -1 );
+	while ( nrLeft ) {
+		assert( i < outputs_.size( ) );
+		if ( outputs_[ i ].id != 0 ) {
+			requiredSize += 1 + outputs_[ i ].name.length( );
+			selectorMapping_.push_back( i );
+			nrLeft --;
+		}
+		i ++;
+	}
+
+	assert( selectorMapping_.size( ) == 1 + nRegistered_ );
+	assert( requiredSize >= 2 + NormalOutput_.length( ) + 2 * nRegistered_ );
+
+	selectorItems_ = new char[ requiredSize ];
+	char* ptr = selectorItems_;
+	strcpy( ptr , NormalOutput_.c_str( ) );
+	ptr += NormalOutput_.length( ) + 1;
+	for ( i = 1 ; i < selectorMapping_.size( ) ; i ++ ) {
+		auto const& name( outputs_[ selectorMapping_[ i ] ].name );
+		strcpy( ptr , name.c_str( ) );
+		ptr += name.length( ) + 1;
+	}
+	*ptr = 0;
+}
diff --git a/odbg.hh b/odbg.hh
new file mode 100644
index 0000000..79c8593
--- /dev/null
+++ b/odbg.hh
@@ -0,0 +1,85 @@
+#pragma once
+#include "texture.hh"
+#include "shaders.hh"
+
+
+enum class E_ODbgMode {
+	LDR ,
+	LDR_ALPHA ,
+	HDR ,
+	DEPTH ,
+	__COUNT__
+};
+
+
+struct T_OutputDebugger
+{
+	friend struct T_Texture;
+
+	T_OutputDebugger( );
+	NO_COPY( T_OutputDebugger );
+	NO_MOVE( T_OutputDebugger );
+	~T_OutputDebugger( );
+
+	void registerTexture(
+			__rw__ T_Texture& texture ,
+			__rd__ const E_ODbgMode mode ,
+			__rd__ std::string const& name );
+
+	bool& uiEnabled( )
+		{ return enabled_; }
+	void makeUI( );
+
+	bool isActive( ) const
+		{ return selected_ != -1; }
+	void debugOutput( );
+
+    private:
+	struct T_Texture_
+	{
+		GLuint id;
+		uint32_t levels;
+		E_ODbgMode mode;
+		std::string name;
+		//
+		int submode;
+		int lod;
+	};
+
+	using F_SubmodeSetup = std::function< void( GLuint ) >;
+	struct T_Submode_
+	{
+		std::string name;
+		T_ShaderPipeline pipeline;
+		F_SubmodeSetup setup;
+	};
+
+	bool enabled_ = false;
+	uint32_t nRegistered_ = 0;
+	std::vector< T_Texture_ > outputs_;
+	std::vector< T_Submode_ > submodes_[ int( E_ODbgMode::__COUNT__ ) ];
+	char* smCombo_[ int( E_ODbgMode::__COUNT__ ) ];
+
+	int32_t selected_ = -1;
+	char* selectorItems_;
+	std::vector< int32_t > selectorMapping_;
+
+	void registerSubmode(
+			__rd__ const E_ODbgMode mode ,
+			__rd__ std::string const& name ,
+			__rd__ std::string const& shader ,
+			__rd__ F_SubmodeSetup setup = F_SubmodeSetup( ) );
+	void initSubmodeCombo( );
+
+	int32_t registerTexture(
+			__rd__ const GLuint id ,
+			__rd__ const uint32_t levels ,
+			__rd__ const E_ODbgMode mode ,
+			__rd__ std::string const& name );
+	void unregisterTexture(
+			__rd__ const uint32_t index );
+
+	void makeSelectorItems( );
+	void clearSelectorItems( );
+};
+
diff --git a/raymarcher.cc b/raymarcher.cc
index d1b8db7..91c445d 100644
--- a/raymarcher.cc
+++ b/raymarcher.cc
@@ -2,6 +2,7 @@
 #include "raymarcher.hh"
 #include "profiling.hh"
 #include "globals.hh"
+#include "odbg.hh"
 
 
 namespace {
@@ -23,6 +24,8 @@ T_Raymarcher::T_Raymarcher(
 				.add( txDepth_ )
 				.create( ) )
 {
+	Globals::ODbg( ).registerTexture( txOutput_ , E_ODbgMode::HDR , "Raymarched image" );
+	Globals::ODbg( ).registerTexture( txDepth_ , E_ODbgMode::DEPTH , "Raymarched distance" );
 	program_ = Globals::Shaders( ).pipeline({
 			"fullscreen.v.glsl" , "scene.f.glsl" });
 }
diff --git a/shaders/debug/alpha-channel.glsl b/shaders/debug/alpha-channel.glsl
new file mode 100644
index 0000000..80705e0
--- /dev/null
+++ b/shaders/debug/alpha-channel.glsl
@@ -0,0 +1,18 @@
+#version 450 core
+
+//! type fragment
+
+layout( location = 0 ) uniform sampler2D u_InputTexture;
+layout( location = 1 ) uniform int u_LOD;
+layout( location = 2 ) uniform vec2 u_OutputSize;
+
+layout( location = 0 ) out vec4 o_Color;
+
+void main( void )
+{
+	vec2 ts = textureSize( u_InputTexture , u_LOD );
+	ivec2 pos = ivec2( ts * gl_FragCoord.xy / u_OutputSize );
+	o_Color = vec4( vec3( texelFetch( u_InputTexture , pos , u_LOD ).a ) ,
+			1 );
+}
+
diff --git a/shaders/debug/copy.glsl b/shaders/debug/copy.glsl
new file mode 100644
index 0000000..42bd304
--- /dev/null
+++ b/shaders/debug/copy.glsl
@@ -0,0 +1,17 @@
+#version 450 core
+
+//! type fragment
+
+layout( location = 0 ) uniform sampler2D u_InputTexture;
+layout( location = 1 ) uniform int u_LOD;
+layout( location = 2 ) uniform vec2 u_OutputSize;
+
+layout( location = 0 ) out vec4 o_Color;
+
+void main( void )
+{
+	vec2 ts = textureSize( u_InputTexture , u_LOD );
+	ivec2 pos = ivec2( ts * gl_FragCoord.xy / u_OutputSize );
+	o_Color = vec4( texelFetch( u_InputTexture , pos , u_LOD ).rgb , 1 );
+}
+
diff --git a/shaders/debug/depth-linear.glsl b/shaders/debug/depth-linear.glsl
new file mode 100644
index 0000000..ad8393d
--- /dev/null
+++ b/shaders/debug/depth-linear.glsl
@@ -0,0 +1,20 @@
+#version 450 core
+
+//! type fragment
+
+layout( location = 0 ) uniform sampler2D u_InputTexture;
+layout( location = 1 ) uniform int u_LOD;
+layout( location = 2 ) uniform vec2 u_OutputSize;
+layout( location = 3 ) uniform float u_Factor;
+
+layout( location = 0 ) out vec4 o_Color;
+
+void main( void )
+{
+	vec2 ts = textureSize( u_InputTexture , u_LOD );
+	ivec2 pos = ivec2( ts * gl_FragCoord.xy / u_OutputSize );
+	float depth = texelFetch( u_InputTexture , pos , u_LOD ).x
+		* u_Factor;
+	o_Color = vec4( vec3( depth ) , 1 );
+}
+
diff --git a/shaders/debug/depth-reinhart.glsl b/shaders/debug/depth-reinhart.glsl
new file mode 100644
index 0000000..6ef88df
--- /dev/null
+++ b/shaders/debug/depth-reinhart.glsl
@@ -0,0 +1,18 @@
+#version 450 core
+
+//! type fragment
+
+layout( location = 0 ) uniform sampler2D u_InputTexture;
+layout( location = 1 ) uniform int u_LOD;
+layout( location = 2 ) uniform vec2 u_OutputSize;
+
+layout( location = 0 ) out vec4 o_Color;
+
+void main( void )
+{
+	vec2 ts = textureSize( u_InputTexture , u_LOD );
+	ivec2 pos = ivec2( ts * gl_FragCoord.xy / u_OutputSize );
+	vec3 c = vec3( texelFetch( u_InputTexture , pos , u_LOD ).r );
+	o_Color = vec4( 1 - c / ( c + 1 ) , 1 );
+}
+
diff --git a/shaders/debug/reinhart.glsl b/shaders/debug/reinhart.glsl
new file mode 100644
index 0000000..3d1cec3
--- /dev/null
+++ b/shaders/debug/reinhart.glsl
@@ -0,0 +1,18 @@
+#version 450 core
+
+//! type fragment
+
+layout( location = 0 ) uniform sampler2D u_InputTexture;
+layout( location = 1 ) uniform int u_LOD;
+layout( location = 2 ) uniform vec2 u_OutputSize;
+
+layout( location = 0 ) out vec4 o_Color;
+
+void main( void )
+{
+	vec2 ts = textureSize( u_InputTexture , u_LOD );
+	ivec2 pos = ivec2( ts * gl_FragCoord.xy / u_OutputSize );
+	vec3 c = texelFetch( u_InputTexture , pos , u_LOD ).rgb;
+	o_Color = vec4( c / ( c + 1 ) , 1 );
+}
+
diff --git a/shaders/scene.f.glsl b/shaders/scene.f.glsl
index 06e6eba..3a7237f 100644
--- a/shaders/scene.f.glsl
+++ b/shaders/scene.f.glsl
@@ -164,8 +164,10 @@ void main( )
 		}
 		bc = FOG_Apply( bc , length( u_CamPos - hitPos ) ,
 				u_FogAttenuation , background );
+		o_Z = r.x;
+	} else {
+		o_Z = u_Render.w;
 	}
 
 	o_Color = bc;
-	o_Z = r.x;
 }
diff --git a/shaders/unused/copy.glsl b/shaders/unused/copy.glsl
deleted file mode 100644
index 4b65478..0000000
--- a/shaders/unused/copy.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 450 core
-
-layout( location = 0 ) uniform sampler2D u_InputTexture;
-layout( location = 1 ) uniform int u_LOD;
-
-layout( location = 0 ) out vec4 color;
-
-void main( void )
-{
-#if 1
-	vec2 ts = textureSize( u_InputTexture , u_LOD );
-	vec2 tmp = gl_FragCoord.xy / vec2( 1280. , 720. );
-	ivec2 pos = ivec2( ts * tmp );
-	color = texelFetch( u_InputTexture , pos , u_LOD );
-#else
-	vec2 tmp = gl_FragCoord.xy / vec2( 1280. , 720. );
-	color = textureLod( u_InputTexture , tmp , float( u_LOD ) );
-#endif
-}
-
diff --git a/texture.cc b/texture.cc
index 26b2326..3561a2e 100644
--- a/texture.cc
+++ b/texture.cc
@@ -1,6 +1,8 @@
 #include "externals.hh"
 #include "utilities.hh"
 #include "texture.hh"
+#include "globals.hh"
+#include "odbg.hh"
 
 
 /*==============================================================================*/
@@ -10,7 +12,8 @@ T_Texture::T_Texture(
 		__rd__ const uint32_t height ,
 		__rd__ const E_TexType type ,
 		__rd__ const uint32_t levels )
-	: levels_( levels ) , width_( width ) , height_( height )
+	: levels_( levels ) , width_( width ) , height_( height ) ,
+		debugIndex_( -1 )
 {
 	assert( levels > 0 );
 
@@ -80,6 +83,13 @@ T_Texture::T_Texture(
 
 T_Texture::~T_Texture( )
 {
+	if ( debugIndex_ != -1 ) {
+		auto& odbg( Globals::ODbg( ) );
+		assert( odbg.outputs_[ debugIndex_ ].id == id_ );
+		assert( odbg.nRegistered_ > 0 );
+		odbg.outputs_[ debugIndex_ ].id = 0;
+		odbg.nRegistered_ --;
+	}
 	glDeleteTextures( 1 , &id_ );
 }
 
diff --git a/texture.hh b/texture.hh
index cff5002..73936ff 100644
--- a/texture.hh
+++ b/texture.hh
@@ -4,6 +4,9 @@
 #endif
 
 
+struct T_OutputDebugger;
+
+
 enum class E_TexType {
 	RGBA8 ,
 	RGBA16F ,
@@ -24,8 +27,12 @@ enum class E_TexWrap {
 	CLAMP_BORDER
 };
 
+/*----------------------------------------------------------------------------*/
+
 struct T_Texture
 {
+	friend struct T_OutputDebugger;
+
 	T_Texture( ) = delete;
 	T_Texture( T_Texture const& ) = delete;
 	T_Texture( T_Texture&& ) = delete;
@@ -50,6 +57,7 @@ struct T_Texture
     private:
 	GLuint id_;
 	uint32_t levels_ , width_ , height_;
+	int32_t debugIndex_;
 };