diff --git a/src/libprojectM/Renderer/CMakeLists.txt b/src/libprojectM/Renderer/CMakeLists.txt index 59d00d0b1..ad7f45217 100644 --- a/src/libprojectM/Renderer/CMakeLists.txt +++ b/src/libprojectM/Renderer/CMakeLists.txt @@ -6,6 +6,8 @@ add_library(Renderer OBJECT IdleTextures.hpp MilkdropNoise.cpp MilkdropNoise.hpp + PresetTransition.cpp + PresetTransition.hpp RenderContext.hpp RenderItem.cpp RenderItem.hpp diff --git a/src/libprojectM/Renderer/PresetTransition.cpp b/src/libprojectM/Renderer/PresetTransition.cpp new file mode 100644 index 000000000..b18d63145 --- /dev/null +++ b/src/libprojectM/Renderer/PresetTransition.cpp @@ -0,0 +1,125 @@ +#include "PresetTransition.hpp" + +#include "TextureManager.hpp" + +#include +#include +#include + +PresetTransition::PresetTransition(const std::shared_ptr& transitionShader, double durationSeconds) + : m_transitionShader(transitionShader) + , m_durationSeconds(durationSeconds) +{ + std::mt19937 rand32(m_randomDevice()); + m_staticRandomValues = {rand32(), rand32(), rand32(), rand32()}; +} + +void PresetTransition::InitVertexAttrib() +{ + static const std::array points{{{-1.0f, 1.0f}, + {1.0f, 1.0f}, + {-1.0f, -1.0f}, + {1.0f, -1.0f}}}; + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Point), reinterpret_cast(offsetof(Point, x))); // Position + glBufferData(GL_ARRAY_BUFFER, sizeof(points), points.data(), GL_STATIC_DRAW); +} + +auto PresetTransition::IsDone() const -> bool +{ + const auto secondsSinceStart = std::chrono::duration(std::chrono::system_clock::now() - m_transitionStartTime).count(); + return m_durationSeconds <= 0.0 || secondsSinceStart >= m_durationSeconds; +} + +void PresetTransition::Draw(const Preset& oldPreset, + const Preset& newPreset, + const RenderContext& context, + const libprojectM::Audio::FrameAudioData& audioData) +{ + using namespace std::chrono_literals; + + if (m_transitionShader == nullptr) + { + return; + } + + std::mt19937 rand32(m_randomDevice()); + + // Calculate progress values + const auto secondsSinceStart = std::chrono::duration(std::chrono::system_clock::now() - m_transitionStartTime).count(); + + // If duration is zero, + double linearProgress{1.0}; + double cosineProgress{1.0}; + double bicubicProgress{1.0}; + + if (m_durationSeconds > 0.0) + { + linearProgress = secondsSinceStart / m_durationSeconds; + cosineProgress = (-std::cos(linearProgress * M_PI) + 1.0) * 0.5; + bicubicProgress = linearProgress < 0.5 ? 4.0 * linearProgress * linearProgress * linearProgress : 1.0 - pow(-2.0 * linearProgress + 2.0, 3.0) / 2.0; + } + + m_transitionShader->Bind(); + + // Numerical parameters + m_transitionShader->SetUniformFloat3("iResolution", {static_cast(context.viewportSizeX), + static_cast(context.viewportSizeY), + 0.0f}); + + m_transitionShader->SetUniformFloat4("durationParams", {linearProgress, + cosineProgress, + bicubicProgress, + m_durationSeconds}); + + m_transitionShader->SetUniformFloat2("timeParams", {secondsSinceStart, + std::chrono::duration(std::chrono::system_clock::now() - m_lastFrameTime).count()}); + + m_transitionShader->SetUniformInt4("iRandStatic", m_staticRandomValues); + + m_transitionShader->SetUniformInt4("iRandFrame", {rand32(), + rand32(), + rand32(), + rand32()}); + + m_transitionShader->SetUniformFloat3("iBeatValues", {audioData.bass, + audioData.mid, + audioData.treb}); + + m_transitionShader->SetUniformFloat3("iBeatAttValues", {audioData.bassAtt, + audioData.midAtt, + audioData.trebAtt}); + + // Texture samplers + oldPreset.OutputTexture()->Bind(0, m_presetSampler); + newPreset.OutputTexture()->Bind(1, m_presetSampler); + + int textureUnit = 2; + std::vector noiseDescriptors(m_noiseTextureNames.size()); + for (const auto& noiseTextureName : m_noiseTextureNames) + { + noiseDescriptors[textureUnit - 2] = context.textureManager->GetTexture(noiseTextureName); + noiseDescriptors[textureUnit - 2].Bind(textureUnit, *m_transitionShader); + textureUnit++; + } + + // Render the transition quad + glBindVertexArray(m_vaoID); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Clean up + oldPreset.OutputTexture()->Unbind(0); + newPreset.OutputTexture()->Unbind(1); + + for (int i = 2; i < textureUnit; i++) + { + noiseDescriptors[textureUnit - 2].Unbind(textureUnit); + } + + Shader::Unbind(); + + // Update last frame time. + m_lastFrameTime = std::chrono::system_clock::now(); +} diff --git a/src/libprojectM/Renderer/PresetTransition.hpp b/src/libprojectM/Renderer/PresetTransition.hpp new file mode 100644 index 000000000..af181ac2a --- /dev/null +++ b/src/libprojectM/Renderer/PresetTransition.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "RenderItem.hpp" +#include "Shader.hpp" +#include "TextureSamplerDescriptor.hpp" + +#include + +#include + +#include +#include + +/** + * @brief Implements the shader and rendering logic to blend two presets into each other. + */ +class PresetTransition : public RenderItem +{ +public: + PresetTransition() = delete; + + explicit PresetTransition(const std::shared_ptr& transitionShader, double durationSeconds); + + void InitVertexAttrib() override; + + /** + * @brief Returns true if the transition is done. + * @return false if the transition is still in progress, true if it's done. + */ + auto IsDone() const -> bool; + + /** + * @brief Updates the transition variables and renders the shader quad to the current FBO. + * @param oldPreset A reference to the old (fading out) preset. + * @param newPreset A reference to the new (fading in) preset. + * @param context The rendering context used to render the presets. + * @param audioData Current audio data and beat detection values. + */ + void Draw(const Preset& oldPreset, + const Preset& newPreset, + const RenderContext& context, + const libprojectM::Audio::FrameAudioData& audioData); + +private: + std::vector m_noiseTextureNames{"noise_lq", + "pw_noise_lq", + "noise_mq", + "pw_noise_mq", + "noise_hq", + "pw_noise_hq", + "noisevol_lq", + "pw_noisevol_lq", + "noisevol_hq", + "pw_noisevol_hq"}; //!< Names of noise textures to retrieve from TextureManager. + + std::shared_ptr m_transitionShader; //!< The compiled shader used for this transition. + std::shared_ptr m_presetSampler{std::make_shared(GL_CLAMP_TO_EDGE, GL_LINEAR)}; //!< Sampler for preset textures. Uses bilinear interpolation and no repeat. + + double m_durationSeconds{3.0}; //!< Transition duration in seconds. + std::chrono::time_point m_transitionStartTime{std::chrono::system_clock::now()}; //!< Start time of this transition. Duration is measured from this point. + std::chrono::time_point m_lastFrameTime{std::chrono::system_clock::now()}; //!< Time when the previous frame was rendered. + + glm::ivec4 m_staticRandomValues{}; //!< Four random integers, remaining static during the whole transition. + + std::random_device m_randomDevice; //!< Seed for the random number generator +}; diff --git a/src/libprojectM/Renderer/TransitionShaders/TransitionShaderBuiltInSimpleBlendGlsl330.frag b/src/libprojectM/Renderer/TransitionShaders/TransitionShaderBuiltInSimpleBlendGlsl330.frag new file mode 100644 index 000000000..cdea8fcd1 --- /dev/null +++ b/src/libprojectM/Renderer/TransitionShaders/TransitionShaderBuiltInSimpleBlendGlsl330.frag @@ -0,0 +1,13 @@ + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = fragCoord / iResolution.xy; + vec3 imgOld = texture(iChannel0, uv).xyz; + vec3 imgNew = texture(iChannel1, uv).xyz; + + // Blending + vec3 col = vec3((1.0 - iProgressCosine) * imgOld + iProgressCosine * imgNew); + + // Output to screen + fragColor = vec4(col, 1.0); +} diff --git a/src/libprojectM/Renderer/TransitionShaders/TransitionShaderHeaderGlsl330.frag b/src/libprojectM/Renderer/TransitionShaders/TransitionShaderHeaderGlsl330.frag new file mode 100644 index 000000000..251a7d60a --- /dev/null +++ b/src/libprojectM/Renderer/TransitionShaders/TransitionShaderHeaderGlsl330.frag @@ -0,0 +1,56 @@ +// Uniforms +uniform vec3 iResolution; +uniform vec4 durationParams; +uniform vec2 timeParams; +uniform float iFrameRate; +uniform int iFrame; +uniform ivec4 iRandStatic; +uniform ivec4 iRandFrame; +uniform vec3 iBeatValues; +uniform vec3 iBeatAttValues; + +#define iProgressLinear durationParams.x +#define iProgressCosine durationParams.y +#define iProgressBicubic durationParams.z +#define iTransitionDuration durationParams.w + +#define iTime timeParams.x +#define iTimeDelta timeParams.y + +#define iBass iBeatValues.x; +#define iMid iBeatValues.y; +#define iTreb iBeatValues.z; + +#define iBassAtt iBeatAttValues.x; +#define iMidAtt iBeatAttValues.y; +#define iTrebAtt iBeatAttValues.z; + +// Samplers +uniform sampler2D iChannel0; +uniform sampler2D iChannel1; + +// These are named as in Milkdrop shaders so we can reuse the code. +uniform sampler2D sampler_noise_lq; +uniform sampler2D sampler_pw_noise_lq; +uniform sampler2D sampler_noise_mq; +uniform sampler2D sampler_pw_noise_mq; +uniform sampler2D sampler_noise_hq; +uniform sampler2D sampler_pw_noise_hq; +uniform sampler3D sampler_noisevol_lq; +uniform sampler3D sampler_pw_noisevol_lq; +uniform sampler3D sampler_noisevol_hq; +uniform sampler3D sampler_pw_noisevol_hq; + +#define iNoiseLQ sampler_noise_lq; +#define iNoiseLQNearest sampler_pw_noise_lq; +#define iNoiseMQ sampler_noise_mq; +#define iNoiseMQNearest sampler_pw_noise_mq; +#define iNoiseHQ sampler_noise_hq; +#define iNoiseHQNearest sampler_pw_noise_hq; +#define iNoiseVolLQ sampler_noisevol_lq; +#define iNoiseVolLQNearest sampler_pw_noisevol_lq; +#define iNoiseVolHQ sampler_noisevol_hq; +#define iNoiseVolHQNearest sampler_pw_noisevol_hq; + +// Shader output +out vec4 _prjm_transition_out; diff --git a/src/libprojectM/Renderer/TransitionShaders/TransitionShaderMainGlsl330.frag b/src/libprojectM/Renderer/TransitionShaders/TransitionShaderMainGlsl330.frag new file mode 100644 index 000000000..dcf454b48 --- /dev/null +++ b/src/libprojectM/Renderer/TransitionShaders/TransitionShaderMainGlsl330.frag @@ -0,0 +1,10 @@ + +void main() { + _prjm_transition_out = vec4(1.0, 1.0, 1.0, 1.0); + + vec4 _user_out_color = vec4(1.0, 1.0, 1.0, 1.0); + + mainImage(_user_out_color, gl_FragCoord.xy); + + _prjm_transition_out = vec4(_user_out_color.xyz, 1.0); +} \ No newline at end of file diff --git a/src/libprojectM/Renderer/TransitionShaders/TransitionVertexShaderGlsl330.vert b/src/libprojectM/Renderer/TransitionShaders/TransitionVertexShaderGlsl330.vert new file mode 100644 index 000000000..f81e1c0d9 --- /dev/null +++ b/src/libprojectM/Renderer/TransitionShaders/TransitionVertexShaderGlsl330.vert @@ -0,0 +1,7 @@ +precision mediump float; + +layout(location = 0) in vec2 iPosition; + +void main() { + gl_Position = vec4(iPosition, 0.0, 1.0); +}