1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29 
30 // Interface header.
31 #include "samplegeneratorjob.h"
32 
33 // appleseed.renderer headers.
34 #include "renderer/global/globallogger.h"
35 #include "renderer/kernel/rendering/isamplegenerator.h"
36 #include "renderer/kernel/rendering/progressive/samplecounter.h"
37 #include "renderer/kernel/rendering/sampleaccumulationbuffer.h"
38 
39 // appleseed.foundation headers.
40 #include "foundation/image/canvasproperties.h"
41 #include "foundation/image/image.h"
42 #include "foundation/math/scalar.h"
43 #include "foundation/platform/defaulttimers.h"
44 #include "foundation/utility/stopwatch.h"
45 
46 // Standard headers.
47 #include <algorithm>
48 #include <cmath>
49 
50 using namespace foundation;
51 using namespace std;
52 
53 namespace renderer
54 {
55 
56 //#define PRINT_DETAILED_PROGRESS
57 
SampleGeneratorJob(SampleAccumulationBuffer & buffer,ISampleGenerator * sample_generator,SampleCounter & sample_counter,const Spectrum::Mode spectrum_mode,JobQueue & job_queue,const size_t job_index,IAbortSwitch & abort_switch)58 SampleGeneratorJob::SampleGeneratorJob(
59     SampleAccumulationBuffer&   buffer,
60     ISampleGenerator*           sample_generator,
61     SampleCounter&              sample_counter,
62     const Spectrum::Mode        spectrum_mode,
63     JobQueue&                   job_queue,
64     const size_t                job_index,
65     IAbortSwitch&               abort_switch)
66   : m_buffer(buffer)
67   , m_sample_generator(sample_generator)
68   , m_sample_counter(sample_counter)
69   , m_spectrum_mode(spectrum_mode)
70   , m_job_queue(job_queue)
71   , m_job_index(job_index)
72   , m_abort_switch(abort_switch)
73 {
74 }
75 
76 namespace
77 {
78     // Duration of the interruptible phase.
79     // The higher this value, the more refined is the image but the lower is the interactivity.
80     const uint64 SamplesInUninterruptiblePhase = 32 * 32 * 2;
81 
82     // Duration of the linear phase.
83     const uint64 SamplesInLinearPhase = 500 * 1000;
84 
85     // Refinement rates of the image in the different phases.
86     // The higher the refinement rates, the lower the parallelism / CPU efficiency.
87     const uint64 SamplesPerJobInLinearPhase = 250;
88     const uint64 MaxSamplesPerJobInExponentialPhase = 250 * 1000;
89 
90     // Shape of the curve in the exponential phase.
91     const double CurveExponentInExponentialPhase = 2.0;
92 
93     // Constraints.
94     static_assert(
95         SamplesInUninterruptiblePhase >= SamplesPerJobInLinearPhase,
96         "In renderer::SampleGeneratorJob, the uninterruptible phase must have at least as many samples as the linear phase");
97 }
98 
samples_to_samples_per_job(const uint64 samples)99 uint64 SampleGeneratorJob::samples_to_samples_per_job(const uint64 samples)
100 {
101     if (samples < SamplesInLinearPhase)
102         return SamplesPerJobInLinearPhase;
103 
104     const double x = (samples - SamplesInLinearPhase) * 0.001;
105     const uint64 y = SamplesPerJobInLinearPhase + truncate<uint64>(pow(x, CurveExponentInExponentialPhase));
106 
107     return min(y, MaxSamplesPerJobInExponentialPhase);
108 }
109 
execute(const size_t thread_index)110 void SampleGeneratorJob::execute(const size_t thread_index)
111 {
112     // Initialize thread-local variables.
113     Spectrum::set_mode(m_spectrum_mode);
114 
115 #ifdef PRINT_DETAILED_PROGRESS
116     Stopwatch<DefaultWallclockTimer> stopwatch(0);
117     stopwatch.measure();
118     const double t1 = stopwatch.get_seconds();
119 #endif
120 
121     // We will base the number of samples to be rendered by this job on
122     // the number of samples already reserved (not necessarily rendered).
123     const uint64 current_sample_count = m_sample_counter.read();
124 
125     // Reserve a number of samples to be rendered by this job.
126     const uint64 acquired_sample_count =
127         m_sample_counter.reserve(
128             samples_to_samples_per_job(current_sample_count));
129 
130     // Terminate this job is there are no more samples to render.
131     if (acquired_sample_count == 0)
132         return;
133 
134     // The first phase is uninterruptible in order to always have something to
135     // show during navigation. todo: the renderer freezes if it cannot generate
136     // samples during this phase; fix.
137     const bool abortable = current_sample_count > SamplesInUninterruptiblePhase;
138 
139     // Render the samples and store them into the accumulation buffer.
140     if (abortable)
141     {
142         m_sample_generator->generate_samples(
143             static_cast<size_t>(acquired_sample_count),
144             m_buffer,
145             m_abort_switch);
146     }
147     else
148     {
149         AbortSwitch no_abort;
150         m_sample_generator->generate_samples(
151             static_cast<size_t>(acquired_sample_count),
152             m_buffer,
153             no_abort);
154     }
155 
156 #ifdef PRINT_DETAILED_PROGRESS
157     stopwatch.measure();
158     const double t2 = stopwatch.get_seconds();
159 
160     RENDERER_LOG_DEBUG(
161         "job " FMT_SIZE_T ", rendered " FMT_UINT64 " samples%s, in %s",
162         m_job_index,
163         acquired_sample_count,
164         abortable ? "" : " (non-abortable)",
165         pretty_time(t2 - t1).c_str());
166 #endif
167 
168     // Reschedule this job.
169     if (!abortable || !m_abort_switch.is_aborted())
170         m_job_queue.schedule(this, false);
171 }
172 
173 }   // namespace renderer
174