Reimplement random texture selection.

Use the same basic logic as Milkdrop. Also automatically declare a shorthand "texsize_randXX" uniform for prefixed random texture names as preset authors expect.
This commit is contained in:
Kai Blaschke
2023-10-06 18:09:05 +02:00
parent 116de5733c
commit 2f93d3f41b
4 changed files with 125 additions and 38 deletions

View File

@ -55,6 +55,8 @@ void MilkdropShader::LoadCode(const std::string& presetShaderCode)
void MilkdropShader::LoadTexturesAndCompile(PresetState& presetState)
{
std::locale loc;
// Now request the textures and descriptors from the texture manager.
for (const auto& name : m_samplerNames)
{
@ -95,6 +97,43 @@ void MilkdropShader::LoadTexturesAndCompile(PresetState& presetState)
continue;
}
// Random textures need special treatment.
if (lowerCaseName.length() >= 6 &&
lowerCaseName.substr(0, 4) == "rand"
&& std::isdigit(lowerCaseName.at(4), loc)
&& std::isdigit(lowerCaseName.at(5), loc)
)
{
// First look up the random texture index in the preset state so the texture matches between warp and composite shaders
int randomSlot = -1;
try {
randomSlot = std::stoi(lowerCaseName.substr(4, 2));
}
catch (...) // Ignore any conversion errors.
{}
if (randomSlot >= 0 && randomSlot <= 15)
{
if (presetState.randomTextureDescriptors.find(randomSlot) != presetState.randomTextureDescriptors.end())
{
// Use existing texture descriptor.
m_textureSamplerDescriptors.push_back(presetState.randomTextureDescriptors.at(randomSlot));
continue;
}
// Slot empty, request a new random texture.
auto desc = presetState.renderContext.textureManager->GetRandomTexture(name);
// Also store a copy in preset state!
presetState.randomTextureDescriptors.insert({randomSlot, desc});
m_textureSamplerDescriptors.push_back(std::move(desc));
continue;
}
// Fall through if slot number is out of range and treat as normal texture.
}
auto desc = presetState.renderContext.textureManager->GetTexture(name);
m_textureSamplerDescriptors.push_back(std::move(desc));
}

View File

@ -11,6 +11,7 @@
#include <algorithm>
#include <memory>
#include <random>
#include <vector>
// Missing in macOS SDK. Query will most certainly fail, but then use the default format.
@ -218,15 +219,7 @@ TextureSamplerDescriptor TextureManager::TryLoadingTexture(const std::string& na
ExtractTextureSettings(name, wrapMode, filterMode, unqualifiedName);
if (!m_filesScanned)
{
FileScanner fileScanner = FileScanner(m_textureSearchPaths, m_extensions);
// scan for textures
using namespace std::placeholders;
fileScanner.scan(std::bind(&TextureManager::AddTextureFile, this, _1, _2));
m_filesScanned = true;
}
ScanTextures();
std::string lowerCaseFileName(name);
std::transform(lowerCaseFileName.begin(), lowerCaseFileName.end(), lowerCaseFileName.begin(), tolower);
@ -292,47 +285,64 @@ TextureSamplerDescriptor TextureManager::LoadTexture(const std::string& fileName
return {newTexture, sampler, name, unqualifiedName};
}
TextureSamplerDescriptor TextureManager::GetRandomTexture(const std::string& randomName)
auto TextureManager::GetRandomTexture(const std::string& randomName) -> TextureSamplerDescriptor
{
GLint wrapMode;
GLint filterMode;
std::string unqualifiedName;
std::string selectedFilename;
ExtractTextureSettings(randomName, wrapMode, filterMode, unqualifiedName);
std::random_device rndDevice;
std::default_random_engine rndEngine(rndDevice());
std::vector<std::string> user_texture_names;
size_t separator = unqualifiedName.find('_');
std::string textureNameFilter;
ScanTextures();
if (separator != std::string::npos)
std::string lowerCaseName(randomName);
std::transform(lowerCaseName.begin(), lowerCaseName.end(), lowerCaseName.begin(), tolower);
if (m_scannedTextureFiles.empty())
{
textureNameFilter = unqualifiedName.substr(separator + 1);
unqualifiedName = unqualifiedName.substr(0, separator);
return {};
}
for (auto& texture : m_textures)
std::string prefix;
if (lowerCaseName.length() > 7 && lowerCaseName.at(6) == '_')
{
if (texture.second->IsUserTexture())
prefix = lowerCaseName.substr(7);
}
if (prefix.empty())
{
// Just pick a random index.
std::uniform_int_distribution<size_t> distribution(0, m_scannedTextureFiles.size() - 1);
selectedFilename = m_scannedTextureFiles.at(distribution(rndEngine)).lowerCaseBaseName;
}
else
{
std::vector<ScannedFile> filteredFiles;
auto prefixLength = prefix.length();
std::copy_if(m_scannedTextureFiles.begin(), m_scannedTextureFiles.end(),
std::back_inserter(filteredFiles),
[&prefix, prefixLength](const ScannedFile& file) {
return file.lowerCaseBaseName.substr(0, prefixLength) == prefix;
});
if (!filteredFiles.empty())
{
if (textureNameFilter.empty() || texture.first.find(textureNameFilter) == 0)
{
user_texture_names.push_back(texture.first);
}
std::uniform_int_distribution<size_t> distribution(0, filteredFiles.size() - 1);
selectedFilename = filteredFiles.at(distribution(rndEngine)).lowerCaseBaseName;
}
}
if (!user_texture_names.empty())
// If a prefix was set and no file matched, filename can be empty.
if (selectedFilename.empty())
{
std::string random_name = user_texture_names[rand() % user_texture_names.size()];
m_randomTextures.push_back(randomName);
auto randomTexture = m_textures[random_name];
auto sampler = m_samplers.at({wrapMode, filterMode});
return {randomTexture, sampler, randomName, unqualifiedName};
return {};
}
return {};
// Use selected filename to load the texture.
auto desc = GetTexture(selectedFilename);
// Create new descriptor with the original "rand00[_prefix]" name.
return {desc.Texture(), desc.Sampler(), randomName, randomName};
}
void TextureManager::AddTextureFile(const std::string& fileName, const std::string& baseName)
@ -395,3 +405,15 @@ void TextureManager::ExtractTextureSettings(const std::string& qualifiedName, GL
wrapMode = GL_REPEAT;
}
}
void TextureManager::ScanTextures()
{
if (!m_filesScanned)
{
FileScanner fileScanner = FileScanner(m_textureSearchPaths, m_extensions);
using namespace std::placeholders;
fileScanner.scan(std::bind(&TextureManager::AddTextureFile, this, _1, _2));
m_filesScanned = true;
}
}

