Add PresetTransition class to render the transition quad/shader.

Also added the required shader files. Still need to pack them as resources into the library.
This commit is contained in:
Kai Blaschke
2023-10-24 19:33:53 +02:00
parent 37ec15d13b
commit 9191ff149e
7 changed files with 279 additions and 0 deletions

View File

@ -6,6 +6,8 @@ add_library(Renderer OBJECT
IdleTextures.hpp
MilkdropNoise.cpp
MilkdropNoise.hpp
PresetTransition.cpp
PresetTransition.hpp
RenderContext.hpp
RenderItem.cpp
RenderItem.hpp

View File

@ -0,0 +1,125 @@
#include "PresetTransition.hpp"
#include "TextureManager.hpp"
#include <array>
#include <cmath>
#include <cstddef>
PresetTransition::PresetTransition(const std::shared_ptr<Shader>& 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<RenderItem::Point, 4> 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<void*>(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<double>(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<double>(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<float>(context.viewportSizeX),
static_cast<float>(context.viewportSizeY),
0.0f});
m_transitionShader->SetUniformFloat4("durationParams", {linearProgress,
cosineProgress,
bicubicProgress,
m_durationSeconds});
m_transitionShader->SetUniformFloat2("timeParams", {secondsSinceStart,
std::chrono::duration<float>(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<TextureSamplerDescriptor> 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();
}

View File

@ -0,0 +1,66 @@
#pragma once
#include "RenderItem.hpp"
#include "Shader.hpp"
#include "TextureSamplerDescriptor.hpp"
#include <Preset.hpp>
#include <glm/glm.hpp>
#include <chrono>
#include <random>
/**
* @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<Shader>& 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<std::string> 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<Shader> m_transitionShader; //!< The compiled shader used for this transition.
std::shared_ptr<Sampler> m_presetSampler{std::make_shared<Sampler>(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<std::chrono::system_clock> m_transitionStartTime{std::chrono::system_clock::now()}; //!< Start time of this transition. Duration is measured from this point.
std::chrono::time_point<std::chrono::system_clock> 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
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -0,0 +1,7 @@
precision mediump float;
layout(location = 0) in vec2 iPosition;
void main() {
gl_Position = vec4(iPosition, 0.0, 1.0);
}