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