View File

@ -31,13 +31,21 @@ public:
void SetCurrentPresetPath(const std::string& path);
/**
* @brief Requests a texture and sampler with the given name.
* @brief Loads a texture and returns a descriptor with the given name.
* Resets the texture age to zero.
* @param fullName
* @return
*/
auto GetTexture(const std::string& fullName) -> TextureSamplerDescriptor;
/**
* @brief Returns a random texture descriptor, optionally using a prefix (after the `randXX_` name).
* Will use the default texture loading logic by calling GetTexture() if a texture was selected.
* @param randomName The filename prefix to filter. If empty, all available textures are matches. Case-insensitive.
* @return A texture descriptor with the random texture and a default sampler, or an empty sampler if no texture could be matched.
*/
auto GetRandomTexture(const std::string& randomName) -> TextureSamplerDescriptor;
/**
* @brief Returns a sampler for the given name.
* Does not load any texture, only analyzes the prefix.
@ -74,8 +82,6 @@ private:
auto TryLoadingTexture(const std::string& name) -> TextureSamplerDescriptor;
auto GetRandomTexture(const std::string& randomName) -> TextureSamplerDescriptor;
void Preload();
TextureSamplerDescriptor LoadTexture(const std::string& fileName, const std::string& name);
@ -84,6 +90,8 @@ private:
static void ExtractTextureSettings(const std::string& qualifiedName, GLint& wrapMode, GLint& filterMode, std::string& name);
void ScanTextures();
std::vector<std::string> m_textureSearchPaths; //!< Search paths to scan for textures.
std::string m_currentPresetDir; //!< Path of the current preset to add to the search list.
std::vector<ScannedFile> m_scannedTextureFiles; //!< The cached list with scanned texture files.

View File

@ -24,6 +24,7 @@ void TextureSamplerDescriptor::Bind(GLint unit, const Shader& shader) const
{
auto texture = m_texture.lock();
auto sampler = m_sampler.lock();
if (texture && sampler)
{
texture->Bind(unit, sampler);
@ -34,6 +35,14 @@ void TextureSamplerDescriptor::Bind(GLint unit, const Shader& shader) const
texture->Height(),
1.0f / static_cast<float>(texture->Width()),
1.0f / static_cast<float>(texture->Height())});
// Bind shorthand random texture size uniform
if (m_sizeName.substr(0, 4) == "rand" && m_sizeName.length() > 7 && m_sizeName.at(6) == '_')
{
shader.SetUniformFloat4(std::string("texsize_" + m_sizeName.substr(0, 6)).c_str(), {texture->Width(),
texture->Height(),
1.0f / static_cast<float>(texture->Width()),
1.0f / static_cast<float>(texture->Height())});
}
}
}
@ -101,6 +110,15 @@ auto TextureSamplerDescriptor::TexSizeDeclaration() const -> std::string
declaration.append("uniform float4 texsize_");
declaration.append(m_sizeName);
declaration.append(";\n");
// Add short texsize uniform for prefixed random textures.
// E.g. "texsize_rand00" if a sampler "sampler_rand00_smalltiled" was declared
if (m_sizeName.substr(0, 4) == "rand" && m_sizeName.length() > 7 && m_sizeName.at(6) == '_')
{
declaration.append("uniform float4 texsize_");
declaration.append(m_sizeName.substr(0, 6));
declaration.append(";\n");
}
}
return declaration;