WS30: Improve tile loading times by ~50%

Previously on my system, generating vegetation for a given tile
took 0.15-0.2s (for ~ 56k positions), of ~0.4s.  While this is done in a
separate loading thread, the sheer number of tiles being loaded
caused noticeable delays and popping of higher LoD tiles.

As a tile at the highest LoD is typically 6x6km, and vegetation
is generated on a scanline of individual triangles, there is a _lot_
of temporal locality on the landclass of a series of points.

By caching the material information required from the landclass lookup
we can avoid a whole much of material lookups for each successive
vegetation point.

On my system this reduced the time to generate vegetation down to
0.02s per tile, and total tile generation time down to ~0.2s.
This commit is contained in:
Stuart Buchanan 2021-08-03 20:56:56 +01:00
parent e93839a9c5
commit 578a8acff4

View File

@ -1464,6 +1464,19 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator)
const double D = det2(ll_x, ll_y); const double D = det2(ll_x, ll_y);
// At the detailed tile level we are generating vegetation, and
// as we walk across the tile in a scanline, the landclass doesn't
// change regularly from point to point. Cache the required
// material information for the current landclass to reduce the
// number of lookups into the material cache.
int current_land_class = -1;
osg::Texture2D* object_mask = NULL;
osg::Image* img = NULL;
float x_scale = 1000.0;
float y_scale = 1000.0;
TreeBin* bin = NULL;
float wood_coverage = 0.0;
for (int lat_int = min_lat - 1; lat_int <= max_lat + 1; lat_int++) for (int lat_int = min_lat - 1; lat_int <= max_lat + 1; lat_int++)
{ {
const double lat = (lat_int - off_y) * delta_lat; const double lat = (lat_int - off_y) * delta_lat;
@ -1480,28 +1493,78 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator)
continue; continue;
} }
osg::Vec2 t = osg::Vec2(t_0 + t_x * x + t_y * y);
if (!image) { if (!image) {
SG_LOG(SG_TERRAIN, SG_ALERT, "Image disappeared under my feet."); SG_LOG(SG_TERRAIN, SG_ALERT, "Image disappeared under my feet.");
continue; continue;
} }
osg::Vec2 t = osg::Vec2(t_0 + t_x * x + t_y * y);
unsigned int tx = (unsigned int) (image->s() * t.x()) % image->s(); unsigned int tx = (unsigned int) (image->s() * t.x()) % image->s();
unsigned int ty = (unsigned int) (image->t() * t.y()) % image->t(); unsigned int ty = (unsigned int) (image->t() * t.y()) % image->t();
const osg::Vec4 tc = image->getColor(tx, ty); const osg::Vec4 tc = image->getColor(tx, ty);
const int land_class = int(round(tc.x() * 255.0)); const int land_class = int(round(tc.x() * 255.0));
mat = matcache->find(land_class);
if (!mat) { if (land_class != current_land_class) {
//SG_LOG(SG_TERRAIN, SG_ALERT, "Failed to find material for landclass " << land_class << " (" << tc.r() << ")"); // Use temporal locality to reduce material lookup by caching
continue; // some elements for future lookups against the same landclass.
mat = matcache->find(land_class);
if (!mat) continue;
current_land_class = land_class;
if (mat->get_wood_coverage() <= 0) continue;
wood_coverage = 2000.0 / mat->get_wood_coverage();
object_mask = mat->get_one_object_mask(0);
if (object_mask != NULL) {
img = object_mask->getImage();
if (!img || ! img->valid()) continue;
// Texture coordinates run [0..1][0..1] across the entire tile whereas
// the texure itself has defined dimensions in m.
// We therefore need to use the tile width and height to determine the correct
// texture coordinate transformation.
x_scale = buffer._width / 1000.0;
y_scale = buffer._height / 1000.0;
if (mat->get_xsize() > 0.0) { x_scale = buffer._width / mat->get_xsize(); }
if (mat->get_ysize() > 0.0) { y_scale = buffer._height / mat->get_ysize(); }
}
bool found = false;
for (SGTreeBinList::iterator iter = randomForest.begin(); iter != randomForest.end(); iter++) {
bin = *iter;
if ((bin->texture == mat->get_tree_texture() ) &&
(bin->teffect == mat->get_tree_effect() ) &&
(bin->texture_varieties == mat->get_tree_varieties()) &&
(bin->range == mat->get_tree_range() ) &&
(bin->width == mat->get_tree_width() ) &&
(bin->height == mat->get_tree_height() ) ) {
found = true;
break;
}
}
if (!found) {
bin = new TreeBin();
bin->texture = mat->get_tree_texture();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree texture " << bin->texture);
bin->teffect = mat->get_tree_effect();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree effect " << bin->teffect);
bin->range = mat->get_tree_range();
bin->width = mat->get_tree_width();
bin->height = mat->get_tree_height();
bin->texture_varieties = mat->get_tree_varieties();
randomForest.push_back(bin);
}
} }
if (!mat) continue;
if (mat->get_wood_coverage() <= 0) continue; if (mat->get_wood_coverage() <= 0) continue;
float wood_coverage = 2000.0 / mat->get_wood_coverage();
if (pc_map_rand(lon_int, lat_int, 2) > wood_coverage) continue; if (pc_map_rand(lon_int, lat_int, 2) > wood_coverage) continue;
if (mat->get_is_plantation()) { if (mat->get_is_plantation()) {
@ -1515,81 +1578,26 @@ void VPBTechnique::applyTrees(BufferData& buffer, Locator* masterLocator)
x = det2(ll_x, p) / D; x = det2(ll_x, p) / D;
y = det2(p, ll_y) / D; y = det2(p, ll_y) / D;
if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) // Check for invalid triangle coordinates.
{ if ((x < 0.0) || (y < 0.0) || (x + y > 1.0)) continue;
continue;
}
t = osg::Vec2(t_0 + t_x * x + t_y * y);
// Check against any object mask // Check against any object mask
osg::Texture2D* object_mask = mat->get_one_object_mask(0);
if (object_mask != NULL) { if (object_mask != NULL) {
osg::Image* img = object_mask->getImage(); t = osg::Vec2(t_0 + t_x * x + t_y * y);
if (!img || ! img->valid()) continue;
// Texture coordinates run [0..1][0..1] across the entire tile whereas
// the texure itself has defined dimensions in m.
// We therefore need to use the tile width and height to determine the correct
// texture coordinate transformation.
float x_scale = buffer._width / 1000.0;
float y_scale = buffer._height / 1000.0;
if (mat->get_xsize() > 0.0) { x_scale = buffer._width / mat->get_xsize(); }
if (mat->get_ysize() > 0.0) { y_scale = buffer._height / mat->get_ysize(); }
unsigned int x = (unsigned int) (img->s() * t.x() * x_scale) % img->s(); unsigned int x = (unsigned int) (img->s() * t.x() * x_scale) % img->s();
unsigned int y = (unsigned int) (img->t() * t.y() * y_scale) % img->t(); unsigned int y = (unsigned int) (img->t() * t.y() * y_scale) % img->t();
// green (for trees) channel // green (for trees) channel
if (pc_map_rand(lon_int, lat_int, 3) > img->getColor(x, y).g()) continue;
if (pc_map_rand(lon_int, lat_int, 3) > img->getColor(x, y).g()) {
continue;
}
}
TreeBin* bin = NULL;
bool found = false;
for (SGTreeBinList::iterator iter = randomForest.begin(); iter != randomForest.end(); iter++) {
bin = *iter;
if ((bin->texture == mat->get_tree_texture() ) &&
(bin->teffect == mat->get_tree_effect() ) &&
(bin->texture_varieties == mat->get_tree_varieties()) &&
(bin->range == mat->get_tree_range() ) &&
(bin->width == mat->get_tree_width() ) &&
(bin->height == mat->get_tree_height() ) ) {
found = true;
break;
}
}
if (!found) {
bin = new TreeBin();
bin->texture = mat->get_tree_texture();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree texture " << bin->texture);
bin->teffect = mat->get_tree_effect();
SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree effect " << bin->teffect);
bin->range = mat->get_tree_range();
bin->width = mat->get_tree_width();
bin->height = mat->get_tree_height();
bin->texture_varieties = mat->get_tree_varieties();
randomForest.push_back(bin);
} }
// Check against constraints to stop trees growing from roads;
const osg::Vec3 vp = v_x * x + v_y * y + v_0; const osg::Vec3 vp = v_x * x + v_y * y + v_0;
if (checkAgainstVegetationConstraints(vp - up*100, vp + up*100)) continue;
if (! checkAgainstVegetationConstraints(vp - up*100, vp + up*100)) { bin->insert(SGVec3f(vp.x(), vp.y(), vp.z()), SGVec3f(n.x(), n.y(), n.z()));
bin->insert(SGVec3f(vp.x(), vp.y(), vp.z()), SGVec3f(n.x(), n.y(), n.z()));
}
} }
} }
} }
if (randomForest.size() > 0) { if (randomForest.size() > 0) {