Cogs.Core
FourierTransform.cpp
1#include "FourierTransform.h"
2#include "GPGPUQuadRenderer.h"
3#include "RenderContext.h"
4
5#include "Rendering/IEffects.h"
6#include "Rendering/IGraphicsDevice.h"
7#include "Rendering/ITextures.h"
8#include "Rendering/IRenderTargets.h"
9#include "Rendering/IBuffers.h"
10#include "Rendering/IContext.h"
11
12#include "Foundation/Logging/Logger.h"
13
14#include <cmath>
15#include <complex>
16#include <cassert>
17
18using std::complex;
19
20namespace
21{
22 Cogs::Logging::Log logger = Cogs::Logging::getLogger("FourierTransform");
23
24 const float twoPi = float(2.0*M_PI);
25
26 // Reverse the lower logN bits of i
27 inline size_t reverseBits16(size_t i, size_t logN)
28 {
29 size_t t = i;
30 t = ((0xAAAAu & t) >> 1) | ((t << 1) & 0xAAAAu); // 1010101010101010
31 t = ((0xCCCCu & t) >> 2) | ((t << 2) & 0xCCCCu); // 1100110011001100
32 t = ((0xF0F0u & t) >> 4) | ((t << 4) & 0xF0F0u); // 1111000011110000
33 t = ((0xFF00u & t) >> 8) | ((t << 8) & 0xFF00u); // 1111111100000000
34 return t >> (16 - logN);
35 }
36
37 // Calculate complex twiddle-factor e^{-2\pi n/N}
38 inline complex<float> twiddleFactor(const int n, const int N)
39 {
40 const float arg = (-twoPi*float(n)) / float(N);
41 return complex<float>(std::cos(arg), std::sin(arg));
42 }
43
44 struct Globals{
45 int fourierPass;
46 };
47
48} // of anonymous namespace
49
50Cogs::TextureHandle Cogs::FourierTransform2D::createBakedTable(IGraphicsDevice* device, const int log2N)
51{
52 int N = 1 << log2N;
53
54 std::vector<glm::vec4> table(log2N*N);
55
56 // First pass with permutations
57 for (int k = 0; k < N; k++) {
58 size_t iReversedIndex0 = reverseBits16(k &(~1), log2N);
59 size_t iReversedIndex1 = reverseBits16(k | 1, log2N);
60 bool iEven = (k & 1) == 0;
61 complex<float> iW = (iEven ? 1.f : -1.f);
62 table[k].x = float(iReversedIndex0);
63 table[k].y = float(iReversedIndex1);
64 table[k].z = iW.real();
65 table[k].w = iW.imag();
66 }
67 // Subsequent passes
68 for (int l = 1; l < log2N; l++) {
69 int blockSize = 2 << l;
70 int halfBlockSize = 1 << l;
71 int halfBlockMask = halfBlockSize - 1;
72 for (int k = 0; k < N; k++) {
73 int iBlockNumber = k / blockSize;
74 int iBlockIndex = k & halfBlockMask;
75 bool iEven = (k & halfBlockSize) == 0;
76 int iTwiddleIndex = int(iBlockIndex << (log2N - l - 1));
77 int iIndex0 = blockSize*iBlockNumber + iBlockIndex;
78 int iIndex1 = iIndex0 + halfBlockSize;
79 complex<float> iW = (iEven ? 1.f : -1.f)*twiddleFactor(-iTwiddleIndex, N);
80 table[N*l + k].x = float(iIndex0);
81 table[N*l + k].y = float(iIndex1);
82 table[N*l + k].z = iW.real();
83 table[N*l + k].w = iW.imag();
84 }
85 }
86
87 return device->getTextures()->loadTexture(reinterpret_cast<const unsigned char*>(table.data()),
88 N, log2N, TextureFormat::R32G32B32A32_FLOAT, 0);
89
90}
91
92void Cogs::FourierTransform2D::initialize(IGraphicsDevice* device, GPGPUQuadRenderer& gpgpuQuadRenderer)
93{
94 this->device = device;
95 IEffects* effects = device->getEffects();
96
97 PreprocessorDefinitions definitions;
98 passRadix2x2 = effects->loadEffect("Terrain/GPGPUPassThroughVS.hlsl",
99 "Terrain/FourierTransformPS.hlsl",
100 definitions);
101 if (!HandleIsValid(passRadix2x2)) {
102 LOG_ERROR(logger, "Error loading passRadix2x2 effect.");
103 return;
104 }
105 assert(effects->checkEffect(passRadix2x2) == Cogs::ResourceStatus::Ready && "Expects synchronous effect loading");
106 VertexFormatHandle handle = gpgpuQuadRenderer.vertexFormat();
107 passRadix2x2InputLayout = device->getBuffers()->loadInputLayout(&handle, 1, passRadix2x2);
108
109 definitions.push_back({ "DOUBLE", "1" });
110 passRadix2x2Double = effects->loadEffect("Terrain/GPGPUPassThroughVS.hlsl",
111 "Terrain/FourierTransformPS.hlsl",
112 definitions);
113
114 if (!HandleIsValid(passRadix2x2Double)) {
115 LOG_ERROR(logger, "Error loading passRadix2x2Double effect.");
116 return;
117 }
118 assert(effects->checkEffect(passRadix2x2Double) == Cogs::ResourceStatus::Ready && "Expects synchronous effect loading");
119 passRadix2x2DoubleInputLayout = device->getBuffers()->loadInputLayout(&handle, 1, passRadix2x2Double);
120
121 constantBuffer = device->getBuffers()->loadBuffer(nullptr, sizeof(int), Usage::Dynamic, AccessMode::Write, BindFlags::ConstantBuffer);
122}
123
124void Cogs::FourierTransform2D::setSize(int log2N)
125{
126 this->log2N = log2N;
127
128 tableTex = createBakedTable(device, log2N);
129
130 ITextures* textures = device->getTextures();
131 IRenderTargets* renderTargets = device->getRenderTargets();
132
133 for (int k = 0; k < 2; k++) {
134 scratchTex[k] = textures->loadTexture(nullptr, 1 << log2N, 1 << log2N, TextureFormat::R32G32_FLOAT, TextureFlags::RenderTarget);
135 scratchTarget[k] = renderTargets->createRenderTarget(scratchTex[k]);
136
137 scratchDoubleTex[k] = textures->loadTexture(nullptr, 1 << log2N, 1 << log2N, TextureFormat::R32G32B32A32_FLOAT, TextureFlags::RenderTarget);
138 scratchDoubleTarget[k] = renderTargets->createRenderTarget(scratchDoubleTex[k]);
139 }
140}
141
142
143void Cogs::FourierTransform2D::inverseFourierTransform(RenderContext& renderContext,
144 GPGPUQuadRenderer& gpgpuQuadRenderer,
145 RenderTargetHandle& target,
146 TextureHandle& source,
147 bool doubleData)
148{
149 IContext* context = renderContext.context;
150
151 context->setViewport(0, 0, float(1 << log2N), float(1 << log2N));
152 context->setEffect(doubleData ? passRadix2x2Double : passRadix2x2);
153
154 for (int pass = 0; pass < log2N; pass++) {
155 context->setRenderTarget(pass + 1 == log2N ? target : (doubleData ? scratchDoubleTarget[pass & 1] : scratchTarget[pass & 1]), DepthStencilHandle::InvalidHandle);
156 gpgpuQuadRenderer.bind(context);
157 {
158 MappedBuffer<Globals> constants(context, constantBuffer, MapMode::WriteDiscard);
159 if (constants) {
160 constants->fourierPass = pass;
161 }
162 }
163 context->setConstantBuffer("Globals", constantBuffer);
164 context->setTexture("bakedTable", 0, tableTex);
165 context->setTexture("source", 1, pass == 0 ? source : (doubleData ? scratchDoubleTex[(pass + 1) & 1] : scratchTex[(pass + 1) & 1]));
166 context->setInputLayout(doubleData ? passRadix2x2DoubleInputLayout : passRadix2x2InputLayout);
167
168 gpgpuQuadRenderer.draw(context);
169 }
170
172}
Log implementation class.
Definition: LogManager.h:140
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
@ Ready
The resource has loaded successfully and is ready for use.
std::vector< PreprocessorDefinition > PreprocessorDefinitions
A set of preprocessor definitions.
Definition: IEffects.h:20
@ Write
The buffer can be mapped and written to by the CPU after creation.
Definition: Flags.h:50
@ ConstantBuffer
The buffer can be bound as input to effects as a constant buffer.
Definition: Flags.h:72
static const Handle_t NoHandle
Represents a handle to nothing.
Definition: Common.h:78
static const Handle_t InvalidHandle
Represents an invalid handle.
Definition: Common.h:81
@ WriteDiscard
Write access. When unmapping the graphics system will discard the old contents of the resource.
Definition: Flags.h:103
@ RenderTarget
The texture can be used as a render target and drawn into.
Definition: Flags.h:120
@ Dynamic
Buffer will be loaded and modified with some frequency.
Definition: Flags.h:30