// // starfield/Controller.h // interface // // Created by Tobias Schwinger on 3/29/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // #ifndef __interface__starfield__Controller__ #define __interface__starfield__Confroller__ #ifndef __interface__Starfield_impl__ #error "This is an implementation file - not intended for direct inclusion." #endif // // Data pipeline // ============= // // ->> readInput -(load)--+---- (get brightness & sort) ---> brightness LUT // | | // ->> setResolution --+ | >extractBrightnessLevels< // V | // (sort by (tile,brightness)) // | | // ->> setLOD ---+ | >retile< ->> setLOD --> (just parameterize // V V when enough data on-GPU) // (filter by max-LOD brightness, // build tile info for rendering) // | | // V >recreateRenderer< // (set new renderer)/ // // // (process), ->> entry point, ---> data flow, >internal routine< // // (member functions are ordered by data flow) // // Still open // ========== // // o atomics/mutexes need to be added as annotated in the source to allow // concurrent threads to pull the strings to e.g. have a low priority // thread run the data pipeline for update -- rendering is wait-free // #include "starfield/data/InputVertex.h" #include "starfield/data/BrightnessLevel.h" #include "starfield/Loader.h" #include "starfield/renderer/Renderer.h" #include "starfield/renderer/VertexOrder.h" namespace starfield { class Controller { InputVertices _seqInput; #if STARFIELD_MULTITHREADING mutex _mtxInput; atomic _valTileResolution; mutex _mtxLodState; #else unsigned _valTileResolution; #endif double _valLodFraction; double _valLodLowWaterMark; double _valLodHighWaterMark; double _valLodOveralloc; size_t _valLodNalloc; size_t _valLodNrender; BrightnessLevels _seqLodBrightness; #if STARFIELD_MULTITHREADING atomic _valLodBrightness; BrightnessLevel _valLodAllocBrightness; atomic _ptrRenderer; typedef lock_guard lock; #else BrightnessLevel _valLodBrightness; BrightnessLevel _valLodAllocBrightness; Renderer* _ptrRenderer; #define lock #define _(x) #endif static inline size_t toBufSize(double f) { return size_t(floor(f + 0.5f)); } public: Controller() : _valTileResolution(20), _valLodFraction(1.0), _valLodLowWaterMark(0.8), _valLodHighWaterMark(1.0), _valLodOveralloc(1.2), _valLodNalloc(0), _valLodNrender(0), _valLodBrightness(0), _valLodAllocBrightness(0), _ptrRenderer(0l) { } bool readInput(const char* url, unsigned limit) { InputVertices vertices; if (! Loader().loadVertices(vertices, url, limit)) return false; BrightnessLevels brightness; extractBrightnessLevels(brightness, vertices); // input is read, now run the entire data pipeline on the new input { lock _(_mtxInput); _seqInput.swap(vertices); #if STARFIELD_MULTITHREADING unsigned k = _valTileResolution.load(memory_order_relaxed); #else unsigned k = _valTileResolution; #endif size_t n, nRender; BrightnessLevel bMin, b; double rcpChange; // we'll have to build a new LOD state for a new total N, // ideally keeping allocation size and number of vertices { lock _(_mtxLodState); size_t newLast = _seqInput.size() - 1; // reciprocal change N_old/N_new tells us how to scale // the fractions rcpChange = min(1.0, double(vertices.size()) / _seqInput.size()); // initialization? use defaults / previously set values if (rcpChange == 0.0) { rcpChange = 1.0; nRender = toBufSize(_valLodFraction * newLast); n = min(newLast, toBufSize(_valLodOveralloc * nRender)); } else { // cannot allocate or render more than we have n = min(newLast, _valLodNalloc); nRender = min(newLast, _valLodNrender); } // determine new minimum brightness levels bMin = brightness[n]; b = brightness[nRender]; // adjust n n = std::upper_bound( brightness.begin() + n - 1, brightness.end(), bMin, GreaterBrightness() ) - brightness.begin(); } // invoke next stage try { this->retile(n, k, b, bMin); } catch (...) { // rollback transaction and rethrow vertices.swap(_seqInput); throw; } // finally publish the new LOD state { lock _(_mtxLodState); _seqLodBrightness.swap(brightness); _valLodFraction *= rcpChange; _valLodLowWaterMark *= rcpChange; _valLodHighWaterMark *= rcpChange; _valLodOveralloc *= rcpChange; _valLodNalloc = n; _valLodNrender = nRender; _valLodAllocBrightness = bMin; #if STARFIELD_MULTITHREADING _valLodBrightness.store(b, memory_order_relaxed); #else _valLodBrightness = b; #endif } } return true; } bool setResolution(unsigned k) { if (k <= 3) { return false; } // fprintf(stderr, "Stars.cpp: setResolution(%d)\n", k); #if STARFIELD_MULTITHREADING if (k != _valTileResolution.load(memory_order_relaxed)) #else if (k != _valTileResolution) #endif { lock _(_mtxInput); unsigned n; BrightnessLevel b, bMin; { lock _(_mtxLodState); n = _valLodNalloc; #if STARFIELD_MULTITHREADING b = _valLodBrightness.load(memory_order_relaxed); #else b = _valLodBrightness; #endif bMin = _valLodAllocBrightness; } this->retile(n, k, b, bMin); return true; } else { return false; } } private: void retile(size_t n, unsigned k, BrightnessLevel b, BrightnessLevel bMin) { Tiling tiling(k); VertexOrder scanner(tiling); radix2InplaceSort(_seqInput.begin(), _seqInput.end(), scanner); // fprintf(stderr, // "Stars.cpp: recreateRenderer(%d, %d, %d, %d)\n", n, k, b, bMin); recreateRenderer(n, k, b, bMin); _valTileResolution = k; } public: double changeLOD(double factor, double overalloc, double realloc) { assert(overalloc >= realloc && realloc >= 0.0); assert(overalloc <= 1.0 && realloc <= 1.0); // fprintf(stderr, // "Stars.cpp: changeLOD(%lf, %lf, %lf)\n", factor, overalloc, realloc); size_t n, nRender; BrightnessLevel bMin, b; double fraction, lwm, hwm; { lock _(_mtxLodState); // acuire a consistent copy of the current LOD state fraction = _valLodFraction; lwm = _valLodLowWaterMark; hwm = _valLodHighWaterMark; size_t last = _seqLodBrightness.size() - 1; // apply factor fraction = max(0.0, min(1.0, fraction * factor)); // calculate allocation size and corresponding brightness // threshold double oaFract = std::min(fraction * (1.0 + overalloc), 1.0); n = toBufSize(oaFract * last); bMin = _seqLodBrightness[n]; n = std::upper_bound( _seqLodBrightness.begin() + n - 1, _seqLodBrightness.end(), bMin, GreaterBrightness() ) - _seqLodBrightness.begin(); // also determine number of vertices to render and brightness nRender = toBufSize(fraction * last); // Note: nRender does not have to be accurate b = _seqLodBrightness[nRender]; // this setting controls the renderer, also keep b as the // brightness becomes volatile as soon as the mutex is // released, so keep b #if STARFIELD_MULTITHREADING _valLodBrightness.store(b, memory_order_relaxed); #else _valLodBrightness = b; #endif // fprintf(stderr, "Stars.cpp: " // "fraction = %lf, oaFract = %lf, n = %d, n' = %d, bMin = %d, b = %d\n", // fraction, oaFract, toBufSize(oaFract * last)), n, bMin, b); // will not have to reallocate? set new fraction right away // (it is consistent with the rest of the state in this case) if (fraction >= _valLodLowWaterMark && fraction <= _valLodHighWaterMark) { _valLodFraction = fraction; return fraction; } } // reallocate { lock _(_mtxInput); recreateRenderer(n, _valTileResolution, b, bMin); // fprintf(stderr, "Stars.cpp: LOD reallocation\n"); // publish new lod state { lock _(_mtxLodState); _valLodNalloc = n; _valLodNrender = nRender; _valLodFraction = fraction; _valLodLowWaterMark = fraction * (1.0 - realloc); _valLodHighWaterMark = fraction * (1.0 + realloc); _valLodOveralloc = fraction * (1.0 + overalloc); _valLodAllocBrightness = bMin; } } return fraction; } private: void recreateRenderer(size_t n, unsigned k, BrightnessLevel b, BrightnessLevel bMin) { #if STARFIELD_MULTITHREADING delete _ptrRenderer.exchange(new Renderer(_seqInput, n, k, b, bMin) ); #else delete _ptrRenderer; _ptrRenderer = new Renderer(_seqInput, n, k, b, bMin); #endif } public: void render(float perspective, float angle, mat4 const& orientation) { #if STARFIELD_MULTITHREADING // check out renderer Renderer* renderer = _ptrRenderer.exchange(0l); #else Renderer* renderer = _ptrRenderer; #endif // have it render if (renderer != 0l) { #if STARFIELD_MULTITHREADING BrightnessLevel b = _valLodBrightness.load(memory_order_relaxed); #else BrightnessLevel b = _valLodBrightness; #endif renderer->render(perspective, angle, orientation, b); } #if STARFIELD_MULTITHREADING // check in - or dispose if there is a new one Renderer* newOne = 0l; if (! _ptrRenderer.compare_exchange_strong(newOne, renderer)) { assert(!! newOne); delete renderer; } #else # undef lock # undef _ #endif } private: struct BrightnessSortScanner : Radix2IntegerScanner { typedef Radix2IntegerScanner base; BrightnessSortScanner() : base(BrightnessBits) { } bool bit(BrightnessLevel const& k, state_type& s) { // bit is inverted to achieve descending order return ! base::bit(k,s); } }; static void extractBrightnessLevels(BrightnessLevels& dst, InputVertices const& src) { dst.clear(); dst.reserve(src.size()); for (InputVertices::const_iterator i = src.begin(), e = src.end(); i != e; ++i) dst.push_back( getBrightness(i->getColor()) ); radix2InplaceSort(dst.begin(), dst.end(), BrightnessSortScanner()); } }; } #endif