Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
DammertzDispatcher.h
Go to the documentation of this file.
1#pragma once
2
5#include "Frame/TFrame.h"
6#include "Math/math.h"
7
8#include <Common/assertion.h>
9#include <Common/primitive_type.h>
10
11#include <cmath>
12#include <cstddef>
13#include <queue>
14#include <utility>
15#include <vector>
16#include <limits>
17#include <algorithm>
18
19namespace ph
20{
21
22/*
23 Regions are recursively refined and dispatched based on an error metric
24 calculated from two frames. A region will not be dispatched again if its
25 error is below a certain threshold. The implementation roughly follows
26 the paper written by Dammertz et al, with some modifications.
27
28 Reference:
29
30 "A Hierarchical Automatic Stopping Condition for Monte Carlo Global
31 Illumination", Holger Dammertz, Johannes Hanika, Alexander Keller,
32 Hendrik Lensch; Full Papers Proceedings of the WSCG 2010, p. 159-164.
33*/
35{
36public:
37 enum class ERefineMode
38 {
41 };
42
43 template<ERefineMode MODE>
44 class TAnalyzer;
45
46 DammertzDispatcher() = default;
47
48 explicit DammertzDispatcher(
49 uint32 numWorkers,
50 const Region& fullRegion);
51
53 uint32 numWorkers,
54 const Region& fullRegion,
55 real precisionStandard,
56 std::size_t initialDepthPerRegion);
57
59 uint32 numWorkers,
60 const Region& fullRegion,
61 real precisionStandard,
62 std::size_t initialDepthPerRegion,
63 std::size_t standardDepthPerRegion,
64 std::size_t terminusDepthPerRegion);
65
66 bool dispatch(WorkUnit* out_workUnit) override;
67
68 template<ERefineMode MODE>
70
71 template<ERefineMode MODE>
72 void addAnalyzedData(const TAnalyzer<MODE>& analyzer);
73
74 std::size_t numPendingRegions() const;
75
76 template<ERefineMode MODE>
77 class TAnalyzer final
78 {
79 friend DammertzDispatcher;
80
81 public:
83 const Region& finishedRegion,
84 const HdrRgbFrame& allEffortFrame,
85 const HdrRgbFrame& halfEffortFrame);
86
87 bool isConverged() const;
88
89 private:
91 real splitThreshold,
92 real terminateThreshold,
93 real numFullRegionPixels);
94
95 std::pair<Region, Region> getNextRegions() const;
96
97 real m_splitThreshold;
98 real m_terminateThreshold;
99 std::pair<Region, Region> m_nextRegions;
100 real m_rcpNumRegionPixels;
101 std::vector<real> m_accumulatedEps;
102 };
103
104private:
105 constexpr static std::size_t MIN_REGION_AREA = 256;
106
107 real m_splitThreshold;
108 real m_terminateThreshold;
109 std::size_t m_standardDepthPerRegion;
110 std::size_t m_terminusDepthPerRegion;
111 Region m_fullRegion;
112 std::queue<WorkUnit> m_pendingRegions;
113
114 void addAnalyzedRegion(const Region& region);
115};
116
117// In-header Implementations:
118
119template<DammertzDispatcher::ERefineMode MODE>
121{
122 return TAnalyzer<MODE>(
123 m_splitThreshold,
124 m_terminateThreshold,
125 static_cast<real>(m_fullRegion.getArea()));
126}
127
128template<DammertzDispatcher::ERefineMode MODE>
130{
131 const auto nextRegions = analyzer.getNextRegions();
132 addAnalyzedRegion(nextRegions.first);
133 addAnalyzedRegion(nextRegions.second);
134}
135
136template<DammertzDispatcher::ERefineMode MODE>
138 const real splitThreshold,
139 const real terminateThreshold,
140 const real numFullRegionPixels) :
141
142 m_splitThreshold (splitThreshold),
143 m_terminateThreshold(terminateThreshold),
144 m_nextRegions (Region({0, 0}), Region({0, 0})),
145 m_rcpNumRegionPixels(1.0_r / numFullRegionPixels),
146 m_accumulatedEps ()
147{}
148
150{
151 return m_pendingRegions.size();
152}
153
154template<DammertzDispatcher::ERefineMode MODE>
155inline std::pair<Region, Region> DammertzDispatcher::TAnalyzer<MODE>::getNextRegions() const
156{
157 return m_nextRegions;
158}
159
160template<DammertzDispatcher::ERefineMode MODE>
162{
163 return !m_nextRegions.first.isArea() && !m_nextRegions.second.isArea();
164}
165
166template<>
168 const Region& finishedRegion,
169 const HdrRgbFrame& allEffortFrame,
170 const HdrRgbFrame& halfEffortFrame)
171{
172 using namespace math;
173
174 PH_ASSERT_GE(finishedRegion.getMinVertex().x(), 0);
175 PH_ASSERT_GE(finishedRegion.getMinVertex().y(), 0);
176 PH_ASSERT_LE(finishedRegion.getWidth(), allEffortFrame.widthPx());
177 PH_ASSERT_LE(finishedRegion.getHeight(), allEffortFrame.heightPx());
178 PH_ASSERT_LE(finishedRegion.getWidth(), halfEffortFrame.widthPx());
179 PH_ASSERT_LE(finishedRegion.getHeight(), halfEffortFrame.heightPx());
180 const TAABB2D<uint32> frameRegion(finishedRegion);
181
182 real regionError = 0;
183 for(uint32 y = frameRegion.getMinVertex().y(); y < frameRegion.getMaxVertex().y(); ++y)
184 {
185 for(uint32 x = frameRegion.getMinVertex().x(); x < frameRegion.getMaxVertex().x(); ++x)
186 {
188 allEffortFrame.getPixel(x, y, &I);
189 halfEffortFrame.getPixel(x, y, &A);
190
191 const real numerator = I.sub(A).abs().sum();
192 const real sumOfI = I.sum();
193 const real rcpDenominator = sumOfI > 0 ? math::fast_rcp_sqrt(sumOfI) : 0;
194
195 regionError += numerator * rcpDenominator;
196 }
197 }
198 regionError /= frameRegion.getArea();
199 regionError *= fast_sqrt(frameRegion.getArea() * m_rcpNumRegionPixels);
200 PH_ASSERT_MSG(std::isfinite(regionError), std::to_string(regionError));
201
202 if(regionError >= m_splitThreshold)
203 {
204 // error is large, added for more effort
205 m_nextRegions.first = finishedRegion;
206 m_nextRegions.second = Region({0, 0});
207 }
208 else if(regionError >= m_terminateThreshold)
209 {
210 if(finishedRegion.getArea() >= MIN_REGION_AREA)
211 {
212 // error is small, splitted and added for more effort
213 const auto maxDimension = finishedRegion.getExtents().maxDimension();
214 const int64 midPoint = (finishedRegion.getMinVertex()[maxDimension] + finishedRegion.getMaxVertex()[maxDimension]) / 2;
215
216 m_nextRegions = finishedRegion.getSplitted(maxDimension, midPoint);
217 }
218 else
219 {
220 m_nextRegions.first = finishedRegion;
221 m_nextRegions.second = Region({0, 0});
222 }
223 }
224 else
225 {
226 // error is very small, no further effort needed
227 m_nextRegions.first = Region({0, 0});
228 m_nextRegions.second = Region({0, 0});
229 }
230}
231
232template<>
234 const Region& finishedRegion,
235 const HdrRgbFrame& allEffortFrame,
236 const HdrRgbFrame& halfEffortFrame)
237{
238 using namespace math;
239
240 PH_ASSERT_GE(finishedRegion.getMinVertex().x(), 0);
241 PH_ASSERT_GE(finishedRegion.getMinVertex().y(), 0);
242 PH_ASSERT_LE(finishedRegion.getWidth(), allEffortFrame.widthPx());
243 PH_ASSERT_LE(finishedRegion.getHeight(), allEffortFrame.heightPx());
244 PH_ASSERT_LE(finishedRegion.getWidth(), halfEffortFrame.widthPx());
245 PH_ASSERT_LE(finishedRegion.getHeight(), halfEffortFrame.heightPx());
246 const TAABB2D<uint32> frameRegion(finishedRegion);
247
248 const auto regionExtents = frameRegion.getExtents();
249 const auto maxDimension = regionExtents.maxDimension();
250
251 m_accumulatedEps.resize(regionExtents[maxDimension]);
252 std::fill(m_accumulatedEps.begin(), m_accumulatedEps.end(), 0.0_r);
253
254 real summedEp = 0;
255 for(uint32 y = frameRegion.getMinVertex().y(); y < frameRegion.getMaxVertex().y(); ++y)
256 {
257 real summedRowEp = 0;
258 for(uint32 x = frameRegion.getMinVertex().x(); x < frameRegion.getMaxVertex().x(); ++x)
259 {
261 allEffortFrame.getPixel(x, y, &I);
262 halfEffortFrame.getPixel(x, y, &A);
263
264 const real numerator = I.sub(A).abs().sum();
265 const real sumOfI = I.sum();
266 const real rcpDenominator = sumOfI > 0 ? fast_rcp_sqrt(sumOfI) : 0;
267
268 PH_ASSERT_GE(numerator * rcpDenominator, 0);
269 summedRowEp += numerator * rcpDenominator;
270
271 if(maxDimension == constant::X_AXIS)
272 {
273 m_accumulatedEps[x - frameRegion.getMinVertex().x()] += summedRowEp;
274 }
275 }
276 summedEp += summedRowEp;
277
278 if(maxDimension == constant::Y_AXIS)
279 {
280 m_accumulatedEps[y - frameRegion.getMinVertex().y()] = summedEp;
281 }
282 }
283
284 real regionError = summedEp;
285 regionError /= frameRegion.getArea();
286 regionError *= fast_sqrt(frameRegion.getArea() * m_rcpNumRegionPixels);
287 PH_ASSERT_MSG(regionError > 0 && std::isfinite(regionError), std::to_string(regionError));
288
289 if(regionError >= m_splitThreshold)
290 {
291 // error is large, added for more effort
292 m_nextRegions.first = finishedRegion;
293 m_nextRegions.second = Region({0, 0});
294 }
295 else if(regionError >= m_terminateThreshold)
296 {
297 if(finishedRegion.getArea() >= MIN_REGION_AREA)
298 {
299 // Split on the point that minimizes the difference of error
300 // across two splitted regions. To find the point, we squared the
301 // error metric (to avoid sqrt) and stripped away some constants
302 // which do not affect the result.
303
304 const real totalEps = m_accumulatedEps.back();
305
306 int64 bestPosPx = 0;
307 real minErrorDiff = totalEps * fast_rcp_sqrt(static_cast<real>(m_accumulatedEps.size()));
308 for(std::size_t i = 0; i < m_accumulatedEps.size(); ++i)
309 {
310 const real summedEp0 = m_accumulatedEps[i];
311 const real summedEp1 = totalEps - summedEp0;
312 PH_ASSERT_GE(summedEp0, 0);
313 PH_ASSERT_GE(summedEp1, 0);
314
315 const real error0 = summedEp0 * fast_rcp_sqrt(static_cast<real>(i + 1));
316 const real error1 = summedEp1 * (i != m_accumulatedEps.size() - 1 ?
317 fast_rcp_sqrt(static_cast<real>(m_accumulatedEps.size() - i - 1)) : 0);
318 const real errorDiff = std::abs(error0 - error1);
319
320 if(errorDiff < minErrorDiff)
321 {
322 minErrorDiff = errorDiff;
323 bestPosPx = static_cast<int64>(i + 1);
324 }
325 }
326
327 m_nextRegions = finishedRegion.getSplitted(
328 maxDimension,
329 finishedRegion.getMinVertex()[maxDimension] + bestPosPx);
330 }
331 else
332 {
333 m_nextRegions.first = finishedRegion;
334 m_nextRegions.second = Region({0, 0});
335 }
336 }
337 else
338 {
339 // error is very small, no further effort needed
340 m_nextRegions.first = Region({0, 0});
341 m_nextRegions.second = Region({0, 0});
342 }
343}
344
345inline void DammertzDispatcher::addAnalyzedRegion(const Region& region)
346{
347 if(region.isArea())
348 {
349 if(region.getArea() <= MIN_REGION_AREA)
350 {
351 m_pendingRegions.push(WorkUnit(region, m_terminusDepthPerRegion));
352 }
353 else
354 {
355 m_pendingRegions.push(WorkUnit(region, m_standardDepthPerRegion));
356 }
357 }
358}
359
360}// end namespace ph
Definition DammertzDispatcher.h:78
void analyzeFinishedRegion(const Region &finishedRegion, const HdrRgbFrame &allEffortFrame, const HdrRgbFrame &halfEffortFrame)
bool isConverged() const
Definition DammertzDispatcher.h:161
Definition DammertzDispatcher.h:35
bool dispatch(WorkUnit *out_workUnit) override
Get some amount of work.
Definition DammertzDispatcher.cpp:64
ERefineMode
Definition DammertzDispatcher.h:38
std::size_t numPendingRegions() const
Definition DammertzDispatcher.h:149
void addAnalyzedData(const TAnalyzer< MODE > &analyzer)
Definition DammertzDispatcher.h:129
DammertzDispatcher()=default
TAnalyzer< MODE > createAnalyzer() const
Definition DammertzDispatcher.h:120
A manager that distributes work.
Definition IWorkDispatcher.h:14
uint32 heightPx() const
Definition TFrame.ipp:440
uint32 widthPx() const
Definition TFrame.ipp:434
PixelType getPixel(const math::TVector2< uint32 > &coordPx) const
Definition TFrame.ipp:343
Represents some amount of work.
Definition WorkUnit.h:17
std::pair< TAABB2D, TAABB2D > getSplitted(std::size_t axis, T splitPoint) const
Definition TAABB2D.ipp:169
TVector2< T > getExtents() const
Get the side lengths of the bound.
Definition TAABB2D.ipp:150
T getHeight() const
Definition TAABB2D.ipp:144
T getWidth() const
Definition TAABB2D.ipp:138
bool isArea() const
Definition TAABB2D.ipp:205
const TVector2< T > & getMaxVertex() const
Definition TAABB2D.ipp:126
T getArea() const
Definition TAABB2D.ipp:69
const TVector2< T > & getMinVertex() const
Definition TAABB2D.ipp:120
T sum() const
Definition TArithmeticArrayBase.ipp:336
Derived sub(const Derived &rhs) const
Definition TArithmeticArrayBase.ipp:62
Definition TArithmeticArray.h:13
T & x()
Definition TVector2.ipp:38
T & y()
Definition TVector2.ipp:44
std::size_t maxDimension() const
Definition TVectorNBase.ipp:81
Miscellaneous math utilities.
float fast_rcp_sqrt(float x)
Computes 1/sqrt(x) in a fast but approximative way.
Definition math.h:407
float fast_sqrt(const float x)
Computes sqrt(x) in a fast but approximative way.
Definition math.h:436
The root for all renderer implementations.
Definition EEngineProject.h:6
math::TAABB2D< int64 > Region
Definition Region.h:8