From 2b3263e01974044e79b1193bdd1b83b22bcff805 Mon Sep 17 00:00:00 2001 From: tosh Date: Fri, 29 Mar 2013 10:53:30 +0100 Subject: [PATCH] splits up Stars.cpp into a module of multiple files --- interface/src/Stars.cpp | 1229 +---------------- interface/src/Stars.h | 10 +- interface/src/starfield/Config.h | 96 ++ interface/src/starfield/Controller.h | 382 +++++ interface/src/starfield/Loader.h | 180 +++ .../src/starfield/data/BrightnessLevel.h | 64 + interface/src/starfield/data/GpuVertex.h | 53 + interface/src/starfield/data/InputVertex.h | 51 + interface/src/starfield/data/Tile.h | 37 + interface/src/starfield/renderer/Renderer.h | 495 +++++++ interface/src/starfield/renderer/Tiling.h | 71 + .../src/starfield/renderer/VertexOrder.h | 52 + 12 files changed, 1499 insertions(+), 1221 deletions(-) create mode 100644 interface/src/starfield/Config.h create mode 100644 interface/src/starfield/Controller.h create mode 100644 interface/src/starfield/Loader.h create mode 100644 interface/src/starfield/data/BrightnessLevel.h create mode 100644 interface/src/starfield/data/GpuVertex.h create mode 100644 interface/src/starfield/data/InputVertex.h create mode 100644 interface/src/starfield/data/Tile.h create mode 100644 interface/src/starfield/renderer/Renderer.h create mode 100644 interface/src/starfield/renderer/Tiling.h create mode 100644 interface/src/starfield/renderer/VertexOrder.h diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 46301613da..95a0af3654 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -7,1241 +7,36 @@ // #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 "Stars.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; - } - } -}; +#define __interface__Starfield_impl__ +#include "starfield/Controller.h" +#undef __interface__Starfield_impl__ Stars::Stars() : - ptr_body(0l) { - ptr_body = new body; + _ptrController(0l) { + _ptrController = new starfield::Controller; } + Stars::~Stars() { - delete ptr_body; + delete _ptrController; } bool Stars::readInput(const char* url, unsigned limit) { - return ptr_body->readInput(url, limit); + return _ptrController->readInput(url, limit); } bool Stars::setResolution(unsigned k) { - return ptr_body->setResolution(k); + return _ptrController->setResolution(k); } float Stars::changeLOD(float fraction, float overalloc, float realloc) { - return float(ptr_body->changeLOD(fraction, overalloc, realloc)); + return float(_ptrController->changeLOD(fraction, overalloc, realloc)); } void Stars::render(FieldOfView const& fov) { - ptr_body->render(fov); + _ptrController->render(fov.getPerspective(), fov.getAspectRatio(), fov.getOrientation()); } - diff --git a/interface/src/Stars.h b/interface/src/Stars.h index 1067f5ba8d..90c52e64ef 100644 --- a/interface/src/Stars.h +++ b/interface/src/Stars.h @@ -11,15 +11,17 @@ #include "FieldOfView.h" +namespace starfield { class Controller; } + /** * Starfield rendering component. */ -class Stars -{ - struct body; - body* ptr_body; +class Stars { + + starfield::Controller* _ptrController; public: + Stars(); ~Stars(); diff --git a/interface/src/starfield/Config.h b/interface/src/starfield/Config.h new file mode 100644 index 0000000000..9b196eae1e --- /dev/null +++ b/interface/src/starfield/Config.h @@ -0,0 +1,96 @@ +// +// starfield/Config.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__Config__ +#define __interface__starfield__Config__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +// +// Compile time configuration: +// + +#ifndef STARFIELD_HEMISPHERE_ONLY +#define STARFIELD_HEMISPHERE_ONLY 0 // set to 1 for hemisphere only +#endif + +#ifndef STARFIELD_LOW_MEMORY +#define STARFIELD_LOW_MEMORY 0 // set to 1 not to use 16-bit types +#endif + +#ifndef STARFIELD_DEBUG_LOD +#define STARFIELD_DEBUG_LOD 0 // set to 1 to peek behind the scenes +#endif + +// +// Dependencies: +// + +#include "InterfaceConfig.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "UrlReader.h" +#include "AngleUtils.h" +#include "Radix2InplaceSort.h" +#include "Radix2IntegerScanner.h" +#include "FloodFill.h" + +// Namespace configuration: + +namespace starfield { + + 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; + + using namespace std; + + +#if STARFIELD_SAVE_MEMORY + typedef uint16_t nuint; + typedef uint32_t wuint; +#else + typedef uint32_t nuint; + typedef uint64_t wuint; +#endif + +} + +#endif + diff --git a/interface/src/starfield/Controller.h b/interface/src/starfield/Controller.h new file mode 100644 index 0000000000..8dc08787ed --- /dev/null +++ b/interface/src/starfield/Controller.h @@ -0,0 +1,382 @@ +// +// 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; + unsigned _valTileResolution; + + double _valLodFraction; + double _valLodLowWaterMark; + double _valLodHighWaterMark; + double _valLodOveralloc; + size_t _valLodNalloc; + size_t _valLodNrender; + BrightnessLevels _seqLodBrightness; + BrightnessLevel _valLodBrightness; + BrightnessLevel _valLodAllocBrightness; + + Renderer* _ptrRenderer; + + 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); + + 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 + + _seqInput.swap(vertices); + + unsigned k = _valTileResolution; + + 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 = _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 = lrint(_valLodFraction * newLast); + n = min(newLast, size_t(lrint(_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 + + { // TODO lod mutex + + _seqLodBrightness.swap(brightness); + _valLodFraction *= rcpChange; + _valLodLowWaterMark *= rcpChange; + _valLodHighWaterMark *= rcpChange; + _valLodOveralloc *= rcpChange; + _valLodNalloc = n; + _valLodNrender = nRender; + _valLodAllocBrightness = bMin; + // keep last, it's accessed asynchronously + _valLodBrightness = b; + } + } + + return true; + } + + bool setResolution(unsigned k) { + + if (k <= 3) { + return false; + } + +// fprintf(stderr, "Stars.cpp: setResolution(%d)\n", k); + + if (k != _valTileResolution) { // TODO make atomic + + // TODO input mutex + + unsigned n; + BrightnessLevel b, bMin; + + { // TODO lod mutex + + n = _valLodNalloc; + b = _valLodBrightness; + 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; + + { // TODO lod mutex + + // 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 = lrint(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 = lrint(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 + _valLodBrightness = b; // TODO make atomic + +// fprintf(stderr, "Stars.cpp: " +// "fraction = %lf, oaFract = %lf, n = %d, n' = %d, bMin = %d, b = %d\n", +// fraction, oaFract, lrint(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 + { // TODO input mutex + recreateRenderer(n, _valTileResolution, b, bMin); + +// fprintf(stderr, "Stars.cpp: LOD reallocation\n"); + + // publish new lod state + { // TODO lod mutex + _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) { + + Renderer* renderer = new Renderer(_seqInput, n, k, b, bMin); + swap(_ptrRenderer, renderer); // TODO make atomic + delete renderer; // will be NULL when was in use + } + + public: + + void render(float perspective, float angle, mat4 const& orientation) { + + // check out renderer + Renderer* renderer = 0l; + swap(_ptrRenderer, renderer); // TODO make atomic + + // have it render + if (renderer != 0l) { + + BrightnessLevel b = _valLodBrightness; // make atomic + + renderer->render(perspective, angle, orientation, b); + } + + // check in - or dispose if there is a new one + // TODO make atomic (CAS) + if (! _ptrRenderer) { + _ptrRenderer = renderer; + } else { + delete renderer; + } + } + + 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 diff --git a/interface/src/starfield/Loader.h b/interface/src/starfield/Loader.h new file mode 100644 index 0000000000..0db0384734 --- /dev/null +++ b/interface/src/starfield/Loader.h @@ -0,0 +1,180 @@ +// +// starfield/Loader.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__Loader__ +#define __interface__starfield__Loader__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "Config.h" + +#include "starfield/data/InputVertex.h" +#include "starfield/data/BrightnessLevel.h" + +namespace starfield { + + class Loader : UrlReader { + + InputVertices* _ptrVertices; + unsigned _valLimit; + + unsigned _valLineNo; + char const* _strUrl; + + unsigned _valRecordsRead; + BrightnessLevel _valMinBrightness; + public: + + bool loadVertices( + InputVertices& destination, char const* url, unsigned limit) + { + _ptrVertices = & destination; + _valLimit = limit; +#if STARFIELD_SAVE_MEMORY + if (_valLimit == 0 || _valLimit > 60000u) + _valLimit = 60000u; +#endif + _strUrl = url; // in case we fail early + + if (! UrlReader::readUrl(url, *this)) + { + fprintf(stderr, "%s:%d: %s\n", + _strUrl, _valLineNo, getError()); + + return false; + } + fprintf(stderr, "Stars.cpp: read %d vertices, using %d\n", + _valRecordsRead, _ptrVertices->size()); + + return true; + } + + protected: + + friend class UrlReader; + + void begin(char const* url, + char const* type, + int64_t size, + int64_t stardate) { + + _valLineNo = 0u; + _strUrl = url; // new value in http redirect + + _valRecordsRead = 0u; + + _ptrVertices->clear(); + _ptrVertices->reserve(_valLimit); +// 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; + ++_valLineNo; + 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); + } + + ++_valRecordsRead; + + } else { + + fprintf(stderr, "Stars.cpp:%d: Bad input from %s\n", + _valLineNo, _strUrl); + } + + } + return consumed; + } + + void end(bool ok) + { } + + private: + + bool atLimit() { return _valLimit > 0u && _valRecordsRead >= _valLimit; } + + bool spaceFor(BrightnessLevel b) { + + if (! atLimit()) { + return true; + } + + // just reached the limit? -> establish a minimum heap and + // remember the brightness at its top + if (_valRecordsRead == _valLimit) { + +// fprintf(stderr, "Stars.cpp: vertex limit reached -> heap mode\n"); + + make_heap( + _ptrVertices->begin(), _ptrVertices->end(), + GreaterBrightness() ); + + _valMinBrightness = getBrightness( + _ptrVertices->begin()->getColor() ); + } + + // not interested? say so + if (_valMinBrightness >= b) + return false; + + // otherwise free up space for the new vertex + pop_heap( + _ptrVertices->begin(), _ptrVertices->end(), + GreaterBrightness() ); + _ptrVertices->pop_back(); + return true; + } + + void storeVertex(float azi, float alt, unsigned color) { + + _ptrVertices->push_back(InputVertex(azi, alt, color)); + + if (atLimit()) { + + push_heap( + _ptrVertices->begin(), _ptrVertices->end(), + GreaterBrightness() ); + + _valMinBrightness = getBrightness( + _ptrVertices->begin()->getColor() ); + } + } + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/BrightnessLevel.h b/interface/src/starfield/data/BrightnessLevel.h new file mode 100644 index 0000000000..3567b88478 --- /dev/null +++ b/interface/src/starfield/data/BrightnessLevel.h @@ -0,0 +1,64 @@ +// +// starfield/data/BrightnessLevel.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__BrightnessLevel__ +#define __interface__starfield__data__BrightnessLevel__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/InputVertex.h" +#include "starfield/data/GpuVertex.h" + +namespace starfield { + + typedef nuint BrightnessLevel; + + +#if STARFIELD_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; +#if STARFIELD_SAVE_MEMORY + return BrightnessLevel((r*r+g*g+b*b) >> 2); +#else + return BrightnessLevel(r*r+g*g+b*b); +#endif + } + + + 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; + } + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/GpuVertex.h b/interface/src/starfield/data/GpuVertex.h new file mode 100644 index 0000000000..6de3b5a15f --- /dev/null +++ b/interface/src/starfield/data/GpuVertex.h @@ -0,0 +1,53 @@ +// +// starfield/data/GpuVertex.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__GpuVertex__ +#define __interface__starfield__data__GpuVertex__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/data/InputVertex.h" + +namespace starfield { + + class GpuVertex { + + unsigned _valColor; + float _valX; + float _valY; + float _valZ; + public: + + GpuVertex() { } + + GpuVertex(InputVertex const& in) { + + _valColor = 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); + _valX = gx * exz; + _valY = sin(alt); + _valZ = gz * exz; + } + + unsigned getColor() const { return _valColor; } + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/InputVertex.h b/interface/src/starfield/data/InputVertex.h new file mode 100644 index 0000000000..dac242ec51 --- /dev/null +++ b/interface/src/starfield/data/InputVertex.h @@ -0,0 +1,51 @@ +// +// starfield/data/InputVertex.h +// interface +// +// Created by Tobias Schwinger on 3/29/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__InputVertex__ +#define __interface__starfield__data__InputVertex__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" + +namespace starfield { + + class InputVertex { + + unsigned _valColor; + float _valAzimuth; + float _valAltitude; + public: + + InputVertex(float azimuth, float altitude, unsigned color) { + + _valColor = color >> 16 & 0xffu | color & 0xff00u | + color << 16 & 0xff0000u | 0xff000000u; + + azimuth = angleConvert(azimuth); + altitude = angleConvert(altitude); + + angleHorizontalPolar(azimuth, altitude); + + _valAzimuth = azimuth; + _valAltitude = altitude; + } + + float getAzimuth() const { return _valAzimuth; } + float getAltitude() const { return _valAltitude; } + unsigned getColor() const { return _valColor; } + }; + + typedef std::vector InputVertices; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/data/Tile.h b/interface/src/starfield/data/Tile.h new file mode 100644 index 0000000000..6d51921ce9 --- /dev/null +++ b/interface/src/starfield/data/Tile.h @@ -0,0 +1,37 @@ +// +// starfield/data/Tile.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__data__Tile__ +#define __interface__starfield__data__Tile__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/BrightnessLevel.h" + +namespace starfield { + + struct Tile { + + nuint offset; + nuint count; + BrightnessLevel lod; + nuint flags; + + static uint16_t const checked = 1; + static uint16_t const visited = 2; + static uint16_t const render = 4; + }; + + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/renderer/Renderer.h b/interface/src/starfield/renderer/Renderer.h new file mode 100644 index 0000000000..d9219e1d79 --- /dev/null +++ b/interface/src/starfield/renderer/Renderer.h @@ -0,0 +1,495 @@ +// +// starfield/renderer/Renderer.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__renderer__Renderer__ +#define __interface__starfield__renderer__Renderer__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/InputVertex.h" +#include "starfield/data/BrightnessLevel.h" + +#include "starfield/data/Tile.h" +#include "starfield/data/GpuVertex.h" + +#include "Tiling.h" + +// +// 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. +// + +namespace starfield { + + class Renderer { + + GpuVertex* _arrData; + Tile* _arrTile; + GLint* _arrBatchOffs; + GLsizei* _arrBatchCount; + GLuint _hndVertexArray; + Tiling _objTiling; + + unsigned* _itrOutIndex; + vec3 _vecWxform; + float _valHalfPersp; + BrightnessLevel _valMinBright; + + public: + + Renderer(InputVertices const& src, + size_t n, + unsigned k, + BrightnessLevel b, + BrightnessLevel bMin) : + + _arrData(0l), + _arrTile(0l), + _objTiling(k) { + + this->glAlloc(); + + Tiling tiling(k); + size_t nTiles = tiling.getTileCount(); + + _arrData = new GpuVertex[n]; + _arrTile = new Tile[nTiles + 1]; + _arrBatchOffs = new GLint[nTiles]; + _arrBatchCount = new GLsizei[nTiles]; + + prepareVertexData(src, n, tiling, b, bMin); + + this->glUpload(n); + } + + ~Renderer() + { + delete[] _arrData; + delete[] _arrTile; + delete[] _arrBatchCount; + delete[] _arrBatchOffs; + + this->glFree(); + } + + void render(float perspective, + float aspect, + mat4 const& orientation, + BrightnessLevel minBright) + { + +// fprintf(stderr, " +// Stars.cpp: rendering at minimal brightness %d\n", minBright); + + float halfPersp = perspective * 0.5f; + + // 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(halfPersp); + float near = std::cos(halfPersp); + + float hw = 0.5f * sqrt(diag * diag / (1.0f + aspect * aspect)); + float hh = hw * aspect; + + // cancel all translation + mat4 matrix = orientation; + 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); +#if STARFIELD_HEMISPHERE_ONLY + altitude = std::max(0.0f, altitude); +#endif + unsigned tileIndex = + _objTiling.getTileIndex(azimuth, altitude); + +// fprintf(stderr, "Stars.cpp: starting on tile #%d\n", tileIndex); + + +#if STARFIELD_DEBUG_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->_itrOutIndex = (unsigned*) _arrBatchOffs; + this->_vecWxform = swizzle(row(matrix, 3)); + this->_valHalfPersp = halfPersp; + this->_valMinBright = minBright; + + floodFill(_arrTile + tileIndex, TileSelection(*this, + _arrTile, _arrTile + _objTiling.getTileCount(), + (Tile**) _arrBatchCount)); + +#if STARFIELD_DEBUG_LOD +# define matrix matrix_debug +#endif + this->glBatch(glm::value_ptr(matrix), prepareBatch( + (unsigned*) _arrBatchOffs, _itrOutIndex) ); + +#if STARFIELD_DEBUG_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 vertexIndex = 0u, currTileIndex = 0u, count_active = 0u; + + _arrTile[0].offset = 0u; + _arrTile[0].lod = b; + _arrTile[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 tileIndex = tiling.getTileIndex( + i->getAzimuth(), i->getAltitude()); + + assert(tileIndex >= currTileIndex); + + // moved on to another tile? -> flush + if (tileIndex != currTileIndex) { + + Tile* t = _arrTile + currTileIndex; + Tile* tLast = _arrTile + tileIndex; + + // set count of active vertices (upcoming lod) + t->count = count_active; + // generate skipped, empty tiles + for(size_t offs = vertexIndex; ++t != tLast ;) { + t->offset = offs, t->count = 0u, + t->lod = b, t->flags = 0u; + } + + // initialize next (as far as possible here) + tLast->offset = vertexIndex; + tLast->lod = b; + tLast->flags = 0u; + + currTileIndex = tileIndex; + count_active = 0u; + } + + if (bv >= b) + ++count_active; + +// fprintf(stderr, "Stars.cpp: Vertex %d on tile #%d\n", vertexIndex, tileIndex); + + // write converted vertex + _arrData[vertexIndex++] = *i; + } + } + assert(vertexIndex == n); + // flush last tile (see above) + Tile* t = _arrTile + currTileIndex; + t->count = count_active; + for (Tile* e = _arrTile + nTiles + 1; ++t != e;) { + t->offset = vertexIndex, t->count = 0u, + t->lod = b, t->flags = 0; + } + } + + + private: // FOV culling / LOD + + class TileSelection; + friend class Renderer::TileSelection; + + class TileSelection { + + Renderer& _refRenderer; + Tile** const _arrStack; + Tile** _itrStack; + Tile const* const _arrTile; + Tile const* const _itrTilesEnd; + + public: + + TileSelection(Renderer& renderer, Tile const* tiles, + Tile const* tiles_end, Tile** stack) : + + _refRenderer(renderer), + _arrStack(stack), + _itrStack(stack), + _arrTile(tiles), + _itrTilesEnd(tiles_end) { + } + + protected: + + // flood fill strategy + + bool select(Tile* t) { + + if (t < _arrTile || t >= _itrTilesEnd || + !! (t->flags & Tile::visited)) { + + return false; + } + if (! (t->flags & Tile::checked)) { + + if (_refRenderer.visitTile(t)) + t->flags |= Tile::render; + } + return !! (t->flags & Tile::render); + } + + void process(Tile* t) { + + t->flags |= Tile::visited; + } + + 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) { *_itrStack++ = t; } + + bool deferred(Tile*& cursor) { + + if (_itrStack != _arrStack) { + cursor = *--_itrStack; + return true; + } + return false; + } + + private: + unsigned yStride() const { + + return _refRenderer._objTiling.getAzimuthalTiles(); + } + }; + + bool visitTile(Tile* t) { + + unsigned index = t - _arrTile; + *_itrOutIndex++ = index; + + if (! tileVisible(t, index)) { + return false; + } + + if (t->lod != _valMinBright) { + updateVertexCount(t, _valMinBright); + } + return true; + } + + bool tileVisible(Tile* t, unsigned i) { + + float slice = _objTiling.getSliceAngle(); + unsigned stride = _objTiling.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 tileCenter = vec3(gx * exz, sin(altitude), gz * exz); + float w = dot(_vecWxform, tileCenter); + + float halfSlice = 0.5f * slice; + float daz = halfSlice * cos(abs(altitude) - halfSlice); + float dal = halfSlice; + float near = cos(_valHalfPersp + 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 = _arrData + t[0].offset; + GpuVertex const* end = _arrData + 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 >= _arrData + t[0].offset); + + t->count = end - _arrData - t[0].offset; + t->lod = minBright; + } + + unsigned prepareBatch(unsigned const* indices, + unsigned const* indicesEnd) { + + unsigned nRanges = 0u; + GLint* offs = _arrBatchOffs; + GLsizei* count = _arrBatchCount; + + for (unsigned* i = (unsigned*) _arrBatchOffs; + i != indicesEnd; ++i) { + + Tile* t = _arrTile + *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, & _hndVertexArray); + } + + void glFree() { + + glDeleteVertexArrays(1, & _hndVertexArray); + } + + void glUpload(GLsizei n) { + + GLuint vbo; + glGenBuffers(1, & vbo); + + glBindVertexArray(_hndVertexArray); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, + n * sizeof(GpuVertex), _arrData, 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, +// _arrBatchOffs[i], _arrBatchCount[i]); + + // setup modelview matrix (identity) + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // set projection matrix + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadMatrixf(matrix); + + // render + glBindVertexArray(_hndVertexArray); + + glEnable(GL_POINT_SMOOTH); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glPointSize(1.42f); + + glMultiDrawArrays(GL_POINTS, + _arrBatchOffs, _arrBatchCount, n_ranges); + + // restore state + glBindVertexArray(0); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } +#ifdef __APPLE__ +# undef glBindVertexArray +# undef glGenVertexArrays +# undef glDeleteVertexArrays +#endif + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/renderer/Tiling.h b/interface/src/starfield/renderer/Tiling.h new file mode 100644 index 0000000000..e1e8e65138 --- /dev/null +++ b/interface/src/starfield/renderer/Tiling.h @@ -0,0 +1,71 @@ +// +// starfield/renderer/Tiling.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__renderer__Tiling__ +#define __interface__starfield__renderer__Tiling__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" + +namespace starfield { + + class Tiling { + + unsigned _valK; + float _valRcpSlice; + unsigned _valBits; + + public: + + Tiling(unsigned k) : + _valK(k), + _valRcpSlice(k / Radians::twice_pi()) { + _valBits = ceil(log2(getTileCount())); + } + + unsigned getAzimuthalTiles() const { return _valK; } + unsigned getAltitudinalTiles() const { return _valK / 2 + 1; } + unsigned getTileIndexBits() const { return _valBits; } + + unsigned getTileCount() const { + return getAzimuthalTiles() * getAltitudinalTiles(); + } + + unsigned getTileIndex(float azimuth, float altitude) const { + return discreteAzimuth(azimuth) + + _valK * discreteAltitude(altitude); + } + + float getSliceAngle() const { + return 1.0f / _valRcpSlice; + } + + private: + + unsigned discreteAngle(float unsigned_angle) const { + return unsigned(round(unsigned_angle * _valRcpSlice)); + } + + unsigned discreteAzimuth(float a) const { + return discreteAngle(a) % _valK; + } + + unsigned discreteAltitude(float a) const { + return min(getAltitudinalTiles() - 1, + discreteAngle(a + Radians::half_pi()) ); + } + + }; + +} // anonymous namespace + +#endif + diff --git a/interface/src/starfield/renderer/VertexOrder.h b/interface/src/starfield/renderer/VertexOrder.h new file mode 100644 index 0000000000..13784de53e --- /dev/null +++ b/interface/src/starfield/renderer/VertexOrder.h @@ -0,0 +1,52 @@ +// +// starfield/renderer/VertexOrder.h +// interface +// +// Created by Tobias Schwinger on 3/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__starfield__renderer__VertexOrder__ +#define __interface__starfield__renderer__VertexOrder__ + +#ifndef __interface__Starfield_impl__ +#error "This is an implementation file - not intended for direct inclusion." +#endif + +#include "starfield/Config.h" +#include "starfield/data/InputVertex.h" +#include "starfield/renderer/Tiling.h" + +namespace starfield { + + /** + * Defines the vertex order for the renderer as a bit extractor for + * binary in-place Radix Sort. + */ + class VertexOrder : public Radix2IntegerScanner + { + Tiling _objTiling; + + typedef Radix2IntegerScanner base; + public: + + explicit VertexOrder(Tiling const& tiling) : + + base(tiling.getTileIndexBits() + BrightnessBits), + _objTiling(tiling) { + } + + bool bit(InputVertex const& v, state_type const& s) const { + + // inspect (tile_index, brightness) tuples + unsigned key = getBrightness(v.getColor()) ^ BrightnessMask; + key |= _objTiling.getTileIndex( + v.getAzimuth(), v.getAltitude()) << BrightnessBits; + return base::bit(key, s); + } + }; + +} // anonymous namespace + +#endif +