Skip to content

Commit

Permalink
Fix upside-down and vertical effect misalignment issues.
Browse files Browse the repository at this point in the history
If anyone else can come up with a solution that doesn't involve up to three vertical flips, please implement.
  • Loading branch information
kblaschke committed Oct 3, 2023
1 parent 70132f1 commit 0d2ac56
Show file tree
Hide file tree
Showing 19 changed files with 321 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/libprojectM/MilkdropPreset/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ add_library(MilkdropPreset OBJECT
Filters.hpp
FinalComposite.cpp
FinalComposite.hpp
FlipTexture.cpp
FlipTexture.hpp
IdlePreset.cpp
IdlePreset.hpp
MilkdropNoise.cpp
Expand Down
2 changes: 1 addition & 1 deletion src/libprojectM/MilkdropPreset/CustomWaveform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext)
}

m_presetState.untexturedShader.Bind();
m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjectionFlipped);
m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection);

auto iterations = (m_drawThick && !m_useDots) ? 4 : 1;

Expand Down
3 changes: 2 additions & 1 deletion src/libprojectM/MilkdropPreset/Filters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ void Filters::Draw()
glEnable(GL_BLEND);

m_presetState.untexturedShader.Bind();
m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjectionFlipped);
m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection);

glBindVertexArray(m_vaoID);
glVertexAttrib4f(1, 1.0, 1.0, 1.0, 1.0);
Expand Down Expand Up @@ -88,6 +88,7 @@ void Filters::Invert()
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

