Commit 0ddf3b00 authored by Christopher Tenter's avatar Christopher Tenter

new plugin: depth of field via summed area table

git-svn-id: http://www.openflipper.org/svnrepo/OpenFlipper/branches/Free-Staging@19859 383ad7c9-94d9-4d36-a494-682f7c89f535
parent 76397e8c
include (plugin)
openflipper_plugin ( INSTALLDATA Shaders )
/*
Access of depth buffer values:
Convert between projected (non-linear) and linear depth values:
- compute depth value (gl_FragDepth) of positions in clip space
- fast depth value computation given a view space position
- inverse functions mapping from gl_FragDepth to clip space depth or view space z-coordinate
P22 and P23 are the entries in the projection matrix (in zero based row-major)
see http://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml
*/
// linear map from (non-linear) clip space depth in [-1,1] range to gl_DepthRange
float ClipSpaceToDepthValue(in float d);
// compute depth value from a position in clip space, taking the correct depth-range into account
// example:
// - in vertex shader: posCS = WorldViewProj * inPosition;
// - in fragment shader (after clipping): gl_FragDepth = ClipSpaceToDepthValue(posCS);
// can be used for depth buffer manipulation, for instance:
// - rendering imposters with correct depth
// - custom depth test
// - soft particles
float ClipSpaceToDepthValue(in vec4 posCS)
{
// divide by w in clip space to get the non-linear depth in range [-1,1]:
// posCS.z is a the linearly modified view space z coordinate: P22 * posVS.z + P23
// posCS.w is the negative view space z-coordinate: w flips the sign if the viewer direction is along the -z axis
// division posCS.z / posCS.w yields a non-linear depth -(P22 + P23 / posVS.z)
// P22, P23 are chosen such that the non-linear depth is in range [-1,1] for all positions between the near and far clipping planes
float d = posCS.z / posCS.w; // [-1,1] (ndc depth)
// map to depth-range
return ClipSpaceToDepthValue(d);
}
float ClipSpaceToDepthValue(in float d)
{
// linear mapping from [-1,1] to [gl_DepthRange.near, gl_DepthRange.far] (which should be [0,1] in most cases)
// gl_DepthRange is set by calling glDepthRange()
// -> http://www.opengl.org/sdk/docs/man/html/glDepthRange.xhtml
return (d * gl_DepthRange.diff + gl_DepthRange.near + gl_DepthRange.far) * 0.5;
// test:
// -1 is mapped to gl_DepthRange.near: (-1 * (f-n) + n + f) / 2 = (n-f + n +f) / 2 = n
// 1 is mapped to gl_DepthRange.far: (1 * (f-n) + n + f) / 2 = (f-n + n + f) / 2 = f
}
// convert depth value in gl_DepthRange to clip-space factor (z / w) in range [-1,1]
float DepthValueToClipSpace(in float depth)
{
return ( depth * 2.0 - (gl_DepthRange.near + gl_DepthRange.far) ) / gl_DepthRange.diff;
}
// convert depth buffer value to viewspace z-coord
float ProjectedToViewspaceDepth(in float depth, in float P22, in float P23)
{
// convert from [0,1] to [-1,1]
// float d = depth * 2.0 - 1.0; // special case gl_DepthRange = [0,1]
float d = DepthValueToClipSpace(depth); // arbitrary gl_DepthRange
return -P23 / (d + P22);
}
// convert view space z-coordinate to the depth value written into the depth buffer
float ViewspaceToProjectedDepth(in float zVS, in float P22, in float P23)
{
// convert to non-linear depth in [-1,1]
float d = -(P22 + P23 / zVS);
// convert to [0,1]
// return d * 0.5 + 0.5; // special case gl_DepthRange = [0,1]
return ClipSpaceToDepthValue(d); // arbitrary gl_DepthRange
}
#version 430
in vec2 vTexCoord;
out vec4 outColor;
layout(binding = 0) uniform sampler2D g_Input;
layout(binding = 0, rgba32f) uniform image2D g_Output;
void main()
{
ivec2 pos = ivec2(gl_FragCoord.xy);
vec4 v = texture(g_Input, vTexCoord);
imageStore(g_Output, pos, v);
discard;
outColor = v;
}
#version 430
/*
ref: http://http.developer.nvidia.com/GPUGems/gpugems_ch23.html
*/
#include "../Common/depthbuffer.glsl"
in vec2 vTexCoord;
out vec4 outColor;
layout(binding = 0) uniform sampler2D g_SAT;
layout(binding = 1) uniform sampler2D g_DepthTex;
layout(binding = 2) uniform sampler2D g_SceneTex;
uniform mat4 g_P;
uniform vec2 g_ClipPlanes;
vec4 textureBoxFilter(in sampler2D tex, in vec2 pos, in vec2 size)
{
ivec2 iDim = ivec2(textureSize(tex, 0));
ivec2 iPos = ivec2(iDim * pos);
ivec2 iSize = ivec2(size);
iSize = max(iSize, ivec2(1,1));
ivec2 iPosMax = iPos + iSize;
ivec2 iPosMin = iPos - iSize;
// fix filtering at border
ivec2 borderShift = max(iPosMax - iDim + ivec2(1,1), ivec2(0,0));
iPosMax -= borderShift;
iPosMin -= borderShift;
// SAT lookup
vec4 v = texelFetch(tex, iPosMax, 0);
v -= texelFetch(tex, ivec2(iPosMin.x, iPosMax.y), 0);
v -= texelFetch(tex, ivec2(iPosMax.x, iPosMin.y), 0);
v += texelFetch(tex, iPosMin, 0);
// div by area
v /= float(iSize.x * iSize.y * 4); // fast enough, but could be in float alu
return v;
}
// input: depth in view space (positive depth, so -posVS.z)
float ComputeCOC(float depthVS)
{
// "Improved Depth-of-Field Rendering" Scheuermann and Tatarchuk (ShaderX 3)
float f;
float focusZ = mix(g_ClipPlanes.x, g_ClipPlanes.y, 0.4);
if (depthVS < focusZ)
{
f = (depthVS - focusZ) / (focusZ - g_ClipPlanes.x);
}
else
{
f = (depthVS - focusZ) / (g_ClipPlanes.y - focusZ);
f = clamp(f, 0, 1);
}
// return f * 0.5 + 0.5;
// not based on a real model, but does not require param tweaking
return pow(abs(depthVS - focusZ), 1.2) * 0.21;
}
void main()
{
vec2 texSize = vec2(textureSize(g_SAT, 0));
float depth = texture(g_DepthTex, vTexCoord).x;
float zVS = ProjectedToViewspaceDepth(depth, g_P[2][2], g_P[3][2]);
float coc = ComputeCOC(-zVS);
vec4 col = vec4(0,0,0,0);
if (coc <= 1.0)
col = texture(g_SceneTex, vTexCoord);
else
col = textureBoxFilter(g_SAT, vTexCoord, vec2(coc, coc));
outColor = col;
// outColor = textureLod(g_SceneTex, vTexCoord, 0);
// outColor = vec4(1,1,1,1) * fract(zVS);
// outColor = vec4(1,1,1,1) * (-zVS);
}
#ifndef SAT_BLOCKSIZE
#define SAT_BLOCKSIZE 64
#endif
/*
supported data types
*/
#define SAT_FLOAT1 1
#define SAT_FLOAT2 2
//#define SAT_FLOAT3 3 rgb32f not supported by opengl
#define SAT_FLOAT4 4
#define SAT_INT1 5
#define SAT_INT2 6
//#define SAT_INT3 7
#define SAT_INT4 8
#define SAT_UINT1 9
#define SAT_UINT2 10
//#define SAT_UINT3 11
#define SAT_UINT4 12
#ifndef SAT_DATATYPE
#define SAT_DATATYPE SAT_FLOAT4
#endif
// find internalfmt of rw-buffers
#if SAT_DATATYPE == SAT_FLOAT1
#define SAT_INTERNALFMT r32f
#define SAT_DATAVEC float
#define SAT_DATANULL 0.0f
#define SAT_ELEMDIM 1
#elif SAT_DATATYPE == SAT_FLOAT2
#define SAT_INTERNALFMT rg32f
#define SAT_DATAVEC vec2
#define SAT_DATANULL vec2(0,0)
#define SAT_ELEMDIM 2
#elif SAT_DATATYPE == SAT_FLOAT4
#define SAT_INTERNALFMT rgba32f
#define SAT_DATAVEC vec4
#define SAT_DATANULL vec4(0,0,0,0)
#define SAT_ELEMDIM 4
#elif SAT_DATATYPE == SAT_INT1
#define SAT_INTERNALFMT r32i
#define SAT_DATAVEC int
#define SAT_DATANULL 0
#define SAT_ELEMDIM 1
#elif SAT_DATATYPE == SAT_INT2
#define SAT_INTERNALFMT rg32i
#define SAT_DATAVEC ivec2
#define SAT_DATANULL ivec2(0,0)
#define SAT_ELEMDIM 2
#elif SAT_DATATYPE == SAT_INT4
#define SAT_INTERNALFMT rgba32i
#define SAT_DATAVEC ivec4
#define SAT_DATANULL ivec4(0,0,0,0)
#define SAT_ELEMDIM 4
#elif SAT_DATATYPE == SAT_UINT1
#define SAT_INTERNALFMT r32ui
#define SAT_DATAVEC uint
#define SAT_DATANULL 0
#define SAT_ELEMDIM 1
#elif SAT_DATATYPE == SAT_UINT2
#define SAT_INTERNALFMT rg32ui
#define SAT_DATAVEC uvec2
#define SAT_DATANULL uvec2(0,0)
#define SAT_ELEMDIM 2
#elif SAT_DATATYPE == SAT_UINT4
#define SAT_INTERNALFMT rgba32ui
#define SAT_DATAVEC uvec4
#define SAT_DATANULL uvec4(0,0,0,0)
#define SAT_ELEMDIM 4
#endif
// find prefix for gvec type of input
#if SAT_DATATYPE <= SAT_FLOAT4
#define SAT_TYPEPREFIX
#define SAT_DATASCALAR float
#elif SAT_DATATYPE <= SAT_INT4
#define SAT_TYPEPREFIX i
#define SAT_DATASCALAR int
#elif SAT_DATATYPE <= SAT_UINT4
#define SAT_TYPEPREFIX u
#define SAT_DATASCALAR uint
#endif
// macro magic, direct concatenation does not work
#define SAT_PASTER_1(x,y) x ## y
#define SAT_PASTER(x,y) SAT_PASTER_1(x,y)
// wrap imageLoad/imageStore to work with values of type SAT_DATAVEC instead of gvec4
#if SAT_ELEMDIM == 1
#define SAT_imageLoad(a,b) imageLoad(a,b).x
#define SAT_imageStore(a,b,c) imageStore(a,b,SAT_PASTER(SAT_TYPEPREFIX,vec4(c,0,0,0)))
#elif SAT_ELEMDIM == 2
#define SAT_imageLoad(a,b) imageLoad(a,b).xy
#define SAT_imageStore(a,b,c) imageStore(a,b,SAT_PASTER(SAT_TYPEPREFIX,vec4(c,0,0)))
#elif SAT_ELEMDIM == 3
#define SAT_imageLoad(a,b) imageLoad(a,b).xyz
#define SAT_imageStore(a,b,c) imageStore(a,b,SAT_PASTER(SAT_TYPEPREFIX,vec4(c,0)))
//#define SAT_imageStore(a,b,c) imageStore(a,b, SAT_TYPEPREFIX ## vec4(c,0,0))
#elif SAT_ELEMDIM == 4
#define SAT_imageLoad(a,b) imageLoad(a,b)
#define SAT_imageStore(a,b,c) imageStore(a,b,c)
#endif
// find image type
#ifdef SAT_2D
#define SAT_IMAGETYPE SAT_PASTER(SAT_TYPEPREFIX,image2D)
#else
#define SAT_IMAGETYPE SAT_PASTER(SAT_TYPEPREFIX,imageBuffer)
#endif
#version 440
#ifndef SAT_BLOCKSIZE
#define SAT_BLOCKSIZE 32
#endif
#include "macros.glsl"
// IMPORTANT: has to be adjusted for prefixsum of blocksums pass,
// local_size_x = inputsize / 64
layout (local_size_x = SAT_BLOCKSIZE, local_size_y = SAT_BLOCKSIZE) in;
layout(binding = 0, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_InputTex;
layout(binding = 1, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_OutputTex;
//#define SAT_OOBCHECK
void main()
{
ivec2 iPixelPos = ivec2(gl_GlobalInvocationID.xy);
#ifdef SAT_OOBCHECK
SAT_DATAVEC pixelData = SAT_DATANULL;
ivec2 imgSize = imageSize(g_InputTex).xy;
if (iPixelPos.x < imgSize.x || iPixelPos.y < imgSize.y)
pixelData = SAT_imageLoad(g_InputTex, iPixelPos);
#else
SAT_DATAVEC pixelData = SAT_imageLoad(g_InputTex, iPixelPos);
#endif
SAT_imageStore(g_OutputTex, iPixelPos, pixelData);
}
#version 440
/*
summed area table compute shader
Computes the cumulative sum of elements in upper left area of each pixel.
exclusive scan: the current pixel is not included in the sum.
not in-place!
requires multiple passes depending on block-size and 1D/2D setting
reference
"Parallel Prefix Sum (Scans) with CUDA" by M. Harris et. al., GPU Gems 3
https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch39.html
*/
// performance got slighty worse with this on ati,
// NUM_BANKS probably has to be adjusted depending on gpu
//#define SAT_AVOID_BANKCONFLICTS
#ifdef SAT_AVOID_BANKCONFLICTS
#define NUM_BANKS 16
#define LOG_NUM_BANKS 4
#define CONFLICT_FREE_OFFSET(n) ((n) >> NUM_BANKS + (n) >> (2 * LOG_NUM_BANKS))
#endif
#include "macros.glsl"
/*
Each work-group computes the prefix-sum of a block of size 2*gl_WorkGroupSize.
If the input array consists of more than one block, additional merge passes are required.
defines:
SAT_DATATYPE default: SAT_FLOAT4
SAT_2D
SAT_BLOCKSCANOUT
SAT_AVOID_BANKCONFLICTS
*/
// each thread processes two elements
layout (local_size_x = SAT_BLOCKSIZE/2, local_size_y = 1) in;
layout(binding = 0, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_InputTex;
layout(binding = 1, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_OutputTex; // if transposed: dimension = size(g_InputTex).yx
#ifdef SAT_BLOCKSCANOUT
layout(binding = 2, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_OutputBlockSumsTex;
#endif
//#define SAT_OOBCHECK
shared SAT_DATAVEC g_SharedTree[SAT_BLOCKSIZE];
void main()
{
int n = SAT_BLOCKSIZE;
int localThreadID = int(gl_LocalInvocationID.x);
#ifdef SAT_AVOID_BANKCONFLICTS
int ai = localThreadID;
int bi = localThreadID + (n/2);
#else
int ai = localThreadID*2;
int bi = ai+1;
#endif
int blockOffset = int(gl_WorkGroupID.x * SAT_BLOCKSIZE);
int iPixelPos1D = ai + blockOffset;
int iNeighborPixelPos1D = bi + blockOffset;
#ifdef SAT_AVOID_BANKCONFLICTS
ai += CONFLICT_FREE_OFFSET(ai);
bi += CONFLICT_FREE_OFFSET(bi);
#endif
#ifdef SAT_OOBCHECK
int imgSize = imageSize(g_InputTex).x;
if (iPixelPos1D >= imgSize) return;
if (iNeighborPixelPos1D >= imgSize) return;
#endif
#ifdef SAT_2D
ivec2 iPixelPos = ivec2(iPixelPos1D, gl_WorkGroupID.y);
ivec2 iNeighborPixelPos = ivec2(iNeighborPixelPos1D, gl_WorkGroupID.y);
#else
int iPixelPos = iPixelPos1D;
int iNeighborPixelPos = iNeighborPixelPos1D;
#endif
SAT_DATAVEC pixelData = SAT_imageLoad(g_InputTex, iPixelPos);
SAT_DATAVEC neighborPixelData = SAT_imageLoad(g_InputTex, iNeighborPixelPos);
g_SharedTree[ai] = pixelData;
g_SharedTree[bi] = neighborPixelData;
// up-sweep
int offset = 1;
for (int d = n>>1; d > 0; d >>= 1)
{
barrier();
memoryBarrierShared();
if (localThreadID < d)
{
int ai = offset * (localThreadID*2 + 1)-1;
int bi = offset * (localThreadID*2 + 2)-1;
#ifdef SAT_AVOID_BANKCONFLICTS
ai += CONFLICT_FREE_OFFSET(ai);
bi += CONFLICT_FREE_OFFSET(bi);
#endif
#ifdef SAT_OOBCHECK
if (ai >= n) continue;
if (ai >= n) continue;
#endif
g_SharedTree[bi] += g_SharedTree[ai];
}
offset *= 2;
}
// down-sweep
if (localThreadID == 0)
{
int lastElement = n-1;
#ifdef SAT_AVOID_BANKCONFLICTS
lastElement += CONFLICT_FREE_OFFSET(lastElement);
#endif
#ifdef SAT_BLOCKSCANOUT
#ifdef SAT_2D
ivec2 iBlockSumPos = ivec2(gl_WorkGroupID.x, gl_WorkGroupID.y);
#else
int iBlockSumPos = int(gl_WorkGroupID.x);
#endif // SAT_2D
SAT_imageStore(g_OutputBlockSumsTex, iBlockSumPos, g_SharedTree[lastElement]);
#endif // SAT_BLOCKSCANOUT
g_SharedTree[lastElement] = SAT_DATANULL;
}
for (int d = 1; d < n; d *= 2)
{
offset >>= 1;
barrier();
memoryBarrierShared();
if (localThreadID < d)
{
int ai = offset * (localThreadID*2 + 1)-1;
int bi = offset * (localThreadID*2 + 2)-1;
#ifdef SAT_AVOID_BANKCONFLICTS
ai += CONFLICT_FREE_OFFSET(ai);
bi += CONFLICT_FREE_OFFSET(bi);
#endif
#ifdef SAT_OOBCHECK
if (ai >= n) continue;
if (ai >= n) continue;
#endif
SAT_DATAVEC t = g_SharedTree[ai];
g_SharedTree[ai] = g_SharedTree[bi];
g_SharedTree[bi] += t;
}
}
// g_SharedTree[2*localThreadID] = pixelData;
// g_SharedTree[2*localThreadID+1] = neighborPixelData;
barrier();
memoryBarrierShared();
SAT_imageStore(g_OutputTex, iPixelPos, g_SharedTree[ai]);
SAT_imageStore(g_OutputTex, iNeighborPixelPos, g_SharedTree[bi]);
// debugging
#ifdef SAT_2D
// SAT_imageStore(g_OutputTex, iPixelPos, SAT_DATAVEC(gl_WorkGroupID.xxxx) );
// SAT_imageStore(g_OutputTex, iNeighborPixelPos, SAT_DATAVEC(gl_WorkGroupID.xxxx));
// SAT_imageStore(g_OutputTex, iPixelPos, SAT_DATAVEC(gl_GlobalInvocationID.xxxx) );
// SAT_imageStore(g_OutputTex, iNeighborPixelPos, SAT_DATAVEC(gl_GlobalInvocationID.xxxx));
// SAT_imageStore(g_OutputTex, iPixelPos, SAT_DATAVEC(vec4(1,2,0,0)) );
// SAT_imageStore(g_OutputTex, iNeighborPixelPos, SAT_DATAVEC(vec4(3,4,0,0)) );
// SAT_imageStore(g_OutputTex, iPixelPos, pixelData );
// SAT_imageStore(g_OutputTex, iNeighborPixelPos, neighborPixelData );
#endif
}
#version 440
#include "macros.glsl"
layout (local_size_x = SAT_BLOCKSIZE) in;
layout(binding = 0, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_InOutTex;
layout(binding = 1, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_InputBlockTex; // block sums
void main()
{
int localThreadID = int(gl_LocalInvocationID.x);
int blockOffsetRd1D = int(gl_WorkGroupID.x * gl_WorkGroupSize.x);
int blockOffsetWr1D = int((gl_WorkGroupID.x + 1) * gl_WorkGroupSize.x);
blockOffsetRd1D = blockOffsetWr1D / int(gl_WorkGroupSize.x);
#ifdef SAT_2D
ivec2 blockOffsetRd = ivec2(blockOffsetRd1D, gl_WorkGroupID.y);
ivec2 blockOffsetWr = ivec2(blockOffsetWr1D, gl_WorkGroupID.y);
#else
int blockOffsetRd = blockOffsetRd1D;
int blockOffsetWr = blockOffsetWr1D;
#endif
SAT_DATAVEC blockSum = SAT_imageLoad(g_InputBlockTex, blockOffsetRd);
#ifdef SAT_2D
ivec2 iPixelPos = blockOffsetWr + ivec2(localThreadID, 0);
#else
int iPixelPos = blockOffsetWr + localThreadID;
#endif
SAT_DATAVEC vCurSum = SAT_imageLoad(g_InOutTex, iPixelPos);
vCurSum += blockSum;
SAT_imageStore(g_InOutTex, iPixelPos, vCurSum);
}
#version 430
layout (local_size_x = 1024) in;
shared vec4 shared_data[gl_WorkGroupSize.x * 2];
layout (binding = 0, r32f) readonly uniform image2D g_InputTex;
layout (binding = 1, r32f) writeonly uniform image2D g_OutputTex;
void main()
{
uint uTexelID = gl_LocalInvocationID.x;
ivec2 iPixelPos = ivec2(uTexelID * 2, gl_WorkGroupID.x);
shared_data[uTexelID * 2] = imageLoad(g_InputTex, iPixelPos);
shared_data[uTexelID * 2 + 1] = imageLoad(g_InputTex, iPixelPos + ivec2(1,0));
int iNumSteps = int(log2(gl_WorkGroupSize.x)) + 1;
barrier();
memoryBarrierShared();
for (int i = 0; i < iNumSteps; ++i)
{
uint mask = (1 << i) - 1;
uint readID = ((uTexelID >> i) << (i + 1)) + mask;
uint writeID = readID + 1 + (uTexelID & mask);
shared_data[writeID] += shared_data[readID];
barrier();
memoryBarrierShared();
}
imageStore(g_OutputTex, iPixelPos.yx, shared_data[uTexelID * 2]);
imageStore(g_OutputTex, iPixelPos.yx + ivec2(0,1), shared_data[uTexelID * 2 + 1]);
}
\ No newline at end of file
#version 440
#include "macros.glsl"
layout(local_size_x = SAT_BLOCKSIZE, local_size_y = SAT_BLOCKSIZE) in;
layout(binding = 0, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_InputTex;
layout(binding = 1, SAT_INTERNALFMT) uniform SAT_IMAGETYPE g_OutputTex;
void main()
{
ivec2 iPos = ivec2(gl_GlobalInvocationID.xy);
SAT_DATAVEC val = SAT_imageLoad(g_InputTex, iPos);
SAT_imageStore(g_OutputTex, iPos.yx, val);
}
\ No newline at end of file
/*===========================================================================*\
* *
* OpenFlipper *
* Copyright (C) 2001-2011 by Computer Graphics Group, RWTH Aachen *
* www.openflipper.org *
* *
*--------------------------------------------------------------------------- *
* This file is part of OpenFlipper. *
* *
* OpenFlipper is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version with the *
* following exceptions: *
* *
* If other files instantiate templates or use macros *
* or inline functions from this file, or you compile this file and *
* link it with other files to produce an executable, this file does *
* not by itself cause the resulting executable to be covered by the *
* GNU Lesser General Public License. This exception does not however *
* invalidate any other reasons why the executable file might be *
* covered by the GNU Lesser General Public License. *
* *
* OpenFlipper is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU LesserGeneral Public *
* License along with OpenFlipper. If not, *
* see <http://www.gnu.org/licenses/>. *
* *
\*===========================================================================*/
/*===========================================================================*\
* *
* $Revision: 17080 $ *
* $LastChangedBy: moeller $ *
* $Date: 2013-07-19 12:58:31 +0200 (Fri, 19 Jul 2013) $ *
* *
\*===========================================================================*/