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) 2019 Stephen Agyemang, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28
29 // Interface header.
30 #include "texturecontrolledpixelrenderer.h"
31
32 // appleseed.renderer headers.
33 #include "renderer/global/globallogger.h"
34 #include "renderer/global/globaltypes.h"
35 #include "renderer/kernel/aov/aovaccumulator.h"
36 #include "renderer/kernel/aov/imagestack.h"
37 #include "renderer/kernel/rendering/isamplerenderer.h"
38 #include "renderer/kernel/rendering/pixelcontext.h"
39 #include "renderer/kernel/rendering/pixelrendererbase.h"
40 #include "renderer/kernel/rendering/shadingresultframebuffer.h"
41 #include "renderer/kernel/shading/shadingresult.h"
42 #include "renderer/modeling/aov/aov.h"
43 #include "renderer/modeling/aov/pixelsamplecountaov.h"
44 #include "renderer/modeling/frame/frame.h"
45 #include "renderer/utility/settingsparsing.h"
46
47 // appleseed.foundation headers.
48 #include "foundation/image/canvasproperties.h"
49 #include "foundation/image/image.h"
50 #include "foundation/math/aabb.h"
51 #include "foundation/math/filtersamplingtable.h"
52 #include "foundation/math/hash.h"
53 #include "foundation/math/population.h"
54 #include "foundation/math/scalar.h"
55 #include "foundation/math/vector.h"
56 #include "foundation/platform/types.h"
57 #include "foundation/utility/autoreleaseptr.h"
58 #include "foundation/utility/containers/dictionary.h"
59 #include "foundation/utility/statistics.h"
60
61 // OpenImageIO headers.
62 #include "foundation/platform/_beginoiioheaders.h"
63 #include "OpenImageIO/imagebufalgo.h"
64 #include "OpenImageIO/typedesc.h"
65 #include "foundation/platform/_endoiioheaders.h"
66
67 // Standard headers.
68 #include <cmath>
69
70 using namespace foundation;
71 using namespace OIIO;
72
73 namespace renderer
74 {
75
76 namespace
77 {
78 //
79 // Texture-controlled pixel renderer.
80 //
81
82 class TextureControlledPixelRenderer
83 : public PixelRendererBase
84 {
85 public:
TextureControlledPixelRenderer(const Frame & frame,const std::shared_ptr<const ImageBuf> texture,ISampleRendererFactory * factory,const ParamArray & params,const size_t thread_index)86 TextureControlledPixelRenderer(
87 const Frame& frame,
88 const std::shared_ptr<const ImageBuf> texture,
89 ISampleRendererFactory* factory,
90 const ParamArray& params,
91 const size_t thread_index)
92 : m_params(params)
93 , m_texture(texture)
94 , m_sample_renderer(factory->create(thread_index))
95 , m_min_sample_count(m_params.m_min_samples)
96 , m_max_sample_count(m_params.m_max_samples)
97 {
98 const size_t sample_aov_index = frame.aovs().get_index("pixel_sample_count");
99
100 // If the sample count AOV is enabled, we need to reset its normalization
101 // range in case an adaptive render was done previously.
102 if (sample_aov_index != ~size_t(0))
103 {
104 PixelSampleCountAOV* sample_aov =
105 static_cast<PixelSampleCountAOV*>(
106 frame.aovs().get_by_index(sample_aov_index));
107
108 sample_aov->set_normalization_range(0, 0);
109 }
110 }
111
release()112 void release() override
113 {
114 delete this;
115 }
116
print_settings() const117 void print_settings() const override
118 {
119 RENDERER_LOG_INFO(
120 "texture-controlled pixel renderer settings:\n"
121 " min samples %s\n"
122 " max samples %s\n"
123 " force anti-aliasing %s\n",
124 pretty_uint(m_params.m_min_samples).c_str(),
125 pretty_uint(m_params.m_max_samples).c_str(),
126 m_params.m_force_aa ? "on" : "off");
127
128 m_sample_renderer->print_settings();
129 }
130
render_pixel(const Frame & frame,Tile & tile,TileStack & aov_tiles,const AABB2i & tile_bbox,const uint32 pass_hash,const Vector2i & pi,const Vector2i & pt,AOVAccumulatorContainer & aov_accumulators,ShadingResultFrameBuffer & framebuffer)131 void render_pixel(
132 const Frame& frame,
133 Tile& tile,
134 TileStack& aov_tiles,
135 const AABB2i& tile_bbox,
136 const uint32 pass_hash,
137 const Vector2i& pi,
138 const Vector2i& pt,
139 AOVAccumulatorContainer& aov_accumulators,
140 ShadingResultFrameBuffer& framebuffer) override
141 {
142 const size_t aov_count = frame.aov_images().size();
143
144 on_pixel_begin(frame, pi, pt, tile_bbox, aov_accumulators);
145
146 // Create a sampling context.
147 const size_t frame_width = frame.image().properties().m_canvas_width;
148 const size_t pixel_index = pi.y * frame_width + pi.x;
149 const size_t instance = hash_uint32(static_cast<uint32>(pass_hash + pixel_index));
150 SamplingContext::RNGType rng(pass_hash, instance);
151 SamplingContext sampling_context(
152 rng,
153 m_params.m_sampling_mode,
154 2, // number of dimensions
155 0, // number of samples -- unknown
156 instance); // initial instance number
157
158 const ROI roi(
159 pi.x,
160 pi.x + 1,
161 pi.y,
162 pi.y + 1,
163 0,
164 1,
165 0,
166 1);
167
168 float pixel_red_channel;
169 m_texture->get_pixels(roi, TypeDesc::TypeFloat, &pixel_red_channel);
170
171 const size_t sample_count =
172 round<size_t>(
173 foundation::lerp( // qualifier needed
174 static_cast<float>(m_min_sample_count),
175 static_cast<float>(m_max_sample_count),
176 pixel_red_channel));
177
178 for (size_t i = 0; i < sample_count; ++i)
179 {
180 // Generate a uniform sample in [0,1)^2.
181 const Vector2f s =
182 m_max_sample_count > 1 || m_params.m_force_aa
183 ? sampling_context.next2<Vector2f>()
184 : Vector2f(0.5f);
185
186 // Sample the pixel filter.
187 const auto& filter_table = frame.get_filter_sampling_table();
188 const Vector2d pf(
189 static_cast<double>(filter_table.sample(s[0]) + 0.5f),
190 static_cast<double>(filter_table.sample(s[1]) + 0.5f));
191
192 // Compute the sample position in NDC.
193 const Vector2d sample_position = frame.get_sample_position(pi.x + pf.x, pi.y + pf.y);
194
195 // Create a pixel context that identifies the pixel and sample currently being rendered.
196 const PixelContext pixel_context(pi, sample_position);
197
198 // Render the sample.
199 ShadingResult shading_result(aov_count);
200 SamplingContext child_sampling_context(sampling_context);
201 m_sample_renderer->render_sample(
202 child_sampling_context,
203 pixel_context,
204 sample_position,
205 aov_accumulators,
206 shading_result);
207
208 // Update sampling statistics.
209 m_total_sampling_dim.insert(child_sampling_context.get_total_dimension());
210
211 // Merge the sample into the framebuffer.
212 if (shading_result.is_valid())
213 framebuffer.add(Vector2u(pt), shading_result);
214 else
215 signal_invalid_sample();
216 }
217
218 on_pixel_end(frame, pi, pt, tile_bbox, aov_accumulators);
219 }
220
get_statistics() const221 StatisticsVector get_statistics() const override
222 {
223 Statistics stats;
224 stats.insert("max sampling dimension", m_total_sampling_dim);
225
226 StatisticsVector vec;
227 vec.insert("generic sample generator statistics", stats);
228 vec.merge(m_sample_renderer->get_statistics());
229
230 return vec;
231 }
232
get_max_samples_per_pixel() const233 size_t get_max_samples_per_pixel() const override
234 {
235 return m_max_sample_count;
236 }
237
238 private:
239 struct Parameters
240 {
241 const SamplingContext::Mode m_sampling_mode;
242 const size_t m_min_samples;
243 const size_t m_max_samples;
244 const bool m_force_aa;
245
Parametersrenderer::__anond2ed27790111::TextureControlledPixelRenderer::Parameters246 explicit Parameters(const ParamArray& params)
247 : m_sampling_mode(get_sampling_context_mode(params))
248 , m_min_samples(params.get_required<size_t>("min_samples", 0))
249 , m_max_samples(params.get_required<size_t>("max_samples", 64))
250 , m_force_aa(params.get_optional<bool>("force_antialiasing", false))
251 {
252 }
253 };
254
255 const Parameters m_params;
256 const std::shared_ptr<const ImageBuf> m_texture;
257 auto_release_ptr<ISampleRenderer> m_sample_renderer;
258 const size_t m_min_sample_count;
259 const size_t m_max_sample_count;
260 Population<uint64> m_total_sampling_dim;
261 };
262 }
263
264
265 //
266 // TextureControlledPixelRendererFactory class implementation.
267 //
268
get_params_metadata()269 Dictionary TextureControlledPixelRendererFactory::get_params_metadata()
270 {
271 Dictionary metadata;
272
273 metadata.dictionaries().insert(
274 "min_samples",
275 Dictionary()
276 .insert("type", "int")
277 .insert("default", "0")
278 .insert("label", "Minimum Samples")
279 .insert("help", "Minimum number of anti-aliasing samples"));
280
281 metadata.dictionaries().insert(
282 "max_samples",
283 Dictionary()
284 .insert("type", "int")
285 .insert("default", "64")
286 .insert("label", "Maximum Samples")
287 .insert("help", "Maximum number of anti-aliasing samples"));
288
289 metadata.dictionaries().insert(
290 "file_path",
291 Dictionary()
292 .insert("type", "text")
293 .insert("default", "")
294 .insert("label", "File Path")
295 .insert("help", "Path to a black and white texture used to determine the sample count"));
296
297 metadata.dictionaries().insert(
298 "force_antialiasing",
299 Dictionary()
300 .insert("type", "bool")
301 .insert("default", "true")
302 .insert("label", "Force Anti-Aliasing")
303 .insert(
304 "help",
305 "When using 1 sample/pixel and Force Anti-Aliasing is disabled, samples are placed at the center of pixels"));
306
307 return metadata;
308 }
309
TextureControlledPixelRendererFactory(const Frame & frame,ISampleRendererFactory * factory,const ParamArray & params)310 TextureControlledPixelRendererFactory::TextureControlledPixelRendererFactory(
311 const Frame& frame,
312 ISampleRendererFactory* factory,
313 const ParamArray& params)
314 : m_frame(frame)
315 , m_factory(factory)
316 , m_params(params)
317 {
318 }
319
load_texture(const std::string & texture_path)320 bool TextureControlledPixelRendererFactory::load_texture(const std::string& texture_path)
321 {
322 // Create a new dedicated ImageCache instead of using the global ImageCache.
323 // This is to prevent errors during ImageBuf access if the file behind tex_path was overwritten on disk.
324 ImageCache* image_cache = ImageCache::create();
325 std::unique_ptr<ImageBuf> texture(new ImageBuf(texture_path, 0, 0, image_cache));
326
327 // Force the read to immediately do the entire disk I/O for the file.
328 // We can then safely destroy the cache since the entire image data has been read into the ImageBuf.
329 const bool read_successful = texture->read(0, 0, true);
330 ImageCache::destroy(image_cache);
331
332 if (!read_successful)
333 return false;
334
335 const CanvasProperties& frame_props = m_frame.image().properties();
336 const ROI roi(
337 0, // xbegin
338 static_cast<int>(frame_props.m_canvas_width), // xend
339 0, // ybegin
340 static_cast<int>(frame_props.m_canvas_height), // yend
341 0, // zbegin
342 1, // zend
343 0, // chbegin
344 texture->nchannels()); // chend
345
346 m_texture = std::make_shared<ImageBuf>();
347 return ImageBufAlgo::resize(*m_texture.get(), *texture.get(), "", 0.0f, roi);
348 }
349
release()350 void TextureControlledPixelRendererFactory::release()
351 {
352 delete this;
353 }
354
create(const size_t thread_index)355 IPixelRenderer* TextureControlledPixelRendererFactory::create(
356 const size_t thread_index)
357 {
358 assert(m_texture);
359
360 return new TextureControlledPixelRenderer(m_frame, m_texture, m_factory, m_params, thread_index);
361 }
362
363 } // namespace renderer
364