void Filters::UpdateMesh()
{
if (m_viewportWidth == m_presetState.renderContext.viewportSizeX &&
Expand Down
5 changes: 5 additions & 0 deletions src/libprojectM/MilkdropPreset/FinalComposite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ void FinalComposite::Draw(const PresetState& presetState, const PerFrameContext&
Shader::Unbind();
}

auto FinalComposite::HasCompositeShader() const -> bool
{
return m_compositeShader != nullptr;
}

void FinalComposite::InitializeMesh(const PresetState& presetState)
{
if (m_viewportWidth == presetState.renderContext.viewportSizeX &&
Expand Down
6 changes: 6 additions & 0 deletions src/libprojectM/MilkdropPreset/FinalComposite.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class FinalComposite : public RenderItem
void Draw(const PresetState& presetState,
const PerFrameContext& perFrameContext);

/**
* @brief Returns if the final composite is using a shader or classic filters.
* @return true if the final composite is done via a shader, false if not.
*/
auto HasCompositeShader() const -> bool;

private:
/**
* Composite mesh vertex with all required attributes.
Expand Down
148 changes: 148 additions & 0 deletions src/libprojectM/MilkdropPreset/FlipTexture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include "FlipTexture.hpp"

FlipTexture::FlipTexture(const PresetState& presetState)
: RenderItem()
, m_presetState(presetState)
{
RenderItem::Init();

m_framebuffer.CreateColorAttachment(0, 0);
}

void FlipTexture::InitVertexAttrib()
{
glEnableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glEnableVertexAttribArray(2);

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast<void*>(offsetof(TexturedPoint, x))); // Position
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast<void*>(offsetof(TexturedPoint, u))); // Texture coordinate

std::array<RenderItem::TexturedPoint, 4> points;

points[0].x = -1.0;
points[0].y = 1.0;
points[1].x = 1.0;
points[1].y = 1.0;
points[2].x = -1.0;
points[2].y = -1.0;
points[3].x = 1.0;
points[3].y = -1.0;

points[0].u = 0.0;
points[0].v = 1.0;
points[1].u = 1.0;
points[1].v = 1.0;
points[2].u = 0.0;
points[2].v = 0.0;
points[3].u = 1.0;
points[3].v = 0.0;

glBufferData(GL_ARRAY_BUFFER, sizeof(points), points.data(), GL_STATIC_DRAW);
}

void FlipTexture::Draw(const std::shared_ptr<Texture>& originalTexture, const std::shared_ptr<Texture>& targetTexture)
{
if (originalTexture == nullptr || originalTexture == targetTexture)
{
return;
}

UpdateTextureSize();

if (m_viewportWidth == 0 || m_viewportHeight == 0)
{
return;
}

std::shared_ptr<Texture> internalTexture;

m_framebuffer.Bind(0);

// Draw from unflipped texture
originalTexture->Bind(0);

if (targetTexture)
{
internalTexture = m_framebuffer.GetColorAttachmentTexture(0, 0);
m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(targetTexture);
}

Flip();

// Rebind our internal texture.
if (targetTexture)
{
m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(internalTexture);
}

Framebuffer::Unbind();
}

void FlipTexture::Draw(const std::shared_ptr<Texture>& originalTexture, Framebuffer& framebuffer, int framebufferIndex)
{
if (originalTexture == nullptr || framebuffer.GetColorAttachmentTexture(framebufferIndex, 0) == nullptr)
{
return;
}

UpdateTextureSize();

if (m_viewportWidth == 0 || m_viewportHeight == 0)
{
return;
}

m_framebuffer.Bind(0);

// Draw from unflipped texture
originalTexture->Bind(0);

Flip();

// Swap texture attachments
auto tempAttachment = framebuffer.GetAttachment(framebufferIndex, TextureAttachment::AttachmentType::Color, 0);
framebuffer.RemoveColorAttachment(framebufferIndex, 0);
framebuffer.SetAttachment(framebufferIndex, 0, m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0));
m_framebuffer.RemoveColorAttachment(0, 0);
m_framebuffer.SetAttachment(0, 0, tempAttachment);

Framebuffer::Unbind();
}

auto FlipTexture::FlippedTexture() -> std::shared_ptr<Texture>
{
return m_framebuffer.GetColorAttachmentTexture(0, 0);
}

void FlipTexture::UpdateTextureSize()
{
if (m_viewportWidth == m_presetState.renderContext.viewportSizeX &&
m_viewportHeight == m_presetState.renderContext.viewportSizeY)
{
return;
}

m_viewportWidth = m_presetState.renderContext.viewportSizeX;
m_viewportHeight = m_presetState.renderContext.viewportSizeY;

m_framebuffer.SetSize(m_viewportWidth, m_viewportHeight);
}

void FlipTexture::Flip() const
{
m_presetState.texturedShader.Bind();
m_presetState.texturedShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection);
m_presetState.texturedShader.SetUniformInt("texture_sampler", 0);

m_sampler.Bind(0);

glBindVertexArray(m_vaoID);
glVertexAttrib4f(1, 1.0, 1.0, 1.0, 1.0); // Color
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);

glBindTexture(GL_TEXTURE_2D, 0);
Sampler::Unbind(0);
Shader::Unbind();
}
67 changes: 67 additions & 0 deletions src/libprojectM/MilkdropPreset/FlipTexture.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#pragma once

#include "PresetState.hpp"

#include <Renderer/Framebuffer.hpp>
#include <Renderer/RenderItem.hpp>

/**
* @class FlipTexture
* @brief Flips the given input texture upside-down.
*
* Milkdrop uses HLSL, so the input and output UV coordinates in the draw call are upside-down because
* DirectX has the origin at the top-left while OpenGL/Vulkan use the bottom-left.
*
* Some presets need the input, the calculated UVs and the output to be properly aligned, so at some
* point, input textures must be flipped for the next rendering step. This class uses a simple draw
* call with a pass-through shader to draw the same image 1:1, but vertically flipped.
*/
class FlipTexture : public RenderItem
{
public:
FlipTexture() = delete;
explicit FlipTexture(const PresetState& presetState);

void InitVertexAttrib();

/**
* @brief Flips the original texture either into the object's internal framebuffer or a given target texture.
* The original and target textures must not be the same.
* @param originalTexture The texture to be flipped.
* @param targetTexture Optional target texture to draw onto.
*/
void Draw(const std::shared_ptr<Texture>& originalTexture, const std::shared_ptr<Texture>& targetTexture = {});

/**
* @brief Flips the texture bound the given framebuffer's first color attachment.
* This is done by drawing into a second framebuffer, then swapping the textures, so the original texture
* can be the current color attachment of targetFramebuffer.
* @param originalTexture The texture to be flipped.
* @param targetFramebuffer Optional target texture to draw onto.
* @param framebufferIndex The index of the framebuffer to use.
*/
void Draw(const std::shared_ptr<Texture>& originalTexture, Framebuffer& framebuffer, int framebufferIndex);

/**
* @brief Returns the flipped texture.
*
* @return The flipped texture.
*/
auto FlippedTexture() -> std::shared_ptr<Texture>;

private:
/**
* Updates the mesh
*/
void UpdateTextureSize();

void Flip() const;

const PresetState& m_presetState; //!< The global preset state.

Framebuffer m_framebuffer{1}; //!< Framebuffer for drawing the flipped texture
Sampler m_sampler{GL_CLAMP_TO_EDGE, GL_NEAREST}; //!< Texture sampler settings

int m_viewportWidth{}; //!< Last known viewport width
int m_viewportHeight{}; //!< Last known viewport height
};
20 changes: 16 additions & 4 deletions src/libprojectM/MilkdropPreset/MilkdropPreset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,12 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio
m_motionVectors.Draw(m_perFrameContext, m_motionVectorUVMap->Texture());
}

// We now draw to the first framebuffer, but read from the second one for warping and textured shapes.
m_framebuffer.BindRead(m_previousFrameBuffer);
m_framebuffer.BindDraw(m_currentFrameBuffer);
// y-flip the previous frame and assign the flipped texture as "main"
m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0));
m_state.mainTexture = m_flipTexture.FlippedTexture();

// We now draw to the current framebuffer.
m_framebuffer.Bind(m_currentFrameBuffer);

// Add motion vector u/v texture for the warp mesh draw and clean both buffers.
m_framebuffer.SetAttachment(m_currentFrameBuffer, 1, m_motionVectorUVMap);
Expand Down Expand Up @@ -135,15 +138,24 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio

// Todo: Song title anim would go here

// y-flip the image for final compositing again
m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_currentFrameBuffer, 0));
m_state.mainTexture = m_flipTexture.FlippedTexture();

