Add the ability to load images lazily for RenderablePlanes

Add ability to purge textures from RAM if they are read-only
Make use of both for constellation images to reduce the memory footprint

(cherry picked from commit 7f0c92430f)
This commit is contained in:
Alexander Bock
2020-06-15 16:07:16 +02:00
parent b3ee03b8db
commit e5719952b1
7 changed files with 130 additions and 90 deletions

View File

@@ -1,11 +1,102 @@
local constellation_helper = asset.require('./generate_constellations')
local constellationsCSV = asset.localResource('constellation_data.csv')
local transforms = asset.require("scene/solarsystem/sun/transforms")
local images = asset.syncedResource({
Name = "Constellation Images",
Type = "HttpSynchronization",
Identifier = "constellation_images",
Version = 1
})
-- local image_resolution = "low"
-- local image_resolution = "medium"
local image_resolution = "high"
--function that reads the file
local createConstellations = function (guiPath, constellationfile)
local genConstellations = {};
--skip the first line
local notFirstLine = false;
-- define parsec to meters
local PARSEC_CONSTANT = 3.0856776E16;
-- how many parsecs away do you want the images to be?
-- this setting puts the billboards at the location of the constellation bounds grid from DU.
-- but they can really be anywhere since the billboard size will scale with distance.
local distanceMultiplier = 3.2;
local baseScale = 1e17;
for line in io.lines(openspace.absPath(constellationfile)) do
if (notFirstLine) then
-- describes the data
local matchstring = '(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-)$'
local group, abbreviation, name, x, y, z, scale, imageName, rotX, rotY, rotZ, centerStar = line:match(matchstring)
local magVec = math.sqrt(x*x + y*y + z*z)
local normx = x/magVec
local normy = y/magVec
local normz = z/magVec
group = (group == '' and globe or group)
local texture
if image_resolution == "low" then
texture = images .. "/" .. imageName:sub(1, -5) .. "-512.png"
elseif image_resolution == "medium" then
texture = images .. "/" .. imageName:sub(1, -5) .. "-1024.png"
elseif image_resolution == "high" then
texture = images .. "/" .. imageName
else
openspace.printError("Unknown image resolution: " .. image_resolution)
end
local aconstellation = {
Identifier = guiPath .. '-' .. name,
Parent = transforms.SolarSystemBarycenter.Identifier,
Transform = {
Translation = {
Type = "StaticTranslation",
-- position is in parsecs from the SolarSystemBarycenter, so convert to meters
Position = {
normx * PARSEC_CONSTANT * distanceMultiplier,
normy * PARSEC_CONSTANT * distanceMultiplier,
normz * PARSEC_CONSTANT * distanceMultiplier
}
},
Rotation = {
Type = "StaticRotation",
Rotation = { tonumber(rotX), tonumber(rotY), tonumber(rotZ) }
}
},
Renderable = {
Type = "RenderablePlaneImageLocal",
Size = tonumber(baseScale * scale * distanceMultiplier / 10),
Enabled = false,
Origin = "Center",
Billboard = false,
LazyLoading = true,
Texture = texture,
BlendMode = "Additive",
Opacity = 0.1
},
Tag = { "ConstellationArtImage", group },
GUI = {
Name = name .. ' Image',
Path = '/Milky Way/' .. guiPath
}
}
table.insert(genConstellations, aconstellation);
else
notFirstLine = true
end
end
return genConstellations
end
local nodes = {}
asset.onInitialize(function ()
nodes = constellation_helper.createConstellations('Constellation Art', constellationsCSV)
nodes = createConstellations('Constellation Art', constellationsCSV)
for _, n in ipairs(nodes) do
openspace.addSceneGraphNode(n);
end

View File

@@ -1,79 +0,0 @@
local assetHelper = asset.require('util/asset_helper')
local transforms = asset.require("scene/solarsystem/sun/transforms")
local images = asset.syncedResource({
Name = "Constellation Images",
Type = "HttpSynchronization",
Identifier = "constellation_images",
Version = 1
})
--function that reads the file
local createConstellations = function (guiPath, constellationfile)
local genConstellations = {};
--skip the first line
local notFirstLine = false;
-- define parsec to meters
local PARSEC_CONSTANT = 3.0856776E16;
-- how many parsecs away do you want the images to be?
-- this setting puts the billboards at the location of the constellation bounds grid from DU.
-- but they can really be anywhere since the billboard size will scale with distance.
local distanceMultiplier = 3.2;
local baseScale = 1e17;
for line in io.lines(openspace.absPath(constellationfile)) do
if (notFirstLine) then
-- describes the data
local matchstring = '(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-),(.-)$'
local group, abbreviation, name, x, y, z, scale, imageName, rotX, rotY, rotZ, centerStar = line:match(matchstring)
local magVec = math.sqrt(x*x + y*y + z*z)
local normx = x/magVec
local normy = y/magVec
local normz = z/magVec
group = (group == '' and globe or group)
local aconstellation = {
Identifier = guiPath .. '-' .. name,
Parent = transforms.SolarSystemBarycenter.Identifier,
Transform = {
Translation = {
Type = "StaticTranslation",
-- position is in parsecs from the SolarSystemBarycenter, so convert to meters
Position = {
normx * PARSEC_CONSTANT * distanceMultiplier,
normy * PARSEC_CONSTANT * distanceMultiplier,
normz * PARSEC_CONSTANT * distanceMultiplier
}
},
Rotation = {
Type = "StaticRotation",
Rotation = { tonumber(rotX), tonumber(rotY), tonumber(rotZ) }
}
},
Renderable = {
Type = "RenderablePlaneImageLocal",
Size = tonumber(baseScale*scale*distanceMultiplier/10),
Enabled = false,
Origin = "Center",
Billboard = false,
Texture = images .. "/" .. imageName,
BlendMode = "Additive",
Opacity = 0.1
},
Tag = { "ConstellationArtImage", group },
GUI = {
Name = name .. ' Image',
Path = '/Milky Way/' .. guiPath
}
}
table.insert(genConstellations, aconstellation);
else
notFirstLine = true
end
end
return genConstellations
end
asset.export('createConstellations', createConstellations)

View File

@@ -36,6 +36,8 @@
#include <fstream>
namespace {
constexpr const char* KeyLazyLoading = "LazyLoading";
constexpr openspace::properties::Property::PropertyInfo TextureInfo = {
"Texture",
"Texture",
@@ -70,6 +72,15 @@ documentation::Documentation RenderablePlaneImageLocal::Documentation() {
new StringVerifier,
Optional::Yes,
RenderableTypeInfo.description
},
{
KeyLazyLoading,
new BoolVerifier,
Optional::Yes,
"If this value is set to 'true', the image for this plane will not be "
"loaded at startup but rather when image is shown for the first time. "
"Additionally, if the plane is hidden, the image will automatically be "
"unloaded"
}
}
};
@@ -116,16 +127,34 @@ RenderablePlaneImageLocal::RenderablePlaneImageLocal(const ghoul::Dictionary& di
else {
setRenderBin(Renderable::RenderBin::Opaque);
}
if (dictionary.hasKey(KeyLazyLoading)) {
_isLoadingLazily = dictionary.value<bool>(KeyLazyLoading);
if (_isLoadingLazily) {
_enabled.onChange([this]() {
if (!_enabled) {
BaseModule::TextureManager.release(_texture);
_texture = nullptr;
}
if (_enabled) {
_textureIsDirty = true;
}
});
}
}
}
bool RenderablePlaneImageLocal::isReady() const {
return RenderablePlane::isReady() && (_texture != nullptr);
return RenderablePlane::isReady();
}
void RenderablePlaneImageLocal::initializeGL() {
RenderablePlane::initializeGL();
loadTexture();
if (!_isLoadingLazily) {
loadTexture();
}
}
void RenderablePlaneImageLocal::deinitializeGL() {
@@ -165,8 +194,8 @@ void RenderablePlaneImageLocal::loadTexture() {
fmt::format("Loaded texture from '{}'", absPath(path))
);
texture->uploadTexture();
texture->setFilter(ghoul::opengl::Texture::FilterMode::LinearMipMap);
texture->purgeFromRAM();
return texture;
}

View File

@@ -60,6 +60,7 @@ private:
ghoul::opengl::Texture* _texture = nullptr;
std::unique_ptr<ghoul::filesystem::File> _textureFile;
bool _isLoadingLazily = false;
bool _textureIsDirty = false;
};

View File

@@ -133,10 +133,8 @@ void RenderablePlaneImageOnline::update(const UpdateData&) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
texture->uploadTexture();
// Textures of planets looks much smoother with AnisotropicMipMap rather
// than linear
texture->setFilter(ghoul::opengl::Texture::FilterMode::LinearMipMap);
texture->purgeFromRAM();
_texture = std::move(texture);
_textureIsDirty = false;