diff --git a/cmake/GenerateShaderResources.cmake b/cmake/GenerateShaderResources.cmake new file mode 100644 index 000000000..74b90ccdc --- /dev/null +++ b/cmake/GenerateShaderResources.cmake @@ -0,0 +1,20 @@ +macro(GENERATE_SHADER_RESOURCES _output_file) + string(REPLACE ";" "\\;" SHADER_FILES_ARG "${ARGN}") + add_custom_command(OUTPUT + "${_output_file}" + + COMMAND ${CMAKE_COMMAND} + + ARGS + -D "SHADER_FILES=${SHADER_FILES_ARG}" + -D "OUTPUT_FILE=${_output_file}" + -P "${PROJECTM_SOURCE_DIR}/cmake/GenerateShaderResourcesScript.cmake" + + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS # Watch scripts and shader files for changes + ${PROJECTM_SOURCE_DIR}/cmake/ShaderResources.hpp.in + ${PROJECTM_SOURCE_DIR}/cmake/GenerateShaderResources.cmake + ${PROJECTM_SOURCE_DIR}/cmake/GenerateShaderResourcesScript.cmake + ${ARGN} + ) +endmacro() diff --git a/src/libprojectM/Renderer/GenerateBuiltInTransitionsResources.cmake b/cmake/GenerateShaderResourcesScript.cmake similarity index 62% rename from src/libprojectM/Renderer/GenerateBuiltInTransitionsResources.cmake rename to cmake/GenerateShaderResourcesScript.cmake index 208a884d2..3bba3ea60 100644 --- a/src/libprojectM/Renderer/GenerateBuiltInTransitionsResources.cmake +++ b/cmake/GenerateShaderResourcesScript.cmake @@ -1,9 +1,14 @@ -cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +cmake_minimum_required(VERSION 3.21 FATAL_ERROR) # Run as custom command in script mode if any shader file has changed. # Recreates the BuiltInTransitionsResources.hpp file accordingly. set(STATIC_SHADER_CONTENTS "") + +# Windows fix: the backslash is needed to escape the list in the script argument on UNIX shells, +# but Windows keeps it and passes it on to CMake, breaking its use as a list separator. +string(REPLACE "\\;" ";" SHADER_FILES "${SHADER_FILES}") + foreach(shader_file IN LISTS SHADER_FILES) cmake_path(GET shader_file FILENAME _shader_name) cmake_path(GET shader_file EXTENSION _shader_type) @@ -16,4 +21,4 @@ foreach(shader_file IN LISTS SHADER_FILES) endforeach() -configure_file(BuiltInTransitionsResources.hpp.in ${OUTPUT_DIR}/BuiltInTransitionsResources.hpp @ONLY) +configure_file(${CMAKE_CURRENT_LIST_DIR}/ShaderResources.hpp.in "${OUTPUT_FILE}" @ONLY) diff --git a/src/libprojectM/Renderer/BuiltInTransitionsResources.hpp.in b/cmake/ShaderResources.hpp.in similarity index 100% rename from src/libprojectM/Renderer/BuiltInTransitionsResources.hpp.in rename to cmake/ShaderResources.hpp.in diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 656f1529c..1674d9aa6 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -5,10 +5,14 @@ target_sources(projectM_api "${PROJECTM_EXPORT_HEADER}" include/projectM-4/audio.h include/projectM-4/callbacks.h + include/projectM-4/core.h include/projectM-4/debug.h include/projectM-4/memory.h include/projectM-4/projectM.h + include/projectM-4/render_opengl.h + include/projectM-4/touch.h include/projectM-4/types.h + include/projectM-4/user_sprites.h ) set_target_properties(projectM_api PROPERTIES diff --git a/src/api/include/projectM-4/projectM.h b/src/api/include/projectM-4/projectM.h index ba4f70a79..2e93d7b5c 100644 --- a/src/api/include/projectM-4/projectM.h +++ b/src/api/include/projectM-4/projectM.h @@ -34,3 +34,4 @@ #include "projectM-4/render_opengl.h" #include "projectM-4/touch.h" #include "projectM-4/version.h" +#include "projectM-4/user_sprites.h" diff --git a/src/api/include/projectM-4/user_sprites.h b/src/api/include/projectM-4/user_sprites.h new file mode 100644 index 000000000..e777f5916 --- /dev/null +++ b/src/api/include/projectM-4/user_sprites.h @@ -0,0 +1,128 @@ +/** + * @file user_sprites.h + * @copyright 2003-2024 projectM Team + * @brief Types and enumerations used in the other API headers. + * @since 4.2.0 + * + * projectM -- Milkdrop-esque visualisation SDK + * Copyright (C)2003-2024 projectM Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * See 'LICENSE.txt' included within this release + * + */ + +#pragma once + +#include "projectM-4/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Loads and displays a new sprite. + * + * Currently, these sprite types are supported: + * + * + * Unsupported types and loading errors will result in failure, and no sprite will be added or replaced. + * + * @important The same OpenGL context used to create the projectM instance @a must be active when calling this method! + * @param instance The projectM instance handle. + * @param type The case-insensitive type name of the sprite to be displayed. See description for supported values. + * @param code The type-specific sprite code, e.g. the contents of an imgNN section from a Milkdrop user + * sprite INI file. + * @return A non-zero identifier if the sprite was successfully created, or zero if the type was + * unrecognized or the code couldn't be parsed. + * @since 4.2.0 + */ +PROJECTM_EXPORT uint32_t projectm_sprite_create(projectm_handle instance, + const char* type, + const char* code); + +/** + * @brief Destroys a single sprite. + * + * If there is no active sprite with the given ID, this method is a no-op. + * + * @param instance The projectM instance handle. + * @param sprite_id The ID of the sprite as returned by projectm_sprite_create(). + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_sprite_destroy(projectm_handle instance, uint32_t sprite_id); + +/** + * @brief Destroys all active sprites. + * @param instance The projectM instance handle. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_sprite_destroy_all(projectm_handle instance); + +/** + * @brief Returns the number of currently active sprites. + * + * @note Sprites may destroy themselves after a frame has been rendered, and projectM can also + * remove a sprite if a new one is added and the sprite limit was already reached. + * Keep this in mind when calling projectm_sprite_get_sprite_ids() - ideally, + * call projectm_sprite_get_sprite_count() @a immediately before allocating the + * ID list. + * @param instance The projectM instance handle. + * @return The current number of sprites being rendered. + * @since 4.2.0 + */ +PROJECTM_EXPORT uint32_t projectm_sprite_get_sprite_count(projectm_handle instance); + +/** + * @brief Returns the number of currently active sprites. + * + * Identifiers are ordered by sprite age, with the oldest sprite first and the newest last. + * + * @param instance The projectM instance handle. + * @param sprite_ids A pointer to an already-allocated list which will receive the sprite IDs. + * Call projectm_sprite_get_sprite_count() to get the required length + * or allocate a list with the current sprite limit as its size and init all entries + * to zero. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_sprite_get_sprite_ids(projectm_handle instance, uint32_t* sprite_ids); + +/** + * @brief Sets the limit of concurrently displayed sprites. + * + * Once the limit is exceeded, the oldest sprite will be destroyed in order to display a new one. + * + * @param instance The projectM instance handle. + * @param max_sprites Maximum number of sprites to be displayed at once. Defaults to 16. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_sprite_set_max_sprites(projectm_handle instance, + uint32_t max_sprites); + +/** + * @brief Returns the currently set limit of concurrently displayed sprites. + * + * @param instance The projectM instance handle. + * @return The current maximum number of sprites to be displayed at once. + * @since 4.2.0 + */ +PROJECTM_EXPORT uint32_t projectm_sprite_get_max_sprites(projectm_handle instance); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/libprojectM/CMakeLists.txt b/src/libprojectM/CMakeLists.txt index 134850496..f05808f4a 100644 --- a/src/libprojectM/CMakeLists.txt +++ b/src/libprojectM/CMakeLists.txt @@ -9,6 +9,7 @@ add_compile_definitions( add_subdirectory(Audio) add_subdirectory(MilkdropPreset) add_subdirectory(Renderer) +add_subdirectory(UserSprites) add_library(projectM_main OBJECT "${PROJECTM_EXPORT_HEADER}" @@ -17,6 +18,8 @@ add_library(projectM_main OBJECT PresetFactory.hpp PresetFactoryManager.cpp PresetFactoryManager.hpp + PresetFileParser.cpp + PresetFileParser.hpp ProjectM.cpp ProjectM.hpp ProjectMCWrapper.cpp @@ -63,6 +66,7 @@ add_library(projectM $ $ $ + $ $ $ $ diff --git a/src/libprojectM/MilkdropPreset/BlurTexture.cpp b/src/libprojectM/MilkdropPreset/BlurTexture.cpp index daa1033d2..6cc33660a 100644 --- a/src/libprojectM/MilkdropPreset/BlurTexture.cpp +++ b/src/libprojectM/MilkdropPreset/BlurTexture.cpp @@ -1,9 +1,12 @@ #include "BlurTexture.hpp" #include "PerFrameContext.hpp" +#include "PresetState.hpp" #include "MilkdropStaticShaders.hpp" +#include "Renderer/ShaderCache.hpp" + #include namespace libprojectM { @@ -12,14 +15,6 @@ namespace MilkdropPreset { BlurTexture::BlurTexture() : m_blurSampler(std::make_shared(GL_CLAMP_TO_EDGE, GL_LINEAR)) { - auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); - - // Compile shader sources - m_blur1Shader.CompileProgram(staticShaders->GetBlurVertexShader(), - staticShaders->GetBlur1FragmentShader()); - m_blur2Shader.CompileProgram(staticShaders->GetBlurVertexShader(), - staticShaders->GetBlur2FragmentShader()); - m_blurFramebuffer.CreateColorAttachment(0, 0); // Initialize Blur VAO/VBO with a single fullscreen quad. @@ -65,6 +60,33 @@ BlurTexture::~BlurTexture() glDeleteVertexArrays(1, &m_vaoBlur); } +void BlurTexture::Initialize(const Renderer::RenderContext& renderContext) +{ + auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); + + // Load/compile shader sources + auto blur1Shader = renderContext.shaderCache->Get("milkdrop_blur1"); + if (!blur1Shader) + { + blur1Shader = std::make_shared(); + blur1Shader->CompileProgram(staticShaders->GetBlurVertexShader(), + staticShaders->GetBlur1FragmentShader()); + renderContext.shaderCache->Insert("milkdrop_blur1", blur1Shader); + } + + auto blur2Shader = renderContext.shaderCache->Get("milkdrop_blur2"); + if (!blur2Shader) + { + blur2Shader = std::make_shared(); + blur2Shader->CompileProgram(staticShaders->GetBlurVertexShader(), + staticShaders->GetBlur2FragmentShader()); + renderContext.shaderCache->Insert("milkdrop_blur2", blur2Shader); + } + + m_blur1Shader = blur1Shader; + m_blur2Shader = blur2Shader; +} + void BlurTexture::SetRequiredBlurLevel(BlurTexture::BlurLevel level) { m_blurLevel = std::max(level, m_blurLevel); @@ -152,15 +174,20 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC } // set pixel shader - Renderer::Shader* blurShader; + std::shared_ptr blurShader; if ((pass % 2) == 0) { - blurShader = &m_blur1Shader; + blurShader = m_blur1Shader.lock(); } else { - blurShader = &m_blur2Shader; + blurShader = m_blur2Shader.lock(); + } + if (!blurShader) + { + return; } + blurShader->Bind(); blurShader->SetUniformInt("texture_sampler", 0); @@ -205,10 +232,10 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC //float4 _c2; // d1..d4 //float4 _c3; // scale, bias, w_div, 0 //------------------------------------- - m_blur1Shader.SetUniformFloat4("_c0", {srcWidth, srcHeight, 1.0f / srcWidth, 1.0f / srcHeight}); - m_blur1Shader.SetUniformFloat4("_c1", {w1, w2, w3, w4}); - m_blur1Shader.SetUniformFloat4("_c2", {d1, d2, d3, d4}); - m_blur1Shader.SetUniformFloat4("_c3", {scaleNow, biasNow, w_div, 0.0}); + blurShader->SetUniformFloat4("_c0", {srcWidth, srcHeight, 1.0f / srcWidth, 1.0f / srcHeight}); + blurShader->SetUniformFloat4("_c1", {w1, w2, w3, w4}); + blurShader->SetUniformFloat4("_c2", {d1, d2, d3, d4}); + blurShader->SetUniformFloat4("_c3", {scaleNow, biasNow, w_div, 0.0}); } else { @@ -224,19 +251,19 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC //float4 _c5; // w1,w2,d1,d2 //float4 _c6; // w_div, edge_darken_c1, edge_darken_c2, edge_darken_c3 //------------------------------------- - m_blur2Shader.SetUniformFloat4("_c0", {srcWidth, srcHeight, 1.0f / srcWidth, 1.0f / srcHeight}); - m_blur2Shader.SetUniformFloat4("_c5", {w1, w2, d1, d2}); + blurShader->SetUniformFloat4("_c0", {srcWidth, srcHeight, 1.0f / srcWidth, 1.0f / srcHeight}); + blurShader->SetUniformFloat4("_c5", {w1, w2, d1, d2}); // note: only do this first time; if you do it many times, // then the super-blurred levels will have big black lines along the top & left sides. if (pass == 1) { // Darken edges - m_blur2Shader.SetUniformFloat4("_c6", {w_div, (1 - blur1EdgeDarken), blur1EdgeDarken, 5.0f}); + blurShader->SetUniformFloat4("_c6", {w_div, (1 - blur1EdgeDarken), blur1EdgeDarken, 5.0f}); } else { // Don't darken - m_blur2Shader.SetUniformFloat4("_c6", {w_div, 1.0f, 0.0f, 5.0f}); + blurShader->SetUniformFloat4("_c6", {w_div, 1.0f, 0.0f, 5.0f}); } } diff --git a/src/libprojectM/MilkdropPreset/BlurTexture.hpp b/src/libprojectM/MilkdropPreset/BlurTexture.hpp index 2f6a7998e..edf63660e 100644 --- a/src/libprojectM/MilkdropPreset/BlurTexture.hpp +++ b/src/libprojectM/MilkdropPreset/BlurTexture.hpp @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -49,6 +50,12 @@ class BlurTexture */ ~BlurTexture(); + /** + * @brief Initializes the blur texture. + * @param renderContext + */ + void Initialize(const Renderer::RenderContext& renderContext); + /** * @brief Sets the minimum required blur level. * If the current level isn't high enough, it'll be increased. @@ -101,8 +108,8 @@ class BlurTexture GLuint m_vboBlur; //!< Vertex buffer object for the fullscreen blur quad. GLuint m_vaoBlur; //!< Vertex array object for the fullscreen blur quad. - Renderer::Shader m_blur1Shader; //!< The shader used on the first blur pass. - Renderer::Shader m_blur2Shader; //!< The shader used for subsequent blur passes after the initial pass. + std::weak_ptr m_blur1Shader; //!< The shader used on the first blur pass. + std::weak_ptr m_blur2Shader; //!< The shader used for subsequent blur passes after the initial pass. int m_sourceTextureWidth{}; //!< Width of the source texture used to create the blur textures. int m_sourceTextureHeight{}; //!< Height of the source texture used to create the blur textures. diff --git a/src/libprojectM/MilkdropPreset/Border.cpp b/src/libprojectM/MilkdropPreset/Border.cpp index 57b34c7cf..2cebe2a54 100644 --- a/src/libprojectM/MilkdropPreset/Border.cpp +++ b/src/libprojectM/MilkdropPreset/Border.cpp @@ -33,8 +33,9 @@ void Border::Draw(const PerFrameContext& presetPerFrameContext) glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); std::array vertices{}; for (int border = 0; border < 2; border++) diff --git a/src/libprojectM/MilkdropPreset/CMakeLists.txt b/src/libprojectM/MilkdropPreset/CMakeLists.txt index 8dfb147ad..6f54abf4f 100644 --- a/src/libprojectM/MilkdropPreset/CMakeLists.txt +++ b/src/libprojectM/MilkdropPreset/CMakeLists.txt @@ -75,8 +75,6 @@ add_library(MilkdropPreset OBJECT PerPixelContext.hpp PerPixelMesh.cpp PerPixelMesh.hpp - PresetFileParser.cpp - PresetFileParser.hpp PresetState.cpp PresetState.hpp ShapePerFrameContext.cpp diff --git a/src/libprojectM/MilkdropPreset/CustomShape.cpp b/src/libprojectM/MilkdropPreset/CustomShape.cpp index d88a1e946..9eaef3784 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.cpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.cpp @@ -72,7 +72,7 @@ void CustomShape::InitVertexAttrib() glBufferData(GL_ARRAY_BUFFER, sizeof(Point) * vertexData.size(), vertexData.data(), GL_STREAM_DRAW); } -void CustomShape::Initialize(PresetFileParser& parsedFile, int index) +void CustomShape::Initialize(::libprojectM::PresetFileParser& parsedFile, int index) { std::string const shapecodePrefix = "shapecode_" + std::to_string(index) + "_"; @@ -192,9 +192,10 @@ void CustomShape::Draw() if (static_cast(*m_perFrameContext.textured) != 0) { - m_presetState.texturedShader.Bind(); - m_presetState.texturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - m_presetState.texturedShader.SetUniformInt("texture_sampler", 0); + auto shader = m_presetState.texturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformInt("texture_sampler", 0); // Textured shape, either main texture or texture from "image" key auto textureAspectY = m_presetState.renderContext.aspectY; @@ -208,7 +209,7 @@ void CustomShape::Draw() auto desc = m_presetState.renderContext.textureManager->GetTexture(m_image); if (!desc.Empty()) { - desc.Bind(0, m_presetState.texturedShader); + desc.Bind(0, *shader); textureAspectY = 1.0f; } else @@ -251,8 +252,9 @@ void CustomShape::Draw() glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(TexturedPoint) * (sides + 2), vertexData.data()); - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); glBindVertexArray(m_vaoIdUntextured); glDrawArrays(GL_TRIANGLE_FAN, 0, sides + 2); @@ -269,8 +271,9 @@ void CustomShape::Draw() points[i].y = vertexData[i + 1].y; } - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); glVertexAttrib4f(1, static_cast(*m_perFrameContext.border_r), diff --git a/src/libprojectM/MilkdropPreset/CustomShape.hpp b/src/libprojectM/MilkdropPreset/CustomShape.hpp index 5ecb23eb0..ea59d34e4 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.hpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.hpp @@ -33,7 +33,7 @@ class CustomShape : public Renderer::RenderItem * @param parsedFile The file parser with the preset data. * @param index The waveform index. */ - void Initialize(PresetFileParser& parsedFile, int index); + void Initialize(::libprojectM::PresetFileParser& parsedFile, int index); /** * @brief Compiles all code blocks and runs the init expression. diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp index d9d0937f6..9f046d533 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp @@ -36,7 +36,7 @@ void CustomWaveform::InitVertexAttrib() glBufferData(GL_ARRAY_BUFFER, sizeof(ColoredPoint) * vertexData.size(), vertexData.data(), GL_STREAM_DRAW); } -void CustomWaveform::Initialize(PresetFileParser& parsedFile, int index) +void CustomWaveform::Initialize(::libprojectM::PresetFileParser& parsedFile, int index) { std::string const wavecodePrefix = "wavecode_" + std::to_string(index) + "_"; std::string const wavePrefix = "wave_" + std::to_string(index) + "_"; @@ -182,8 +182,9 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); auto iterations = (m_drawThick && !m_useDots) ? 4 : 1; diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.hpp b/src/libprojectM/MilkdropPreset/CustomWaveform.hpp index 363c27345..31d2bfae0 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.hpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.hpp @@ -8,10 +8,11 @@ #include namespace libprojectM { -namespace MilkdropPreset { class PresetFileParser; +namespace MilkdropPreset { + class CustomWaveform : public Renderer::RenderItem { public: @@ -32,7 +33,7 @@ class CustomWaveform : public Renderer::RenderItem * @param parsedFile The file parser with the preset data. * @param index The waveform index. */ - void Initialize(PresetFileParser& parsedFile, int index); + void Initialize(::libprojectM::PresetFileParser& parsedFile, int index); /** * @brief Compiles all code blocks and runs the init expression. diff --git a/src/libprojectM/MilkdropPreset/DarkenCenter.cpp b/src/libprojectM/MilkdropPreset/DarkenCenter.cpp index 5e5efac74..64899b324 100644 --- a/src/libprojectM/MilkdropPreset/DarkenCenter.cpp +++ b/src/libprojectM/MilkdropPreset/DarkenCenter.cpp @@ -44,8 +44,9 @@ void DarkenCenter::Draw() glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); glDrawArrays(GL_TRIANGLE_FAN, 0, 6); diff --git a/src/libprojectM/MilkdropPreset/Filters.cpp b/src/libprojectM/MilkdropPreset/Filters.cpp index 9c70904a1..472a0e70b 100644 --- a/src/libprojectM/MilkdropPreset/Filters.cpp +++ b/src/libprojectM/MilkdropPreset/Filters.cpp @@ -29,8 +29,9 @@ void Filters::Draw() glEnable(GL_BLEND); - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); glBindVertexArray(m_vaoID); glVertexAttrib4f(1, 1.0, 1.0, 1.0, 1.0); diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp index d2c3e5473..110489a80 100755 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp @@ -59,6 +59,8 @@ void MilkdropPreset::Initialize(const Renderer::RenderContext& renderContext) { assert(renderContext.textureManager); m_state.renderContext = renderContext; + m_state.blurTexture.Initialize(renderContext); + m_state.LoadShaders(); // Initialize variables and code now we have a proper render state. CompileCodeAndRunInitExpressions(); @@ -143,8 +145,6 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio } m_border.Draw(m_perFrameContext); - // Todo: Song title anim would go here - // y-flip the image for final compositing again m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_currentFrameBuffer, 0), nullptr, true, false); m_state.mainTexture = m_flipTexture.Texture(); @@ -155,8 +155,6 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio m_finalComposite.Draw(m_state, m_perFrameContext); - // ToDo: Draw user sprites (can have evaluated code) - if (!m_finalComposite.HasCompositeShader()) { // Flip texture again in "previous" framebuffer as old-school effects are still upside down. @@ -183,6 +181,14 @@ void MilkdropPreset::DrawInitialImage(const std::shared_ptr& m_flipTexture.Draw(image, m_framebuffer, m_previousFrameBuffer); } +void MilkdropPreset::BindFramebuffer() +{ + if (m_framebuffer.Width() > 0 && m_framebuffer.Height() > 0) + { + m_framebuffer.BindDraw(m_previousFrameBuffer); + } +} + void MilkdropPreset::PerFrameUpdate() { m_perFrameContext.LoadStateVariables(m_state); @@ -205,7 +211,7 @@ void MilkdropPreset::Load(const std::string& pathname) SetFilename(ParseFilename(pathname)); - PresetFileParser parser; + ::libprojectM::PresetFileParser parser; if (!parser.Read(pathname)) { @@ -224,7 +230,7 @@ void MilkdropPreset::Load(std::istream& stream) std::cerr << "[Preset] Loading preset from stream." << std::endl; #endif - PresetFileParser parser; + ::libprojectM::PresetFileParser parser; if (!parser.Read(stream)) { @@ -237,7 +243,7 @@ void MilkdropPreset::Load(std::istream& stream) InitializePreset(parser); } -void MilkdropPreset::InitializePreset(PresetFileParser& parsedFile) +void MilkdropPreset::InitializePreset(::libprojectM::PresetFileParser& parsedFile) { // Create the offscreen rendering surfaces. m_motionVectorUVMap = std::make_shared(GL_RG16F, GL_RG, GL_FLOAT, 0, 0); @@ -314,5 +320,6 @@ auto MilkdropPreset::ParseFilename(const std::string& filename) -> std::string return filename.substr(start + 1, filename.length()); } + } // namespace MilkdropPreset } // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp index cd55339a9..06fa470bb 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp @@ -45,10 +45,11 @@ #include namespace libprojectM { +class PresetFileParser; + namespace MilkdropPreset { class Factory; -class PresetFileParser; class MilkdropPreset : public ::libprojectM::Preset { @@ -86,6 +87,8 @@ class MilkdropPreset : public ::libprojectM::Preset void DrawInitialImage(const std::shared_ptr& image, const Renderer::RenderContext& renderContext) override; + void BindFramebuffer() override; + private: void PerFrameUpdate(); @@ -93,7 +96,7 @@ class MilkdropPreset : public ::libprojectM::Preset void Load(std::istream& stream); - void InitializePreset(PresetFileParser& parsedFile); + void InitializePreset(::libprojectM::PresetFileParser& parsedFile); void CompileCodeAndRunInitExpressions(); diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.cpp b/src/libprojectM/MilkdropPreset/MotionVectors.cpp index cf165cbd9..3ec34e17e 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.cpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.cpp @@ -2,6 +2,7 @@ #include "MilkdropStaticShaders.hpp" +#include #include namespace libprojectM { @@ -11,9 +12,6 @@ MotionVectors::MotionVectors(PresetState& presetState) : RenderItem() , m_presetState(presetState) { - auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); - m_motionVectorShader.CompileProgram(staticShaders->GetPresetMotionVectorsVertexShader(), - staticShaders->GetUntexturedDrawFragmentShader()); RenderItem::Init(); } @@ -89,12 +87,13 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shar glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - m_motionVectorShader.Bind(); - m_motionVectorShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - m_motionVectorShader.SetUniformFloat("length_multiplier", static_cast(*presetPerFrameContext.mv_l)); - m_motionVectorShader.SetUniformFloat("minimum_length", minimumLength); + auto shader = GetShader(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformFloat("length_multiplier", static_cast(*presetPerFrameContext.mv_l)); + shader->SetUniformFloat("minimum_length", minimumLength); - m_motionVectorShader.SetUniformInt("warp_coordinates", 0); + shader->SetUniformInt("warp_coordinates", 0); motionTexture->Bind(0, m_sampler); @@ -162,5 +161,29 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shar glDisable(GL_BLEND); } +std::shared_ptr MotionVectors::GetShader() +{ + auto shader = m_motionVectorShader.lock(); + if (!shader) + { + shader = m_presetState.renderContext.shaderCache->Get("milkdrop_motion_vectors"); + } + if (!shader) + { + // First use, compile and cache. + auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); + + shader = std::make_shared(); + shader->CompileProgram(staticShaders->GetPresetMotionVectorsVertexShader(), + staticShaders->GetUntexturedDrawFragmentShader()); + + m_presetState.renderContext.shaderCache->Insert("milkdrop_motion_vectors", shader); + } + + m_motionVectorShader = shader; + + return shader; +} + } // namespace MilkdropPreset } // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.hpp b/src/libprojectM/MilkdropPreset/MotionVectors.hpp index 6ceb940df..4782e03ad 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.hpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.hpp @@ -36,16 +36,17 @@ class MotionVectors : public Renderer::RenderItem void Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr motionTexture); private: - struct MotionVectorVertex - { + struct MotionVectorVertex { float x{}; float y{}; int32_t index{}; }; + std::shared_ptr GetShader(); + PresetState& m_presetState; //!< The global preset state. - Renderer::Shader m_motionVectorShader; //!< The motion vector shader, calculates the trace positions in the GPU. + std::weak_ptr m_motionVectorShader; //!< The motion vector shader, calculates the trace positions in the GPU. std::shared_ptr m_sampler{std::make_shared(GL_CLAMP_TO_EDGE, GL_LINEAR)}; //!< The texture sampler. int m_lastVertexCount{}; //!< Number of vertices drawn in the previous draw call. diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp index 8f0f49a2f..5ad57c28d 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp @@ -6,6 +6,8 @@ #include "PerPixelContext.hpp" #include "PresetState.hpp" +#include "Renderer/ShaderCache.hpp" + #include #include @@ -22,10 +24,6 @@ PerPixelMesh::PerPixelMesh() : RenderItem() { RenderItem::Init(); - - auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); - m_perPixelMeshShader.CompileProgram(staticShaders->GetPresetWarpVertexShader(), - staticShaders->GetPresetWarpFragmentShader()); } void PerPixelMesh::InitVertexAttrib() @@ -80,7 +78,7 @@ void PerPixelMesh::LoadWarpShader(const PresetState& presetState) #ifdef MILKDROP_PRESET_DEBUG std::cerr << "[Warp Shader] Error loading warp shader code:" << ex.message() << std::endl; #else - (void)ex; // silence unused parameter warning + (void) ex; // silence unused parameter warning #endif m_warpShader.reset(); } @@ -104,7 +102,7 @@ void PerPixelMesh::CompileWarpShader(PresetState& presetState) #ifdef MILKDROP_PRESET_DEBUG std::cerr << "[Warp Shader] Error compiling warp shader code:" << ex.message() << std::endl; #else - (void)ex; // silence unused parameter warning + (void) ex; // silence unused parameter warning #endif m_warpShader.reset(); } @@ -321,18 +319,19 @@ void PerPixelMesh::WarpedBlit(const PresetState& presetState, if (!m_warpShader) { - m_perPixelMeshShader.Bind(); - m_perPixelMeshShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - m_perPixelMeshShader.SetUniformInt("texture_sampler", 0); - m_perPixelMeshShader.SetUniformFloat4("aspect", {presetState.renderContext.aspectX, - presetState.renderContext.aspectY, - presetState.renderContext.invAspectX, - presetState.renderContext.invAspectY}); - m_perPixelMeshShader.SetUniformFloat("warpTime", warpTime); - m_perPixelMeshShader.SetUniformFloat("warpScaleInverse", warpScaleInverse); - m_perPixelMeshShader.SetUniformFloat4("warpFactors", warpFactors); - m_perPixelMeshShader.SetUniformFloat2("texelOffset", texelOffsets); - m_perPixelMeshShader.SetUniformFloat("decay", decay); + auto perPixelMeshShader = GetDefaultWarpShader(presetState); + perPixelMeshShader->Bind(); + perPixelMeshShader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + perPixelMeshShader->SetUniformInt("texture_sampler", 0); + perPixelMeshShader->SetUniformFloat4("aspect", {presetState.renderContext.aspectX, + presetState.renderContext.aspectY, + presetState.renderContext.invAspectX, + presetState.renderContext.invAspectY}); + perPixelMeshShader->SetUniformFloat("warpTime", warpTime); + perPixelMeshShader->SetUniformFloat("warpScaleInverse", warpScaleInverse); + perPixelMeshShader->SetUniformFloat4("warpFactors", warpFactors); + perPixelMeshShader->SetUniformFloat2("texelOffset", texelOffsets); + perPixelMeshShader->SetUniformFloat("decay", decay); } else { @@ -399,5 +398,31 @@ void PerPixelMesh::WarpedBlit(const PresetState& presetState, Renderer::Shader::Unbind(); } +auto PerPixelMesh::GetDefaultWarpShader(const PresetState& presetState) -> std::shared_ptr +{ + auto perPixelMeshShader = m_perPixelMeshShader.lock(); + if (perPixelMeshShader) + { + return perPixelMeshShader; + } + + perPixelMeshShader = presetState.renderContext.shaderCache->Get("milkdrop_default_warp_shader"); + if (perPixelMeshShader) + { + return perPixelMeshShader; + } + + auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); + + perPixelMeshShader = std::make_shared(); + perPixelMeshShader->CompileProgram(staticShaders->GetPresetWarpVertexShader(), + staticShaders->GetPresetWarpFragmentShader()); + + presetState.renderContext.shaderCache->Insert("milkdrop_default_warp_shader", perPixelMeshShader); + m_perPixelMeshShader = perPixelMeshShader; + + return perPixelMeshShader; +} + } // namespace MilkdropPreset } // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp index 6dcef4299..98576d30a 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp @@ -111,6 +111,13 @@ class PerPixelMesh : public Renderer::RenderItem */ void WarpedBlit(const PresetState& presetState, const PerFrameContext& perFrameContext); + /** + * @brief Creates or retrieves the default warp shader. + * @param presetState The preset state to retrieve the rendering context from. + * @return A shared pointer to the default warp shader instance. + */ + auto GetDefaultWarpShader(const PresetState& presetState) -> std::shared_ptr; + int m_gridSizeX{}; //!< Warp mesh X resolution. int m_gridSizeY{}; //!< Warp mesh Y resolution. @@ -122,8 +129,8 @@ class PerPixelMesh : public Renderer::RenderItem std::vector m_listIndices; //!< List of vertex indices to render. VertexList m_drawVertices; //!< Temp data buffer for the vertices to be drawn. - Renderer::Shader m_perPixelMeshShader; //!< Special shader which calculates the per-pixel UV coordinates. - std::unique_ptr m_warpShader; //!< The warp shader. Either preset-defined or a default shader. + std::weak_ptr m_perPixelMeshShader; //!< Special shader which calculates the per-pixel UV coordinates. + std::unique_ptr m_warpShader; //!< The warp shader. Either preset-defined or a default shader. Renderer::Sampler m_perPixelSampler{GL_CLAMP_TO_EDGE, GL_LINEAR}; //!< The main texture sampler. }; diff --git a/src/libprojectM/MilkdropPreset/PresetState.cpp b/src/libprojectM/MilkdropPreset/PresetState.cpp index 7ff872a55..1838269cd 100644 --- a/src/libprojectM/MilkdropPreset/PresetState.cpp +++ b/src/libprojectM/MilkdropPreset/PresetState.cpp @@ -3,6 +3,8 @@ #include "MilkdropStaticShaders.hpp" #include "PresetFileParser.hpp" +#include "Renderer/ShaderCache.hpp" + #include #include @@ -16,12 +18,6 @@ const glm::mat4 PresetState::orthogonalProjectionFlipped = glm::ortho(-1.0f, 1.0 PresetState::PresetState() : globalMemory(projectm_eval_memory_buffer_create()) { - auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); - untexturedShader.CompileProgram(staticShaders->GetUntexturedDrawVertexShader(), - staticShaders->GetUntexturedDrawFragmentShader()); - texturedShader.CompileProgram(staticShaders->GetTexturedDrawVertexShader(), - staticShaders->GetTexturedDrawFragmentShader()); - std::random_device randomDevice; std::mt19937 randomGenerator(randomDevice()); std::uniform_int_distribution<> distrib(0, std::numeric_limits::max()); @@ -37,7 +33,7 @@ PresetState::~PresetState() projectm_eval_memory_buffer_destroy(globalMemory); } -void PresetState::Initialize(PresetFileParser& parsedFile) +void PresetState::Initialize(::libprojectM::PresetFileParser& parsedFile) { // General: @@ -164,5 +160,30 @@ void PresetState::Initialize(PresetFileParser& parsedFile) compositeShader = parsedFile.GetCode("comp_"); } +void PresetState::LoadShaders() +{ + auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); + + auto untexturedShaderShared = renderContext.shaderCache->Get("milkdrop_generic_untextured"); + if (!untexturedShaderShared) + { + untexturedShaderShared = std::make_shared(); + untexturedShaderShared->CompileProgram(staticShaders->GetUntexturedDrawVertexShader(), + staticShaders->GetUntexturedDrawFragmentShader()); + renderContext.shaderCache->Insert("milkdrop_generic_untextured", untexturedShaderShared); + } + untexturedShader = untexturedShaderShared; + + auto texturedShaderShared = renderContext.shaderCache->Get("milkdrop_generic_textured"); + if (!texturedShaderShared) + { + texturedShaderShared = std::make_shared(); + texturedShaderShared->CompileProgram(staticShaders->GetTexturedDrawVertexShader(), + staticShaders->GetTexturedDrawFragmentShader()); + renderContext.shaderCache->Insert("milkdrop_generic_textured", texturedShaderShared); + } + texturedShader = texturedShaderShared; +} + } // namespace MilkdropPreset } // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/PresetState.hpp b/src/libprojectM/MilkdropPreset/PresetState.hpp index 921b46d79..68fef489d 100644 --- a/src/libprojectM/MilkdropPreset/PresetState.hpp +++ b/src/libprojectM/MilkdropPreset/PresetState.hpp @@ -20,10 +20,12 @@ #include namespace libprojectM { -namespace MilkdropPreset { class PresetFileParser; +namespace MilkdropPreset { + + using BlendableFloat = float; //!< Currently a placeholder to mark blendable values. /** @@ -43,7 +45,13 @@ class PresetState * @brief Loads the initial values and code from the preset file. * @param parsedFile The file parser with the preset data. */ - void Initialize(PresetFileParser& parsedFile); + void Initialize(::libprojectM::PresetFileParser& parsedFile); + + /** + * @brief Loads or compiles the generic shaders. + * Call after setting renderContext. + */ + void LoadShaders(); BlendableFloat gammaAdj{2.0f}; BlendableFloat videoEchoZoom{2.0f}; @@ -147,8 +155,8 @@ class PresetState std::string warpShader; //!< Warp shader code. std::string compositeShader; //!< Composite shader code. - Renderer::Shader untexturedShader; //!< Shader used to draw untextured primitives, e.g. waveforms. - Renderer::Shader texturedShader; //!< Shader used to draw textured primitives, e.g. textured shapes and the warp mesh. + std::weak_ptr untexturedShader; //!< Shader used to draw untextured primitives, e.g. waveforms. + std::weak_ptr texturedShader; //!< Shader used to draw textured primitives, e.g. textured shapes and the warp mesh. std::weak_ptr mainTexture; //!< A weak reference to the main texture in the preset framebuffer. BlurTexture blurTexture; //!< The blur textures used in this preset. Contents depend on the shader code using GetBlurX(). diff --git a/src/libprojectM/MilkdropPreset/VideoEcho.cpp b/src/libprojectM/MilkdropPreset/VideoEcho.cpp index bf7d4af97..9bf9ae4f2 100644 --- a/src/libprojectM/MilkdropPreset/VideoEcho.cpp +++ b/src/libprojectM/MilkdropPreset/VideoEcho.cpp @@ -68,9 +68,10 @@ void VideoEcho::Draw() m_vertices[i].a = 1.0f; } - m_presetState.texturedShader.Bind(); - m_presetState.texturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - m_presetState.texturedShader.SetUniformInt("texture_sampler", 0); + auto shader = m_presetState.texturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + shader->SetUniformInt("texture_sampler", 0); auto mainTexture = m_presetState.mainTexture.lock(); if (mainTexture) diff --git a/src/libprojectM/MilkdropPreset/Waveform.cpp b/src/libprojectM/MilkdropPreset/Waveform.cpp index 80e09e406..ce4351784 100644 --- a/src/libprojectM/MilkdropPreset/Waveform.cpp +++ b/src/libprojectM/MilkdropPreset/Waveform.cpp @@ -54,8 +54,9 @@ void Waveform::Draw(const PerFrameContext& presetPerFrameContext) #endif glLineWidth(1); - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + auto shader = m_presetState.untexturedShader.lock(); + shader->Bind(); + shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); glBindVertexArray(m_vaoID); glBindBuffer(GL_ARRAY_BUFFER, m_vboID); diff --git a/src/libprojectM/Preset.hpp b/src/libprojectM/Preset.hpp index ba06a14d9..c96b1bf7f 100644 --- a/src/libprojectM/Preset.hpp +++ b/src/libprojectM/Preset.hpp @@ -47,6 +47,19 @@ class Preset virtual void DrawInitialImage(const std::shared_ptr& image, const Renderer::RenderContext& renderContext) = 0; + /** + * @brief Bind the preset's internal framebuffer. + * This framebuffer contains the image passed on to the next frame, onto which warp effects etc. + * are then applied. Has no effect if the framebuffer isn't initialized yet, e.g. before drawing + * the first frame. + * + * Can be used to draw anything on top of the preset's image, "burning in" additional shapes, + * images or text. Depending on the preset, it's not guaranteed though that the image actually + * is used in the next frame, or completely painted over. That said, the effect varies between + * presets. + */ + virtual void BindFramebuffer() = 0; + inline void SetFilename(const std::string& filename) { m_filename = filename; diff --git a/src/libprojectM/MilkdropPreset/PresetFileParser.cpp b/src/libprojectM/PresetFileParser.cpp similarity index 78% rename from src/libprojectM/MilkdropPreset/PresetFileParser.cpp rename to src/libprojectM/PresetFileParser.cpp index 5a88d9915..a48edea77 100644 --- a/src/libprojectM/MilkdropPreset/PresetFileParser.cpp +++ b/src/libprojectM/PresetFileParser.cpp @@ -2,12 +2,10 @@ #include #include -#include #include #include namespace libprojectM { -namespace MilkdropPreset { auto PresetFileParser::Read(const std::string& presetFile) -> bool { @@ -77,14 +75,16 @@ auto PresetFileParser::Read(std::istream& presetStream) -> bool auto PresetFileParser::GetCode(const std::string& keyPrefix) const -> std::string { + auto lowerKey = ToLower(keyPrefix); + std::stringstream code; //!< The parsed code - std::string key(keyPrefix.length() + 5, '\0'); //!< Allocate a string that can hold up to 5 digits. + std::string key(lowerKey.length() + 5, '\0'); //!< Allocate a string that can hold up to 5 digits. - key.replace(0, keyPrefix.length(), keyPrefix); + key.replace(0, lowerKey.length(), lowerKey); for (int index{1}; index <= 99999; ++index) { - key.replace(keyPrefix.length(), 5, std::to_string(index)); + key.replace(lowerKey.length(), 5, std::to_string(index)); if (m_presetValues.find(key) == m_presetValues.end()) { break; @@ -107,11 +107,12 @@ auto PresetFileParser::GetCode(const std::string& keyPrefix) const -> std::strin auto PresetFileParser::GetInt(const std::string& key, int defaultValue) -> int { - if (m_presetValues.find(key) != m_presetValues.end()) + auto lowerKey = ToLower(key); + if (m_presetValues.find(lowerKey) != m_presetValues.end()) { try { - return std::stoi(m_presetValues.at(key)); + return std::stoi(m_presetValues.at(lowerKey)); } catch (std::logic_error&) { @@ -123,11 +124,12 @@ auto PresetFileParser::GetInt(const std::string& key, int defaultValue) -> int auto PresetFileParser::GetFloat(const std::string& key, float defaultValue) -> float { - if (m_presetValues.find(key) != m_presetValues.end()) + auto lowerKey = ToLower(key); + if (m_presetValues.find(lowerKey) != m_presetValues.end()) { try { - return std::stof(m_presetValues.at(key)); + return std::stof(m_presetValues.at(lowerKey)); } catch (std::logic_error&) { @@ -144,9 +146,10 @@ auto PresetFileParser::GetBool(const std::string& key, bool defaultValue) -> boo auto PresetFileParser::GetString(const std::string& key, const std::string& defaultValue) -> std::string { - if (m_presetValues.find(key) != m_presetValues.end()) + auto lowerKey = ToLower(key); + if (m_presetValues.find(lowerKey) != m_presetValues.end()) { - return m_presetValues.at(key); + return m_presetValues.at(lowerKey); } return defaultValue; @@ -168,7 +171,8 @@ void PresetFileParser::ParseLine(const std::string& line) return; } - std::string varName(line.begin(), line.begin() + varNameDelimiterPos); + // Convert key to lower case, as INI functions are not case-sensitive. + std::string varName(ToLower(std::string(line.begin(), line.begin() + varNameDelimiterPos))); std::string value(line.begin() + varNameDelimiterPos + 1, line.end()); // Only add first occurrence to mimic Milkdrop behaviour @@ -178,5 +182,13 @@ void PresetFileParser::ParseLine(const std::string& line) } } -} // namespace MilkdropPreset +auto PresetFileParser::ToLower(std::string str) -> std::string +{ + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c){ return std::tolower(c); } + ); + + return str; +} + } // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/PresetFileParser.hpp b/src/libprojectM/PresetFileParser.hpp similarity index 95% rename from src/libprojectM/MilkdropPreset/PresetFileParser.hpp rename to src/libprojectM/PresetFileParser.hpp index cd2e00659..8f2a3afc7 100644 --- a/src/libprojectM/MilkdropPreset/PresetFileParser.hpp +++ b/src/libprojectM/PresetFileParser.hpp @@ -4,7 +4,6 @@ #include namespace libprojectM { -namespace MilkdropPreset { /** * @brief Milkdrop preset file parser @@ -118,8 +117,15 @@ class PresetFileParser void ParseLine(const std::string& line); private: + /** + * @brief Converts the string to lower-case. + * Only letters A-Z are converted to a-z by default. + * @param str The original string. + * @return The lower-case string. + */ + static auto ToLower(std::string str) -> std::string; + ValueMap m_presetValues; //!< Map with preset keys and their value. }; -} // namespace MilkdropPreset } // namespace libprojectM diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index 37e5f41a5..495c5d4b7 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -30,8 +30,11 @@ #include #include #include +#include #include +#include + namespace libprojectM { ProjectM::ProjectM() @@ -177,6 +180,9 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/) m_textureCopier->Draw(m_activePreset->OutputTexture(), false, false); } + // Draw user sprites + m_spriteManager->Draw(audioData, renderContext, targetFramebufferObject, { m_activePreset, m_transitioningPreset }); + m_frameCount++; m_previousFrameVolume = audioData.vol; } @@ -194,11 +200,14 @@ void ProjectM::Initialize() /** Initialise per-pixel matrix calculations */ /** We need to initialise this before the builtin param db otherwise bass/mid etc won't bind correctly */ m_textureManager = std::make_unique(m_textureSearchPaths); + m_shaderCache = std::make_unique(); m_transitionShaderManager = std::make_unique(); m_textureCopier = std::make_unique(); + m_spriteManager = std::make_unique(); + m_presetFactoryManager->initialize(); /* Set the seed to the current time in seconds */ @@ -268,6 +277,41 @@ auto ProjectM::WindowHeight() -> int return m_windowHeight; } +auto ProjectM::AddUserSprite(const std::string& type, const std::string& spriteData) -> uint32_t +{ + return m_spriteManager->Spawn(type, spriteData, GetRenderContext()); +} + +void ProjectM::DestroyUserSprite(uint32_t spriteIdentifier) +{ + m_spriteManager->Destroy(spriteIdentifier); +} + +void ProjectM::DestroyAllUserSprites() +{ + m_spriteManager->DestroyAll(); +} + +auto ProjectM::UserSpriteCount() const -> uint32_t +{ + return m_spriteManager->ActiveSpriteCount(); +} + +void ProjectM::SetUserSpriteLimit(uint32_t maxSprites) +{ + m_spriteManager->SpriteSlots(maxSprites); +} + +auto ProjectM::UserSpriteLimit() const -> uint32_t +{ + return m_spriteManager->SpriteSlots(); +} + +auto ProjectM::UserSpriteIdentifiers() const -> std::vector +{ + return m_spriteManager->ActiveSpriteIdentifiers(); +} + void ProjectM::SetPresetLocked(bool locked) { // ToDo: Add a preset switch timer separate from the display timer and reset to 0 when @@ -452,6 +496,16 @@ auto ProjectM::GetRenderContext() -> Renderer::RenderContext ctx.perPixelMeshX = static_cast(m_meshX); ctx.perPixelMeshY = static_cast(m_meshY); ctx.textureManager = m_textureManager.get(); + ctx.shaderCache = m_shaderCache.get(); + + if (m_transition) + { + ctx.blendProgress = m_transition->Progress(ctx.time); + } + else + { + ctx.blendProgress = 0.0; + } return ctx; } diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index d57b9bf47..a718d500a 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -27,6 +27,7 @@ #include