// We no longer need the previous frame image, use it to render the final composite.
m_framebuffer.BindRead(m_currentFrameBuffer);
m_framebuffer.BindDraw(m_previousFrameBuffer);
m_state.mainTexture = m_framebuffer.GetColorAttachmentTexture(m_currentFrameBuffer, 0);

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.
m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0), m_framebuffer, m_previousFrameBuffer);
}

// TEST: Copy result to default framebuffer
m_framebuffer.BindRead(m_previousFrameBuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
Expand Down
2 changes: 2 additions & 0 deletions src/libprojectM/MilkdropPreset/MilkdropPreset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "DarkenCenter.hpp"
#include "Filters.hpp"
#include "FinalComposite.hpp"
#include "FlipTexture.hpp"
#include "MotionVectors.hpp"
#include "PerFrameContext.hpp"
#include "PerPixelContext.hpp"
Expand Down Expand Up @@ -116,6 +117,7 @@ class MilkdropPreset : public Preset
std::array<std::unique_ptr<CustomShape>, CustomShapeCount> m_customShapes; //!< Custom shapes in this preset.
DarkenCenter m_darkenCenter; //!< Center darkening effect.
Border m_border; //!< Inner/outer borders.
FlipTexture m_flipTexture{ m_state }; //!< Texture flip filter

FinalComposite m_finalComposite; //!< Final composite shader or filters.

Expand Down
2 changes: 1 addition & 1 deletion src/libprojectM/MilkdropPreset/MilkdropShader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ void MilkdropShader::LoadVariables(const PresetState& presetState, const PerFram

m_shader.Bind();

m_shader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjectionFlipped);
m_shader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection);

m_shader.SetUniformFloat4("rand_frame", {floatRand(),
floatRand(),
Expand Down
2 changes: 1 addition & 1 deletion src/libprojectM/MilkdropPreset/PerPixelMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ void PerPixelMesh::WarpedBlit(const PresetState& presetState,
if (!m_warpShader)
{
m_perPixelMeshShader.Bind();
m_perPixelMeshShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjectionFlipped);
m_perPixelMeshShader.SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection);
m_perPixelMeshShader.SetUniformInt("texture_sampler", 0);
m_perPixelMeshShader.SetUniformFloat4("aspect", {presetState.renderContext.aspectX,
presetState.renderContext.aspectY,
Expand Down
8 changes: 4 additions & 4 deletions src/libprojectM/MilkdropPreset/PresetState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@

#include <random>

const glm::mat4 PresetState::orthogonalProjection = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -40.0f, 40.0f);
const glm::mat4 PresetState::orthogonalProjectionFlipped = glm::ortho(-1.0f, 1.0f, 1.0f, -1.0f, -40.0f, 40.0f);
const glm::mat4 PresetState::orthogonalProjection = glm::ortho(-1.0f, 1.0f, 1.0f, -1.0f, -40.0f, 40.0f);
const glm::mat4 PresetState::orthogonalProjectionFlipped = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, -40.0f, 40.0f);

PresetState::PresetState()
: globalMemory(projectm_eval_memory_buffer_create())
{
auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get();
untexturedShader.CompileProgram(staticShaders->GetUntexturedDrawVertexShader(),
staticShaders->GetUntexturedDrawFragmentShader());
staticShaders->GetUntexturedDrawFragmentShader());
texturedShader.CompileProgram(staticShaders->GetTexturedDrawVertexShader(),
staticShaders->GetTexturedDrawFragmentShader());
staticShaders->GetTexturedDrawFragmentShader());

std::random_device randomDevice;
std::mt19937 randomGenerator(randomDevice());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ void main() {
// Milkdrop's original code did a simple bilinear interpolation, but here it was already
// done by the fragment shader during the warp mesh drawing. We just need to look up the
// motion vector coordinate.
vec2 oldUV = texture(warp_coordinates, pos.xy).xy;
// We simply invert the y coordinate because it's easier than flipping the u/v texture.
vec2 oldUV = texture(warp_coordinates, vec2(pos.x, 1.0 - pos.y)).xy;

// Enforce minimum trail length
vec2 dist = oldUV - pos;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ void main() {

// Initial texture coordinates, with built-in zoom factor
float u = pos.x * aspectX * 0.5 * zoom2Inverse + 0.5;
float v = -pos.y * aspectY * 0.5 * zoom2Inverse + 0.5;
float v = pos.y * aspectY * 0.5 * zoom2Inverse + 0.5;

// original UV coordinates
vec2 uv_original = vec2(pos.x * 0.5 + 0.5 + texelOffset.x,
-pos.y * 0.5 + 0.5 + texelOffset.y);
pos.y * 0.5 + 0.5 + texelOffset.y);

// Stretch on X, Y
u = (u - center.x) / stretch.x + center.x;
Expand Down
Loading

0 comments on commit 0d2ac56

Please sign in to comment.