// // Stars.cpp // interface // // Created by Tobias Schwinger on 3/22/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // #include "InterfaceConfig.h" #include "Stars.h" #include "UrlReader.h" #include "FieldOfView.h" #include "AngleUtils.h" #include "Radix2InplaceSort.h" #include "Radix2IntegerScanner.h" #include "FloodFill.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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< * * * FOV culling * =========== * * As stars can be thought of as at infinity distance, the field of view only * depends on perspective and rotation: * * _----_ <-- visible stars * from above +-near-+ - - * \ / | * near width: \ / | cos(p/2) * 2sin(p/2) \/ _ * center * * * Now it is important to note that a change in altitude maps uniformly to a * distance on a sphere. This is NOT the case for azimuthal angles: In this * case a factor of 'cos(alt)' (the orbital radius) applies: * * * |<-cos alt ->| | |<-|<----->|->| d_azi cos(alt) * | * __--* | --------- - * __-- * | | | ^ d_alt * __-- alt) * | | | v * --------------*- | ------------- - * | * side view | tile on sphere * * * This lets us find a worst case (Eigen) angle from the center to the edge * of a tile as * * hypot( 0.5 d_alt, 0.5 d_azi cos(alt_absmin) ). * * This angle must be added to 'p' (the perspective angle) in order to find * an altered near plane for the culling decision. * * * 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 */ // Compile time configuration // #define FORCE_POSITIVE_ALTITUDE // uncomment for hemisphere only // #define SAVE_MEMORY // uncomment not to use 16-bit types // #define SEE_LOD // uncomment to peek behind the scenes namespace { using std::swap; using std::min; using std::max; using glm::vec3; using glm::vec4; using glm::dot; using glm::normalize; using glm::swizzle; using glm::X; using glm::Y; using glm::Z; using glm::W; using glm::mat4; using glm::column; using glm::row; #ifdef SAVE_MEMORY typedef uint16_t nuint; typedef uint32_t wuint; #else typedef uint32_t nuint; typedef uint64_t wuint; #endif class InputVertex { unsigned val_color; float val_azimuth; float val_altitude; public: InputVertex(float azimuth, float altitude, unsigned color) { val_color = color >> 16 & 0xffu | color & 0xff00u | color << 16 & 0xff0000u | 0xff000000u; azimuth = angleConvert(azimuth); altitude = angleConvert(altitude); angleHorizontalPolar(azimuth, altitude); val_azimuth = azimuth; val_altitude = altitude; } float getAzimuth() const { return val_azimuth; } float getAltitude() const { return val_altitude; } unsigned getColor() const { return val_color; } }; typedef std::vector InputVertices; typedef nuint BrightnessLevel; #ifdef SAVE_MEMORY const unsigned BrightnessBits = 16u; #else const unsigned BrightnessBits = 18u; #endif const BrightnessLevel BrightnessMask = (1u << (BrightnessBits)) - 1u; typedef std::vector BrightnessLevels; BrightnessLevel getBrightness(unsigned c) { unsigned r = (c >> 16) & 0xff; unsigned g = (c >> 8) & 0xff; unsigned b = c & 0xff; #ifdef SAVE_MEMORY return BrightnessLevel((r*r+g*g+b*b) >> 2); #else return BrightnessLevel(r*r+g*g+b*b); #endif } 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); } }; 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()); } class Tiling { unsigned val_k; float val_rcp_slice; unsigned val_bits; public: Tiling(unsigned k) : val_k(k), val_rcp_slice(k / Radians::twice_pi()) { val_bits = ceil(log2(getTileCount()) + 2); } unsigned getAzimuthalTiles() const { return val_k; } unsigned getAltitudinalTiles() const { return val_k / 2 + 1; } unsigned getTileIndexBits() const { return val_bits; } unsigned getTileCount() const { return getAzimuthalTiles() * getAltitudinalTiles(); } unsigned getTileIndex(float azimuth, float altitude) const { return discreteAzimuth(azimuth) + val_k * discreteAltitude(altitude); } unsigned getTileIndex(InputVertex const& v) const { return getTileIndex(v.getAzimuth(), v.getAltitude()); } float getSliceAngle() const { return 1.0f / val_rcp_slice; } private: unsigned discreteAngle(float unsigned_angle) const { return unsigned(round(unsigned_angle * val_rcp_slice)); } unsigned discreteAzimuth(float a) const { return discreteAngle(a) % val_k; } unsigned discreteAltitude(float a) const { return min(getAltitudinalTiles() - 1, discreteAngle(a + Radians::half_pi()) ); } }; class TileSortScanner : public Radix2IntegerScanner { Tiling obj_tiling; typedef Radix2IntegerScanner Base; public: explicit TileSortScanner(Tiling const& tiling) : Base(tiling.getTileIndexBits() + BrightnessBits), obj_tiling(tiling) { } bool bit(InputVertex const& v, state_type const& s) const { // inspect (tile_index, brightness) tuples unsigned key = getBrightness(v.getColor()) ^ BrightnessMask; key |= obj_tiling.getTileIndex(v) << BrightnessBits; return Base::bit(key, s); } }; struct Tile { nuint offset; nuint count; BrightnessLevel lod; uint16_t flags; static uint16_t const checked = 1; static uint16_t const visited = 2; static uint16_t const render = 4; }; class GpuVertex { unsigned val_color; float val_x; float val_y; float val_z; public: GpuVertex() { } GpuVertex(InputVertex const& in) { val_color = in.getColor(); float azi = in.getAzimuth(); float alt = in.getAltitude(); // ground vector in x/z plane... float gx = sin(azi); float gz = -cos(azi); // ...elevated in y direction by altitude float exz = cos(alt); val_x = gx * exz; val_y = sin(alt); val_z = gz * exz; } unsigned getColor() const { return val_color; } }; struct GreaterBrightness { bool operator()(InputVertex const& lhs, InputVertex const& rhs) const { return getBrightness(lhs.getColor()) > getBrightness(rhs.getColor()); } bool operator()(BrightnessLevel lhs, GpuVertex const& rhs) const { return lhs > getBrightness(rhs.getColor());; } bool operator()(BrightnessLevel lhs, BrightnessLevel rhs) const { return lhs > rhs; } }; class Loader : UrlReader { InputVertices* ptr_vertices; unsigned val_limit; unsigned val_lineno; char const* str_actual_url; unsigned val_records_read; BrightnessLevel val_min_brightness; public: bool loadVertices( InputVertices& destination, char const* url, unsigned limit) { ptr_vertices = & destination; val_limit = limit; #ifdef SAVE_MEMORY if (val_limit == 0 || val_limit > 60000u) val_limit = 60000u; #endif str_actual_url = url; // in case we fail early if (! UrlReader::readUrl(url, *this)) { fprintf(stderr, "%s:%d: %s\n", str_actual_url, val_lineno, getError()); return false; } fprintf(stderr, "Stars.cpp: read %d vertices, using %d\n", val_records_read, ptr_vertices->size()); return true; } protected: friend class UrlReader; void begin(char const* url, char const* type, int64_t size, int64_t stardate) { val_lineno = 0u; str_actual_url = url; // new value in http redirect val_records_read = 0u; ptr_vertices->clear(); ptr_vertices->reserve(val_limit); // fprintf(stderr, "Stars.cpp: loader begin %s\n", url); } size_t transfer(char* input, size_t bytes) { size_t consumed = 0u; char const* end = input + bytes; char* line, * next = input; for (;;) { // advance to next line for (; next != end && isspace(*next); ++next); consumed = next - input; line = next; ++val_lineno; for (; next != end && *next != '\n' && *next != '\r'; ++next); if (next == end) return consumed; *next++ = '\0'; // skip comments if (*line == '\\' || *line == '/' || *line == ';') continue; // parse float azi, alt; unsigned c; if (sscanf(line, " %f %f #%x", & azi, & alt, & c) == 3) { if (spaceFor( getBrightness(c) )) { storeVertex(azi, alt, c); } ++val_records_read; } else { fprintf(stderr, "Stars.cpp:%d: Bad input from %s\n", val_lineno, str_actual_url); } } return consumed; } void end(bool ok) { } private: bool atLimit() { return val_limit > 0u && val_records_read >= val_limit; } bool spaceFor(BrightnessLevel b) { if (! atLimit()) { return true; } // just reached the limit? -> establish a minimum heap and // remember the brightness at its top if (val_records_read == val_limit) { // fprintf(stderr, "Stars.cpp: vertex limit reached -> heap mode\n"); std::make_heap( ptr_vertices->begin(), ptr_vertices->end(), GreaterBrightness() ); val_min_brightness = getBrightness( ptr_vertices->begin()->getColor() ); } // not interested? say so if (val_min_brightness >= b) return false; // otherwise free up space for the new vertex std::pop_heap( ptr_vertices->begin(), ptr_vertices->end(), GreaterBrightness() ); ptr_vertices->pop_back(); return true; } void storeVertex(float azi, float alt, unsigned color) { ptr_vertices->push_back(InputVertex(azi, alt, color)); if (atLimit()) { std::push_heap( ptr_vertices->begin(), ptr_vertices->end(), GreaterBrightness() ); val_min_brightness = getBrightness( ptr_vertices->begin()->getColor() ); } } }; class Renderer; class TileCulling { Renderer& ref_renderer; Tile** const arr_stack; Tile** itr_stack; Tile const* const arr_tile; Tile const* const itr_tiles_end; public: inline TileCulling(Renderer& renderer, Tile const* tiles, Tile const* tiles_end, Tile** stack); protected: // flood fill strategy inline bool select(Tile* t); inline void process(Tile* t); void right(Tile*& cursor) const { cursor += 1; } void left(Tile*& cursor) const { cursor -= 1; } void up(Tile*& cursor) const { cursor += yStride(); } void down(Tile*& cursor) const { cursor -= yStride(); } void defer(Tile* t) { *itr_stack++ = t; } inline bool deferred(Tile*& cursor); private: inline unsigned yStride() const; }; class Renderer { GpuVertex* arr_data; Tile* arr_tile; GLint* arr_batch_offs; GLsizei* arr_batch_count; GLuint hnd_vao; Tiling obj_tiling; unsigned* itr_out_index; vec3 vec_w_xform; float val_half_persp; BrightnessLevel val_min_bright; public: Renderer(InputVertices const& src, size_t n, unsigned k, BrightnessLevel b, BrightnessLevel bMin) : arr_data(0l), arr_tile(0l), obj_tiling(k) { this->glAlloc(); Tiling tiling(k); size_t nTiles = tiling.getTileCount(); arr_data = new GpuVertex[n]; arr_tile = new Tile[nTiles + 1]; arr_batch_offs = new GLint[nTiles]; arr_batch_count = new GLsizei[nTiles]; prepareVertexData(src, n, tiling, b, bMin); this->glUpload(n); } ~Renderer() { delete[] arr_data; delete[] arr_tile; delete[] arr_batch_count; delete[] arr_batch_offs; this->glFree(); } void render(FieldOfView const& fov, BrightnessLevel min_bright) { // fprintf(stderr, " // Stars.cpp: rendering at minimal brightness %d\n", min_bright); float half_persp = fov.getPerspective() * 0.5f; float aspect = fov.getAspectRatio(); // determine dimensions based on a sought screen diagonal // // ww + hh = dd // a = h / w => h = wa // ww + ww aa = dd // ww = dd / (1 + aa) float diag = 2.0f * std::sin(half_persp); float near = std::cos(half_persp); float hw = 0.5f * sqrt(diag * diag / (1.0f + aspect * aspect)); float hh = hw * aspect; // cancel all translation mat4 matrix = fov.getOrientation(); matrix[3][0] = 0.0f; matrix[3][1] = 0.0f; matrix[3][2] = 0.0f; // extract local z vector vec3 ahead = swizzle( column(matrix, 2) ); float azimuth = atan2(ahead.x,-ahead.z) + Radians::pi(); float altitude = atan2(-ahead.y, hypot(ahead.x, ahead.z)); angleHorizontalPolar(azimuth, altitude); #ifdef FORCE_POSITIVE_ALTITUDE altitude = std::max(0.0f, altitude); #endif unsigned tile_index = obj_tiling.getTileIndex(azimuth, altitude); // fprintf(stderr, "Stars.cpp: starting on tile #%d\n", tile_index); #ifdef SEE_LOD mat4 matrix_debug = glm::translate( glm::frustum(-hw,hw, -hh,hh, near,10.0f), vec3(0.0f, 0.0f, -4.0f)) * glm::affineInverse(matrix); #endif matrix = glm::frustum(-hw,hw, -hh,hh, near,10.0f) * glm::affineInverse(matrix); this->itr_out_index = (unsigned*) arr_batch_offs; this->vec_w_xform = swizzle(row(matrix, 3)); this->val_half_persp = half_persp; this->val_min_bright = min_bright; floodFill(arr_tile + tile_index, TileCulling(*this, arr_tile, arr_tile + obj_tiling.getTileCount(), (Tile**) arr_batch_count)); #ifdef SEE_LOD #define matrix matrix_debug #endif this->glBatch(glm::value_ptr(matrix), prepareBatch( (unsigned*) arr_batch_offs, itr_out_index) ); #ifdef SEE_LOD #undef matrix #endif } private: // renderer construction void prepareVertexData(InputVertices const& src, size_t n, // <-- at bMin and brighter Tiling const& tiling, BrightnessLevel b, BrightnessLevel bMin) { size_t nTiles = tiling.getTileCount(); size_t vertex_index = 0u, curr_tile_index = 0u, count_active = 0u; arr_tile[0].offset = 0u; arr_tile[0].lod = b; arr_tile[0].flags = 0u; for (InputVertices::const_iterator i = src.begin(), e = src.end(); i != e; ++i) { BrightnessLevel bv = getBrightness(i->getColor()); // filter by alloc brightness if (bv >= bMin) { size_t tile_index = tiling.getTileIndex(*i); assert(tile_index >= curr_tile_index); // moved on to another tile? -> flush if (tile_index != curr_tile_index) { Tile* t = arr_tile + curr_tile_index; Tile* t_last = arr_tile + tile_index; // set count of active vertices (upcoming lod) t->count = count_active; // generate skipped, empty tiles for(size_t offs = vertex_index; ++t != t_last ;) { t->offset = offs, t->count = 0u, t->lod = b, t->flags = 0u; } // initialize next (as far as possible here) t_last->offset = vertex_index; t_last->lod = b; t_last->flags = 0u; curr_tile_index = tile_index; count_active = 0u; } if (bv >= b) ++count_active; // fprintf(stderr, "Stars.cpp: Vertex %d on tile #%d\n", vertex_index, tile_index); // write converted vertex arr_data[vertex_index++] = *i; } } assert(vertex_index == n); // flush last tile (see above) Tile* t = arr_tile + curr_tile_index; t->count = count_active; for (Tile* e = arr_tile + nTiles + 1; ++t != e;) { t->offset = vertex_index, t->count = 0u, t->lod = b, t->flags = 0; } } private: // FOV culling / LOD friend class TileCulling; bool visitTile(Tile* t) { unsigned index = t - arr_tile; *itr_out_index++ = index; if (! tileVisible(t, index)) return false; if (t->lod != val_min_bright) updateVertexCount(t, val_min_bright); return true; } bool tileVisible(Tile* t, unsigned i) { float slice = obj_tiling.getSliceAngle(); unsigned stride = obj_tiling.getAzimuthalTiles(); float azimuth = (i % stride) * slice; float altitude = (i / stride) * slice - Radians::half_pi(); float gx = sin(azimuth); float gz = -cos(azimuth); float exz = cos(altitude); vec3 tile_center = vec3(gx * exz, sin(altitude), gz * exz); float w = dot(vec_w_xform, tile_center); float half_slice = 0.5f * slice; float daz = half_slice * cos(abs(altitude) - half_slice); float dal = half_slice; float near = cos(val_half_persp + sqrt(daz*daz+dal*dal)); // fprintf(stderr, "Stars.cpp: checking tile #%d, w = %f, near = %f\n", i, w, near); return w > near; } void updateVertexCount(Tile* t, BrightnessLevel minBright) { // a growing number of stars needs to be rendereed when the // minimum brightness decreases // perform a binary search in the so found partition for the // new vertex count of this tile GpuVertex const* start = arr_data + t[0].offset; GpuVertex const* end = arr_data + t[1].offset; assert(end >= start); if (start == end) return; if (t->lod < minBright) end = start + t->count; else start += (t->count > 0 ? t->count - 1 : 0); end = std::upper_bound( start, end, minBright, GreaterBrightness()); assert(end >= arr_data + t[0].offset); t->count = end - arr_data - t[0].offset; t->lod = minBright; } unsigned prepareBatch(unsigned const* indices, unsigned const* indicesEnd) { unsigned nRanges = 0u; GLint* offs = arr_batch_offs; GLsizei* count = arr_batch_count; for (unsigned* i = (unsigned*) arr_batch_offs; i != indicesEnd; ++i) { Tile* t = arr_tile + *i; if ((t->flags & Tile::render) > 0u && t->count > 0u) { *offs++ = t->offset; *count++ = t->count; ++nRanges; } t->flags = 0; } return nRanges; } private: // gl API handling #ifdef __APPLE__ #define glBindVertexArray glBindVertexArrayAPPLE #define glGenVertexArrays glGenVertexArraysAPPLE #define glDeleteVertexArrays glDeleteVertexArraysAPPLE #endif void glAlloc() { glGenVertexArrays(1, & hnd_vao); } void glFree() { glDeleteVertexArrays(1, & hnd_vao); } void glUpload(GLsizei n) { GLuint vbo; glGenBuffers(1, & vbo); glBindVertexArray(hnd_vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, n * sizeof(GpuVertex), arr_data, GL_STATIC_DRAW); glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l); glBindVertexArray(0); } void glBatch(GLfloat const* matrix, GLsizei n_ranges) { // fprintf(stderr, "Stars.cpp: rendering %d-multibatch\n", n_ranges); // for (int i = 0; i < n_ranges; ++i) // fprintf(stderr, "Stars.cpp: Batch #%d - %d stars @ %d\n", i, // arr_batch_offs[i], arr_batch_count[i]); // setup modelview matrix (identity) glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); // set projection matrix glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadMatrixf(matrix); // render glBindVertexArray(hnd_vao); glEnable(GL_POINT_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); glPointSize(1.42f); glMultiDrawArrays(GL_POINTS, arr_batch_offs, arr_batch_count, n_ranges); // restore state glBindVertexArray(0); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } #ifdef __APPLE__ #undef glBindVertexArray #undef glGenVertexArrays #undef glDeleteVertexArrays #endif }; TileCulling::TileCulling(Renderer& renderer, Tile const* tiles, Tile const* tiles_end, Tile** stack) : ref_renderer(renderer), arr_stack(stack), itr_stack(stack), arr_tile(tiles), itr_tiles_end(tiles_end) { } bool TileCulling::select(Tile* t) { if (t < arr_tile || t >= itr_tiles_end || !! (t->flags & Tile::visited)) { return false; } if (! (t->flags & Tile::checked)) { if (ref_renderer.visitTile(t)) t->flags |= Tile::render; } return !! (t->flags & Tile::render); } void TileCulling::process(Tile* t) { t->flags |= Tile::visited; } bool TileCulling::deferred(Tile*& cursor) { if (itr_stack != arr_stack) { cursor = *--itr_stack; return true; } return false; } unsigned TileCulling::yStride() const { return ref_renderer.obj_tiling.getAzimuthalTiles(); } } class Stars::body { InputVertices seq_input; unsigned val_tile_resolution; double val_lod_fraction; double val_lod_low_water_mark; double val_lod_high_water_mark; double val_lod_overalloc; size_t val_lod_n_alloc; size_t val_lod_n_render; BrightnessLevels seq_lod_brightness; BrightnessLevel val_lod_brightness; BrightnessLevel val_lod_alloc_brightness; Renderer* ptr_renderer; public: body() : val_tile_resolution(20), val_lod_fraction(1.0), val_lod_low_water_mark(0.8), val_lod_high_water_mark(1.0), val_lod_overalloc(1.2), val_lod_n_alloc(0), val_lod_n_render(0), val_lod_brightness(0), val_lod_alloc_brightness(0), ptr_renderer(0l) { } bool readInput(const char* url, unsigned limit) { InputVertices vertices; if (! Loader().loadVertices(vertices, url, limit)) return false; BrightnessLevels brightness; extractBrightnessLevels(brightness, vertices); assert(brightness.size() == vertices.size()); for (BrightnessLevels::iterator i = brightness.begin(); i != brightness.end() - 1; ++i) { BrightnessLevels::iterator next = i + 1; if (next != brightness.end()) assert( *i >= *next ); } // input is read, now run the entire data pipeline on the new input { // TODO input mutex seq_input.swap(vertices); unsigned k = val_tile_resolution; 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 { // TODO lod mutex size_t newLast = seq_input.size() - 1; // reciprocal change N_old/N_new tells us how to scale // the fractions rcpChange = min(1.0, double(vertices.size()) / seq_input.size()); // initialization? use defaults / previously set values if (rcpChange == 0.0) { rcpChange = 1.0; nRender = size_t(round(val_lod_fraction * newLast)); n = min(newLast, size_t(round(val_lod_overalloc * nRender))); } else { // cannot allocate or render more than we have n = min(newLast, val_lod_n_alloc); nRender = min(newLast, val_lod_n_render); } // 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(seq_input); throw; } // finally publish the new LOD state { // TODO lod mutex seq_lod_brightness.swap(brightness); val_lod_fraction *= rcpChange; val_lod_low_water_mark *= rcpChange; val_lod_high_water_mark *= rcpChange; val_lod_overalloc *= rcpChange; val_lod_n_alloc = n; val_lod_n_render = nRender; val_lod_alloc_brightness = bMin; // keep last, it's accessed asynchronously val_lod_brightness = b; } } return true; } bool setResolution(unsigned k) { if (k <= 3) { return false; } // fprintf(stderr, "Stars.cpp: setResolution(%d)\n", k); if (k != val_tile_resolution) { // TODO make atomic // TODO input mutex unsigned n; BrightnessLevel b, bMin; { // TODO lod mutex n = val_lod_n_alloc; b = val_lod_brightness; bMin = val_lod_alloc_brightness; } this->retile(n, k, b, bMin); return true; } else { return false; } } void retile(size_t n, unsigned k, BrightnessLevel b, BrightnessLevel bMin) { Tiling tiling(k); TileSortScanner scanner(tiling); radix2InplaceSort(seq_input.begin(), seq_input.end(), scanner); // fprintf(stderr, // "Stars.cpp: recreateRenderer(%d, %d, %d, %d)\n", n, k, b, bMin); recreateRenderer(n, k, b, bMin); val_tile_resolution = k; } 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; { // TODO lod mutex // acuire a consistent copy of the current LOD state fraction = val_lod_fraction; lwm = val_lod_low_water_mark; hwm = val_lod_high_water_mark; size_t last = seq_lod_brightness.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 = size_t(round(oaFract * last)); bMin = seq_lod_brightness[n]; n = std::upper_bound( seq_lod_brightness.begin() + n - 1, seq_lod_brightness.end(), bMin, GreaterBrightness() ) - seq_lod_brightness.begin(); // also determine number of vertices to render and brightness nRender = size_t(round(fraction * last)); // Note: nRender does not have to be accurate b = seq_lod_brightness[nRender]; // this setting controls the renderer, also keep b as the // brightness becomes volatile as soon as the mutex is // released val_lod_brightness = b; // TODO make atomic // fprintf(stderr, "Stars.cpp: " // "fraction = %lf, oaFract = %lf, n = %d, n' = %d, bMin = %d, b = %d\n", // fraction, oaFract, size_t(round(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 >= val_lod_low_water_mark && fraction <= val_lod_high_water_mark) { val_lod_fraction = fraction; return fraction; } } // reallocate { // TODO input mutex recreateRenderer(n, val_tile_resolution, b, bMin); fprintf(stderr, "Stars.cpp: LOD reallocation\n"); // publish new lod state { // TODO lod mutex val_lod_n_alloc = n; val_lod_n_render = nRender; val_lod_fraction = fraction; val_lod_low_water_mark = fraction * (1.0 - realloc); val_lod_high_water_mark = fraction * (1.0 + realloc); val_lod_overalloc = fraction * (1.0 + overalloc); val_lod_alloc_brightness = bMin; } } return fraction; } void recreateRenderer(size_t n, unsigned k, BrightnessLevel b, BrightnessLevel bMin) { Renderer* renderer = new Renderer(seq_input, n, k, b, bMin); swap(ptr_renderer, renderer); // TODO make atomic delete renderer; // will be NULL when was in use } void render(FieldOfView const& fov) { // check out renderer Renderer* renderer = 0l; swap(ptr_renderer, renderer); // TODO make atomic // have it render if (renderer != 0l) { BrightnessLevel b = val_lod_brightness; // make atomic renderer->render(fov, b); } // check in - or dispose if there is a new one // TODO make atomic (CAS) if (! ptr_renderer) { ptr_renderer = renderer; } else { delete renderer; } } }; Stars::Stars() : ptr_body(0l) { ptr_body = new body; } Stars::~Stars() { delete ptr_body; } bool Stars::readInput(const char* url, unsigned limit) { return ptr_body->readInput(url, limit); } bool Stars::setResolution(unsigned k) { return ptr_body->setResolution(k); } float Stars::changeLOD(float fraction, float overalloc, float realloc) { return float(ptr_body->changeLOD(fraction, overalloc, realloc)); } void Stars::render(FieldOfView const& fov) { ptr_body->render(fov); }