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:
+ *
+ *
milkdrop: Original Milkdrop user sprite syntax. Pass the contents of an imgNN section in
+ * the code argument.
+ *
+ *
+ * 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