Program Listing for File scene_previewer.hpp

Return to documentation for file (/home/runner/work/wave_tracer/wave_tracer/include/wt/scene/scene_previewer.hpp)

/*
*
* wave tracer
* Copyright  Shlomi Steinberg
*
* LICENSE: Creative Commons Attribution-NonCommercial 4.0 International
*
*/

#pragma once

#include <string>
#include <map>
#include <chrono>
#include <thread>
#include <atomic>

#include <wt/scene/scene_renderer.hpp>

#include <wt/math/common.hpp>
#include <wt/bitmap/bitmap.hpp>

#include <wt/util/preview/preview_interface.hpp>

#include <wt/sensor/film/film_storage.hpp>
#include <wt/sensor/response/tonemap/tonemap.hpp>

namespace wt::scene {

class scene_previewer_t {
private:
    struct alignas(64) sensor_t {
        const sensor::film_storage_handle_t* film_handle = nullptr;

        mutable std::atomic_flag preview_processed = true;
        mutable f_t fractional_spe_complete = 0;
    };
    struct alignas(64) termination_signal_t {
        std::condition_variable signal;
        bool flag = false;

        std::size_t padding = 0;
        std::mutex m;
    };

    const preview_interface_t* previewer;
    std::map<std::string, std::unique_ptr<sensor_t>> sensors;

    std::thread preview_thread;
    mutable termination_signal_t terminate_flag;

public:
    using sensors_map_t = std::map<std::string, const sensor::film_storage_handle_t*>;

private:
    void preview(const std::string& name, const sensor_t& s) const noexcept {
        // TODO: preview for non-2d sensors
        if (s.film_handle->dimensions_count()!=2) return;

        const auto spe = (std::size_t)(m::round(s.fractional_spe_complete) + f_t(.5));
        if (s.film_handle->is_polarimetric() && previewer->polarimetric_preview()) {
            previewer->update(name,
                              s.film_handle->develop_lin_stokes_d2(spe),
                              s.fractional_spe_complete,
                              s.film_handle->get_tonemap());
        } else {
            previewer->update(name,
                              s.film_handle->develop_lin_d2(spe),
                              s.fractional_spe_complete,
                              s.film_handle->get_tonemap());
        }
    }

    bool process_previews() const noexcept {
        bool updated = false;
        for (const auto& sensor : sensors) {
            if (sensor.second->preview_processed.test_and_set(std::memory_order_acquire))
                continue;
            preview(sensor.first, *sensor.second);
            updated = true;
        }

        return updated;
    }

    void runner() const noexcept {
        using namespace std::chrono;

        auto last_update_duration = steady_clock::duration{ 0 };

        // process any initial preview requests
        process_previews();

        auto last_update_ts = steady_clock::now();

        for (;!terminate_flag.flag;) {
            const auto preview_delay = previewer->preview_update_interval();
            const auto preview_rate_limit = previewer->preview_update_rate_limit_factor();

            {
                // sleep
                std::unique_lock l(terminate_flag.m);
                terminate_flag.signal.wait_for(l, preview_delay);
            }

            const auto start = steady_clock::now();
            const auto d = start-last_update_ts;
            // ratelimit: sleep for at least `preview_rate_limit' times the duration it took to update last time
            // this avoids excessively using up resources for previews, especially when films are large.
            if (d>=preview_delay &&
                d>=preview_rate_limit*last_update_duration &&
                previewer->available()) {
                // do previews
                const bool updated = process_previews();
                if (updated) {
                    last_update_ts = start;
                    last_update_duration = steady_clock::now() - start;
                }
            }
        }

        // wrap up any outstanding preview requests
        process_previews();
    }

    void terminate() noexcept {
        {
            std::unique_lock l(terminate_flag.m);
            terminate_flag.flag = true;
        }
        terminate_flag.signal.notify_all();
    }

public:
    scene_previewer_t(const render_opts_t& opts,
                      const sensors_map_t& sensors)
        : previewer(opts.previewer)
    {
        for (const auto& s : sensors)
            this->sensors.emplace(s.first, std::make_unique<sensor_t>(s.second));

        preview_thread = std::thread([this]() { this->runner(); });
    }
    ~scene_previewer_t() {
        terminate();
        preview_thread.join();
    }

    void preview(const std::string& sensor_name,
                 const f_t fractional_spe_complete) const noexcept {
        const auto it = sensors.find(sensor_name);
        if (it==sensors.end()) {
            assert(false);
            return;
        }

        const auto& s = *it->second;
        s.fractional_spe_complete = fractional_spe_complete;
        s.preview_processed.clear(std::memory_order_release);
    }
};

}