// // 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 #include #include #include #include #include #include #include #include #include #include #include #define DEG2RAD 0.017453292519f /* Data pipeline * ------------- * * ->> readInput -(load)--+---- (get brightness & sort) ---> brightness LUT * | | * ->> setResolution --+ | >extractBrightnessLevels< * V | * (sort by (tile,brightness)) * | | * ->> setLOD ---+ | >retile< ->> setLOD --> (just parameterize * V V renderer when on-GPU * (filter by max-LOD brightness, data suffices) * build tile info for rendering) * | | * V >recreateRenderer< * (set new renderer)/ * * * (process), ->> entry point, ---> data flow, >internal routine< * * * Open issues * ----------- * * o FOV culling is too eager - gotta revisit * o LOD adjustment in a living renderer still needs to be coded (planned) * o input limit (while keeping the brightest) needs to be coded (planned) * 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 */ namespace { using std::swap; using std::min; using std::max; using glm::mat4; using glm::value_ptr; 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; 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; class Loader : UrlReader { InputVertices* ptr_vertices; unsigned val_limit; unsigned val_lineno; char const* str_actual_url; public: bool loadVertices( InputVertices& destination, char const* url, unsigned limit) { ptr_vertices = & destination; val_limit = limit; 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; } 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 ptr_vertices->clear(); ptr_vertices->reserve(val_limit); } 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 (ptr_vertices->size() < val_limit) ptr_vertices->push_back( InputVertex(azi, alt, c) ); // TODO handle limit by switching to a minheap when // buffer is full } else { fprintf(stderr, "Stars.cpp:%d: Bad input from %s\n", val_lineno, str_actual_url); } } return consumed; } void end(bool ok) { } }; typedef uint16_t BrightnessLevel; typedef std::vector BrightnessLevels; const unsigned BrightnessBits = 16u; template< class Vertex > BrightnessLevel getBrightness(Vertex const& v) { unsigned c = v.getColor(); unsigned r = (c >> 16) & 0xff; unsigned g = (c >> 8) & 0xff; unsigned b = c & 0xff; return BrightnessLevel((r*r+g*g+b*b) >> 1); } struct BrightnessSortScanner : Radix2IntegerScanner { typedef Radix2IntegerScanner Base; BrightnessSortScanner() : Base(BrightnessBits) { } bool bit(BrightnessLevel const& k, state_type& s) { 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) ); radix2InplaceSort(dst.begin(), dst.end(), BrightnessSortScanner()); } template< class Unit > class HorizontalTiling { unsigned val_k; float val_rcp_slice; unsigned val_bits; public: HorizontalTiling(unsigned k) : val_k(k), val_rcp_slice(k / Unit::twice_pi()) { val_bits = ceil(log2(getTileCount() )); } 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 { unsigned result; return discreteAngle(azimuth) % val_k + discreteAngle(altitude + Unit::half_pi()) * val_k; } unsigned getTileIndex(InputVertex const& v) const { return getTileIndex(v.getAzimuth(), v.getAltitude()); } unsigned discreteAngle(float unsigned_angle) const { return unsigned(round(unsigned_angle * val_rcp_slice)); } }; class TileSortScanner : public Radix2IntegerScanner { HorizontalTiling obj_tiling; typedef Radix2IntegerScanner Base; public: explicit TileSortScanner(HorizontalTiling 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); key |= obj_tiling.getTileIndex(v) << BrightnessBits; return Base::bit(key, s); } }; struct Tile { uint16_t offset; uint16_t count; // according to previous lod setting }; struct GpuVertex { unsigned val_color; float val_x; float val_y; float val_z; // GpuVertex() { } GpuVertex(InputVertex const& in) { val_color = in.getColor(); float azimuth = in.getAzimuth() * DEG2RAD; float altitude = in.getAltitude() * DEG2RAD; // ground vector in x/z plane... float gx = sin(azimuth); float gz = -cos(azimuth); // ...elevated in y direction by altitude float exz = cos(altitude); val_x = gx * exz; val_y = sin(altitude); val_z = gz * exz; //fprintf(stderr, "Stars.cpp: GpuVertex created (%x,%f,%f,%f)\n", val_color, val_x, val_y, val_z); } unsigned getColor() const { return val_color; } }; class Renderer { GpuVertex* ptr_data; Tile* ptr_tiles; BrightnessLevel val_brightness; unsigned val_tile_resolution; GLint* ptr_batch_offs; GLsizei* ptr_batch_count; GLuint hnd_vao; public: Renderer(InputVertices const& src, size_t n, unsigned k, BrightnessLevel b, BrightnessLevel b_max) : ptr_data(0l), ptr_tiles(0l), val_brightness(b), val_tile_resolution(k) { HorizontalTiling tiling(k); size_t n_tiles = tiling.getTileCount(); ptr_data = new GpuVertex[n]; ptr_tiles = new Tile[n_tiles]; // TODO tighten bounds and save some memory ptr_batch_offs = new GLint[n_tiles]; ptr_batch_count = new GLsizei[n_tiles]; size_t vertex_index = 0u, curr_tile_index = 0u, count_active = 0u; for (InputVertices::const_iterator i = src.begin(), e = src.end(); i != e; ++i) { BrightnessLevel bv = getBrightness(*i); // filter by alloc brightness if (bv >= b_max) { size_t tile_index = tiling.getTileIndex(*i); assert(tile_index >= curr_tile_index); // moved to another tile? if (tile_index != curr_tile_index) { Tile* t = ptr_tiles + curr_tile_index; Tile* t_last = ptr_tiles + tile_index; // set count of active vertices (upcoming lod) t->count = count_active; // generate skipped entries for(size_t offs = t_last->offset; ++t != t_last ;) t->offset = offs, t->count = 0u; // set offset of the beginning tile` t_last->offset = vertex_index; 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 ptr_data[vertex_index++] = *i; } } assert(vertex_index == n); // finish last tile (see above) Tile* t = ptr_tiles + curr_tile_index; Tile* t_last = ptr_tiles + n_tiles; t->count = count_active; for(; ++t != t_last ;) t->offset = vertex_index, t->count = 0u; // OpenGL upload GLuint vbo; glGenBuffers(1, & vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, n * sizeof(GpuVertex), ptr_data, GL_STATIC_DRAW); glGenVertexArrays(1, & hnd_vao); glBindVertexArray(hnd_vao); glInterleavedArrays(GL_C4UB_V3F, sizeof(GpuVertex), 0l); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindVertexArray(0); } ~Renderer() { delete[] ptr_data; delete[] ptr_tiles; delete[] ptr_batch_count; delete[] ptr_batch_offs; glDeleteVertexArrays(1, & hnd_vao); } void render(FieldOfView fov, BrightnessLevel lod) { mat4 local_space = fov.getOrientation(); HorizontalTiling tiling(val_tile_resolution); // get z direction float x = local_space[2][0]; float y = local_space[2][1]; float z = local_space[2][2]; // to polar float azimuth = atan2(x,-z) + Radians::pi(); float altitude = atan2(y, sqrt(x*x+z*z)); fprintf(stderr, "Stars.cpp: viewer azimuth = %f, altitude = %f\n", azimuth, altitude); // half diagonal perspective angle float hd_pers = fov.getPerspective() * 0.5f; unsigned azi_dim = tiling.getAzimuthalTiles(); unsigned alt_dim = tiling.getAltitudinalTiles(); // determine tile range in azimuthal direction (modulated) unsigned azi_from = tiling.discreteAngle( angleUnsignedNormal(azimuth - hd_pers) ) % azi_dim; unsigned azi_to = (1 + tiling.discreteAngle( angleUnsignedNormal(azimuth + hd_pers) )) % azi_dim; // determine tile range in altitudinal direction (clamped) unsigned alt_from = tiling.discreteAngle( max(-Radians::half_pi(),min(Radians::half_pi(), altitude - hd_pers)) + Radians::half_pi() ); unsigned alt_to = tiling.discreteAngle( max(-Radians::half_pi(),min(Radians::half_pi(), altitude + hd_pers)) + Radians::half_pi() ); // iterate the grid... unsigned n_batches = 0u; fprintf(stderr, "Stars.cpp: grid dimensions: %d x %d\n", azi_dim, alt_dim); fprintf(stderr, "Stars.cpp: grid range: [%d;%d) [%d;%d]\n", azi_from, azi_to, alt_from, alt_to); GLint* offs = ptr_batch_offs, * count = ptr_batch_count; for (unsigned alt = alt_from; alt <= alt_to; ++alt) { for (unsigned azi = azi_from; azi != azi_to; azi = (azi + 1) % azi_dim) { unsigned tile_index = azi + alt * azi_dim; Tile& t = ptr_tiles[tile_index]; // TODO handle LOD changes by performing a binary // search for the new brightness, if any if (! t.count) continue; fprintf(stderr, "Stars.cpp: tile %d selected (%d vertices at offset %d)\n", tile_index, t.count, t.offset); *offs++ = t.offset; *count++ = t.count; ++n_batches; } } fprintf(stderr, "Stars.cpp: rendering %d-multibatch\n", n_batches); // cancel translation local_space[3][0] = 0.0f; local_space[3][1] = 0.0f; local_space[3][2] = 0.0f; // and setup modelview matrix glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(glm::value_ptr( fov.setOrientation(local_space).getWorldViewerXform())); // render glBindVertexArray(hnd_vao); glPointSize(1.0); glMultiDrawArrays(GL_POINTS, ptr_batch_offs, ptr_batch_count, n_batches); // restore state state glBindVertexArray(0); glPopMatrix(); } }; } struct Stars::body { InputVertices vec_input; unsigned val_tile_resolution; BrightnessLevels vec_lod_brightness; BrightnessLevel val_lod_brightness; BrightnessLevel val_lod_max_brightness; float val_lod_current_alloc; float val_lod_low_water_mark; float val_lod_high_water_mark; Renderer* ptr_renderer; body() : val_tile_resolution(12), val_lod_brightness(0), val_lod_max_brightness(0), val_lod_current_alloc(1.0f), val_lod_low_water_mark(0.99f), val_lod_high_water_mark(1.0f), ptr_renderer(0l) { } bool readInput(const char* url, unsigned limit) { InputVertices new_vertices; if (! Loader().loadVertices(new_vertices, url, limit)) return false; BrightnessLevels new_brightness; extractBrightnessLevels(new_brightness, new_vertices); { // TODO input mutex vec_input.swap(new_vertices); try { retile(val_tile_resolution); } catch (...) { // rollback transaction new_vertices.swap(vec_input); throw; } { // TODO lod mutex vec_lod_brightness.swap(new_brightness); } } new_vertices.clear(); new_brightness.clear(); return true; } void setResolution(unsigned k) { if (k != val_tile_resolution) { // TODO input mutex retile(k); } } void retile(unsigned k) { HorizontalTiling tiling(k); TileSortScanner scanner(tiling); radix2InplaceSort(vec_input.begin(), vec_input.end(), scanner); recreateRenderer(vec_input.size(), k, val_lod_brightness, val_lod_max_brightness); val_tile_resolution = k; } void setLOD(float fraction, float overalloc, float realloc) { assert(fraction >= 0.0f && fraction <= 0.0f); assert(overalloc >= realloc && realloc >= 0.0f); assert(overalloc <= 1.0f && realloc <= 1.0f); float lwm, hwm; float oa_fraction = min(fraction * (1.0f + oa_fraction), 1.0f); size_t oa_new_size; BrightnessLevel b, b_max; { // TODO lod mutex // Or... There is just one write access, here - so LOD state // could be CMPed as well... lwm = val_lod_low_water_mark; hwm = val_lod_high_water_mark; size_t last = vec_lod_brightness.size() - 1; val_lod_brightness = b = vec_lod_brightness[ size_t(fraction * last) ]; oa_new_size = size_t(oa_fraction * last); b_max = vec_lod_brightness[oa_new_size++]; } // have to reallocate? if (fraction < lwm || fraction > hwm) { // TODO input mutex recreateRenderer(oa_new_size, val_tile_resolution, b, b_max); { // TODO lod mutex val_lod_current_alloc = fraction; val_lod_low_water_mark = fraction * (1.0f - realloc); val_lod_high_water_mark = fraction * (1.0f + realloc); val_lod_max_brightness = b_max; } } } void recreateRenderer( size_t n, unsigned k, BrightnessLevel b, BrightnessLevel b_max) { Renderer* renderer = new Renderer(vec_input, n, k, b, b_max); 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 float new_brightness = val_lod_brightness; // make atomic // have it render renderer->render(fov, new_brightness); // 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); } void Stars::setResolution(unsigned k) { ptr_body->setResolution(k); } void Stars::setLOD(float fraction, float overalloc, float realloc) { ptr_body->setLOD(fraction, 0.0f, 0.0f); } // TODO enable once implemented void Stars::render(FieldOfView const& fov) { ptr_body->render(fov); }