From 2f93d3f41b5c05394b32c8452f93df12ab860be9 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Fri, 6 Oct 2023 18:09:05 +0200 Subject: [PATCH] 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. --- .../MilkdropPreset/MilkdropShader.cpp | 39 ++++++++ src/libprojectM/Renderer/TextureManager.cpp | 92 ++++++++++++------- src/libprojectM/Renderer/TextureManager.hpp | 14 ++- .../Renderer/TextureSamplerDescriptor.cpp | 18 ++++ 4 files changed, 125 insertions(+), 38 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp index 2d34463e7..a0df8d2a5 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp @@ -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)); } diff --git a/src/libprojectM/Renderer/TextureManager.cpp b/src/libprojectM/Renderer/TextureManager.cpp index f126febaf..c53a3aa4f 100644 --- a/src/libprojectM/Renderer/TextureManager.cpp +++ b/src/libprojectM/Renderer/TextureManager.cpp @@ -11,6 +11,7 @@ #include #include +#include #include // 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 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 distribution(0, m_scannedTextureFiles.size() - 1); + selectedFilename = m_scannedTextureFiles.at(distribution(rndEngine)).lowerCaseBaseName; + } + else + { + + std::vector 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 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; + } +} diff --git a/src/libprojectM/Renderer/TextureManager.hpp b/src/libprojectM/Renderer/TextureManager.hpp index 364d4b2d8..110450121 100644 --- a/src/libprojectM/Renderer/TextureManager.hpp +++ b/src/libprojectM/Renderer/TextureManager.hpp @@ -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 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 m_scannedTextureFiles; //!< The cached list with scanned texture files. diff --git a/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp b/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp index ed68c338a..52d9f41d8 100644 --- a/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp +++ b/src/libprojectM/Renderer/TextureSamplerDescriptor.cpp @@ -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(texture->Width()), 1.0f / static_cast(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(texture->Width()), + 1.0f / static_cast(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;