Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Milkdrop user sprites and add API methods to control them #862

Merged
merged 11 commits into from
Feb 27, 2025
20 changes: 20 additions & 0 deletions cmake/GenerateShaderResources.cmake
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
4 changes: 4 additions & 0 deletions src/api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/api/include/projectM-4/projectM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
128 changes: 128 additions & 0 deletions src/api/include/projectM-4/user_sprites.h
Original file line number Diff line number Diff line change
@@ -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:
* <ul>
* <li><tt>milkdrop</tt>: Original Milkdrop user sprite syntax. Pass the contents of an <tt>imgNN</tt> section in
* the <tt>code</tt> argument.</li>
* </ul>
*
* 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 <tt>imgNN</tt> 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 <tt>projectm_sprite_create()</tt>.
* @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 <tt>projectm_sprite_get_sprite_ids()</tt> - ideally,
* call <tt>projectm_sprite_get_sprite_count()</tt> @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 <tt>projectm_sprite_get_sprite_count()</tt> 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
4 changes: 4 additions & 0 deletions src/libprojectM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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
Expand Down Expand Up @@ -63,6 +66,7 @@ add_library(projectM
$<TARGET_OBJECTS:Audio>
$<TARGET_OBJECTS:MilkdropPreset>
$<TARGET_OBJECTS:Renderer>
$<TARGET_OBJECTS:UserSprites>
$<TARGET_OBJECTS:hlslparser>
$<TARGET_OBJECTS:SOIL2>
$<TARGET_OBJECTS:projectM_main>
Expand Down
65 changes: 46 additions & 19 deletions src/libprojectM/MilkdropPreset/BlurTexture.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include "BlurTexture.hpp"

#include "PerFrameContext.hpp"
#include "PresetState.hpp"

#include "MilkdropStaticShaders.hpp"

#include "Renderer/ShaderCache.hpp"

#include <array>

namespace libprojectM {
Expand All @@ -12,14 +15,6 @@ namespace MilkdropPreset {
BlurTexture::BlurTexture()
: m_blurSampler(std::make_shared<Renderer::Sampler>(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.
Expand Down Expand Up @@ -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<Renderer::Shader>();
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<Renderer::Shader>();
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);
Expand Down Expand Up @@ -152,15 +174,20 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC
}

// set pixel shader
Renderer::Shader* blurShader;
std::shared_ptr<Renderer::Shader> 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);

Expand Down Expand Up @@ -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
{
Expand All @@ -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});
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/libprojectM/MilkdropPreset/BlurTexture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include <Renderer/Framebuffer.hpp>
#include <Renderer/RenderContext.hpp>
#include <Renderer/Shader.hpp>
#include <Renderer/TextureSamplerDescriptor.hpp>

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<Renderer::Shader> m_blur1Shader; //!< The shader used on the first blur pass.
std::weak_ptr<Renderer::Shader> 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.
Expand Down
5 changes: 3 additions & 2 deletions src/libprojectM/MilkdropPreset/Border.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Point, 4> vertices{};
for (int border = 0; border < 2; border++)
Expand Down
2 changes: 0 additions & 2 deletions src/libprojectM/MilkdropPreset/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ add_library(MilkdropPreset OBJECT
PerPixelContext.hpp
PerPixelMesh.cpp
PerPixelMesh.hpp
PresetFileParser.cpp
PresetFileParser.hpp
PresetState.cpp
PresetState.hpp
ShapePerFrameContext.cpp
Expand Down
Loading
Loading