From 62b84ef724db6d9ff499aa07e47d627f14ffb62b Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Sun, 5 Mar 2023 18:42:09 +0100 Subject: [PATCH] Added a framebuffer class for drawing the presets into. --- .../MilkdropPreset/MilkdropPreset.cpp | 42 ++++- .../MilkdropPreset/MilkdropPreset.hpp | 8 + .../MilkdropPreset/MotionVectors.cpp | 8 +- .../MilkdropPreset/MotionVectors.hpp | 20 +-- src/libprojectM/Renderer/CMakeLists.txt | 4 + src/libprojectM/Renderer/Framebuffer.cpp | 164 ++++++++++++++++++ src/libprojectM/Renderer/Framebuffer.hpp | 105 +++++++++++ .../Renderer/TextureAttachment.cpp | 92 ++++++++++ .../Renderer/TextureAttachment.hpp | 44 +++++ 9 files changed, 465 insertions(+), 22 deletions(-) create mode 100644 src/libprojectM/Renderer/Framebuffer.cpp create mode 100644 src/libprojectM/Renderer/Framebuffer.hpp create mode 100644 src/libprojectM/Renderer/TextureAttachment.cpp create mode 100644 src/libprojectM/Renderer/TextureAttachment.hpp diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp index 98832c609..cf5b0a78a 100755 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp @@ -26,12 +26,6 @@ #include "PresetFileParser.hpp" #include "PresetFrameIO.hpp" -#ifdef _WIN32 -#include "dirent.h" -#else -#include -#endif /** _WIN32 */ - #ifdef MILKDROP_PRESET_DEBUG #include #endif @@ -57,6 +51,10 @@ MilkdropPreset::MilkdropPreset(const std::string& absoluteFilePath) void MilkdropPreset::InitializePreset(PresetFileParser& parsedFile) { + // Create the offscreen rendering surfaces. + m_framebuffer.CreateColorAttachment(0, 0); + m_framebuffer.CreateColorAttachment(1, 0); + // Load global init variables into the state m_state.Initialize(parsedFile); @@ -142,11 +140,29 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio m_state.audioData = audioData; m_state.renderContext = renderContext; + // Update framebuffer size if needed + m_framebuffer.SetSize(renderContext.viewportSizeX, renderContext.viewportSizeY); + // First evaluate per-frame code PerFrameUpdate(); - // Motion vector field - // ToDo: Draw motion vectors to separate texture + // Motion vector field. Drawn to the previous frame texture before warping it. + m_framebuffer.Bind(1); + // ToDo: Move this to the draw call and pass in the per-frame context. + m_motionVectors.r = static_cast(*m_perFrameContext.mv_r); + m_motionVectors.g = static_cast(*m_perFrameContext.mv_g); + m_motionVectors.b = static_cast(*m_perFrameContext.mv_b); + m_motionVectors.a = static_cast(*m_perFrameContext.mv_a); + m_motionVectors.length = static_cast(*m_perFrameContext.mv_l); + m_motionVectors.x_num = static_cast(*m_perFrameContext.mv_x); + m_motionVectors.y_num = static_cast(*m_perFrameContext.mv_y); + m_motionVectors.x_offset = static_cast(*m_perFrameContext.mv_dx); + m_motionVectors.y_offset = static_cast(*m_perFrameContext.mv_dy); + m_motionVectors.Draw(renderContext); + + // We now draw to the first framebuffer, but read from the second one for warping. + m_framebuffer.BindRead(1); + m_framebuffer.BindDraw(0); // Draw previous frame image warped via per-pixel mesh and warp shader // ToDo: ComputeGridAlphaValues @@ -162,12 +178,20 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio wave->Draw(m_perFrameContext); } m_waveform.Draw(); + // ToDo: Sprite drawing would go here. + // Todo: Song title anim would go here - // ToDo: Store main texture for next frame + // Copy pixels from framebuffer index 0 to 1 + m_framebuffer.BindRead(0); + m_framebuffer.BindDraw(1); + glBlitFramebuffer(0, 0, renderContext.viewportSizeX, renderContext.viewportSizeY, + 0, 0, renderContext.viewportSizeX, renderContext.viewportSizeY, + GL_COLOR_BUFFER_BIT, GL_NEAREST); // ToDo: Apply composite shader + m_framebuffer.Bind(0); // ToDo: Draw user sprites (can have evaluated code) } diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp index 019dc4f79..c077f3727 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp @@ -27,10 +27,13 @@ #include "CustomWaveform.hpp" #include "PerFrameContext.hpp" #include "PerPixelContext.hpp" +#include "PerPixelMesh.hpp" #include "Preset.hpp" #include "PresetFrameIO.hpp" #include "Waveform.hpp" +#include + #include #include #include @@ -91,10 +94,15 @@ private: std::string m_absoluteFilePath; //!< The absolute file path of the MilkdropPreset std::string m_absolutePath; //!< The absolute path of the MilkdropPreset + Framebuffer m_framebuffer{2}; //!< Preset rendering framebuffer with two surfaces (last frame and current frame). + PresetState m_state; //!< Preset state container. PerFrameContext m_perFrameContext; //!< Preset per-frame evaluation code context. PerPixelContext m_perPixelContext; //!< Preset per-pixel/per-vertex evaluation code context. + PerPixelMesh m_perPixelMesh; //!< The per-pixel/per-vertex mesh, responsible for most of the movement/warp effects in Milkdrop presets. + + MotionVectors m_motionVectors; //!< Motion vector grid. Waveform m_waveform; //!< Preset default waveform. std::array, CustomWaveformCount> m_customWaveforms; //!< Custom waveforms in this preset. std::array, CustomShapeCount> m_customShapes; //!< Custom shapes in this preset. diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.cpp b/src/libprojectM/MilkdropPreset/MotionVectors.cpp index 80021f93e..d3614a19e 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.cpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.cpp @@ -10,12 +10,13 @@ MotionVectors::MotionVectors() void MotionVectors::InitVertexAttrib() { glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glDisableVertexAttribArray(1); } -void MotionVectors::Draw(RenderContext &context) +void MotionVectors::Draw(const RenderContext& context) { + // ToDo: Implement the actual Milkdop behaviour here, including reverse propagation. float intervalx=1.0/x_num; float intervaly=1.0/y_num; @@ -42,7 +43,7 @@ void MotionVectors::Draw(RenderContext &context) glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - glBufferData(GL_ARRAY_BUFFER, sizeof(floatPair) * size, NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(floatPair) * size, nullptr, GL_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(floatPair) * size, points, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -66,6 +67,7 @@ void MotionVectors::Draw(RenderContext &context) glBindVertexArray(m_vaoID); + // ToDo: Milkdrop draws lines in the direction of motion, not just points! glDrawArrays(GL_POINTS,0,size); glBindVertexArray(0); diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.hpp b/src/libprojectM/MilkdropPreset/MotionVectors.hpp index 8b6699970..5e1416481 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.hpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.hpp @@ -26,16 +26,16 @@ public: * Redners the motion vectors. * @param context The render context data. */ - void Draw(RenderContext &context); + void Draw(const RenderContext &context); - float r{ 0.0 }; //!< Red color channel of the motion vectors. - float g{ 0.0 }; //!< Green color channel of the motion vectors. - float b{ 0.0 }; //!< Blue color channel of the motion vectors. - float a{ 0.0 }; //!< Alpha channel of the motion vectors. - float length{ 0.0 }; //!< Line length of the motion vectors. - float x_num{ 0.0 }; //!< Horizontal grid size. - float y_num{ 0.0 }; //!< Vertical grid size. - float x_offset{ 0.0 }; //!< Horizontal grid offset. - float y_offset{ 0.0 }; //!< Vertical grid offset. + float r{ 0.0 }; //!< Red color channel of the motion vectors (mv_r). + float g{ 0.0 }; //!< Green color channel of the motion vectors (mv_g). + float b{ 0.0 }; //!< Blue color channel of the motion vectors (mv_b). + float a{ 0.0 }; //!< Alpha channel of the motion vectors (mv_a). + float length{ 0.0 }; //!< Line length of the motion vectors (mv_l). + float x_num{ 0.0 }; //!< Horizontal grid size (integer part of mv_x). + float y_num{ 0.0 }; //!< Vertical grid size (integer part of mv_y). + float x_offset{ 0.0 }; //!< Horizontal grid offset (mv_dx). + float y_offset{ 0.0 }; //!< Vertical grid offset (mv_dy). }; diff --git a/src/libprojectM/Renderer/CMakeLists.txt b/src/libprojectM/Renderer/CMakeLists.txt index 95bc2a740..fdd786224 100644 --- a/src/libprojectM/Renderer/CMakeLists.txt +++ b/src/libprojectM/Renderer/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(Renderer OBJECT FileScanner.cpp FileScanner.hpp + Framebuffer.cpp + Framebuffer.hpp RenderContext.hpp RenderItem.cpp RenderItem.hpp @@ -15,6 +17,8 @@ add_library(Renderer OBJECT StaticGlShaders.cpp Texture.cpp Texture.hpp + TextureAttachment.cpp + TextureAttachment.hpp TextureManager.cpp TextureManager.hpp ) diff --git a/src/libprojectM/Renderer/Framebuffer.cpp b/src/libprojectM/Renderer/Framebuffer.cpp new file mode 100644 index 000000000..b50312cf4 --- /dev/null +++ b/src/libprojectM/Renderer/Framebuffer.cpp @@ -0,0 +1,164 @@ +#include "Framebuffer.hpp" + +Framebuffer::Framebuffer() +{ + m_framebufferIds.resize(1); + glGenFramebuffers(1, m_framebufferIds.data()); + m_attachments.insert({0, {}}); +} + +Framebuffer::Framebuffer(int framebufferCount) +{ + m_framebufferIds.resize(framebufferCount); + glGenFramebuffers(framebufferCount, m_framebufferIds.data()); + for (int index = 0; index < framebufferCount; index++) + { + m_attachments.insert({index, {}}); + } +} + +Framebuffer::~Framebuffer() +{ + if (!m_framebufferIds.empty()) + { + // Delete attached textures first + m_attachments.clear(); + + glDeleteFramebuffers(static_cast(m_framebufferIds.size()), m_framebufferIds.data()); + m_framebufferIds.clear(); + } +} + +auto Framebuffer::Count() const -> int +{ + return static_cast(m_framebufferIds.size()); +} + +void Framebuffer::Bind(int framebufferIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + glBindFramebuffer(GL_FRAMEBUFFER, m_framebufferIds.at(framebufferIndex)); +} + +void Framebuffer::BindRead(int framebufferIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, m_framebufferIds.at(framebufferIndex)); +} + +void Framebuffer::BindDraw(int framebufferIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_framebufferIds.at(framebufferIndex)); +} + +void Framebuffer::SetSize(int width, int height) +{ + if (width == 0 || height == 0 || + (width == m_width && height == m_height)) + { + return; + } + + m_width = width; + m_height = height; + + for (auto& attachments : m_attachments) + { + Bind(attachments.first); + for (auto& texture : attachments.second) + { + texture.second.SetSize(width, height); + glFramebufferTexture2D(GL_FRAMEBUFFER, texture.first, GL_TEXTURE_2D, texture.second.TextureId(), 0); + } + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + TextureAttachment textureAttachment{TextureAttachment::AttachmentType::Color, m_width, m_height}; + auto textureId = textureAttachment.TextureId(); + m_attachments.at(framebufferIndex).insert({GL_COLOR_ATTACHMENT0 + attachmentIndex, std::move(textureAttachment)}); + + if (m_width > 0 && m_height > 0) + { + Bind(framebufferIndex); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachmentIndex, GL_TEXTURE_2D, textureId, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + +void Framebuffer::CreateDepthAttachment(int framebufferIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + TextureAttachment textureAttachment{TextureAttachment::AttachmentType::Depth, m_width, m_height}; + auto textureId = textureAttachment.TextureId(); + m_attachments.at(framebufferIndex).insert({GL_DEPTH_ATTACHMENT, std::move(textureAttachment)}); + + if (m_width > 0 && m_height > 0) + { + Bind(framebufferIndex); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureId, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + +void Framebuffer::CreateStencilAttachment(int framebufferIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + TextureAttachment textureAttachment{TextureAttachment::AttachmentType::Stencil, m_width, m_height}; + auto textureId = textureAttachment.TextureId(); + m_attachments.at(framebufferIndex).insert({GL_STENCIL_ATTACHMENT, std::move(textureAttachment)}); + + if (m_width > 0 && m_height > 0) + { + Bind(framebufferIndex); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, textureId, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + +void Framebuffer::CreateDepthStencilAttachment(int framebufferIndex) +{ + if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) + { + return; + } + + TextureAttachment textureAttachment{TextureAttachment::AttachmentType::DepthStencil, m_width, m_height}; + auto textureId = textureAttachment.TextureId(); + m_attachments.at(framebufferIndex).insert({GL_DEPTH_STENCIL_ATTACHMENT, std::move(textureAttachment)}); + + if (m_width > 0 && m_height > 0) + { + Bind(framebufferIndex); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, textureId, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} diff --git a/src/libprojectM/Renderer/Framebuffer.hpp b/src/libprojectM/Renderer/Framebuffer.hpp new file mode 100644 index 000000000..0ffccb4b5 --- /dev/null +++ b/src/libprojectM/Renderer/Framebuffer.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include "TextureAttachment.hpp" + +#include "projectM-opengl.h" + +#include +#include + +/** + * @brief A framebuffer class holding one or more framebuffer objects. + * + * Framebuffers act as both render targets and sampling sources. This class holds one or more of those + * objects, either used to store current and previous frame images or sub images for multi-stage + * rendering. + * + * Each framebuffer can have multiple color attachments (at least up to 8), one depth buffer, + * one stencil buffer and one depth stencil buffer. + * + * All framebuffers and their attachments will share the same size. + */ +class Framebuffer +{ +public: + /** + * @brief Creates a new framebuffer object with one framebuffer. + */ + Framebuffer(); + + /** + * @brief Creates a new framebuffer object with a given number of framebuffers. + * @param framebufferCount The number of framebuffers to create. Must be at least 1. + */ + explicit Framebuffer(int framebufferCount); + + /** + * @brief Destroys the framebuffer object and all attachments. + */ + ~Framebuffer(); + + /** + * @brief Returns the number of framebuffers in this object. + * @return The number of framebuffers in this object. + */ + int Count() const; + + /** + * @brief Binds the given index as the current read/write framebuffer. + * @param framebufferIndex The framebuffer index. + */ + void Bind(int framebufferIndex); + + /** + * @brief Binds the given index as the current read framebuffer. + * @param framebufferIndex The framebuffer index. + */ + void BindRead(int framebufferIndex); + + /** + * @brief Binds the given index as the current write/draw framebuffer. + * @param framebufferIndex The framebuffer index. + */ + void BindDraw(int framebufferIndex); + + /** + * @brief Sets the framebuffer texture size. + * + * This will bind the framebuffers and reallocate all attachment textures, creating new + * textures with the given size. The default framebuffer is bound after the call is finished. + * If either width or height is zero or both equal the the current size, the framebuffer contents + * won't be changed. + * @param width The width of the framebuffer. + * @param height The height of the framebuffer. + */ + void SetSize(int width, int height); + + /** + * @brief Adds a new color attachment to the framebuffer. + * The texture is always created in RGBA format. + * @param index The index of the attachment, at least indices 0-7 are guaranteed to be available. + */ + void CreateColorAttachment(int framebufferIndex, int attachmentIndex); + + /** + * @brief Adds a depth attachment to the framebuffer. + */ + void CreateDepthAttachment(int framebufferIndex); + + /** + * @brief Adds a stencil buffer attachment to the framebuffer. + */ + void CreateStencilAttachment(int framebufferIndex); + + /** + * @brief Adds a depth stencil buffer attachment to the framebuffer. + */ + void CreateDepthStencilAttachment(int framebufferIndex); + +private: + std::vector m_framebufferIds{}; //!< The framebuffer IDs returned by OpenGL + std::map> m_attachments; //!< Framebuffer texture attachments. + + int m_width{}; //!< Framebuffers texture width + int m_height{}; //!< Framebuffers texture height. +}; diff --git a/src/libprojectM/Renderer/TextureAttachment.cpp b/src/libprojectM/Renderer/TextureAttachment.cpp new file mode 100644 index 000000000..cb3bc60a2 --- /dev/null +++ b/src/libprojectM/Renderer/TextureAttachment.cpp @@ -0,0 +1,92 @@ +#include "TextureAttachment.hpp" + +TextureAttachment::TextureAttachment(AttachmentType attachment, int width, int height) + : m_attachmentType(attachment) +{ + if (width > 0 && height > 0) + { + ReplaceTexture(width, height); + } +} + +TextureAttachment::~TextureAttachment() +{ + if (m_textureId > 0) + { + glDeleteTextures(1, &m_textureId); + m_textureId = 0; + } +} + +auto TextureAttachment::Type() const -> TextureAttachment::AttachmentType +{ + return m_attachmentType; +} + +auto TextureAttachment::TextureId() const -> GLuint +{ + return m_textureId; +} + +void TextureAttachment::SetSize(int width, int height) +{ + if (width > 0 && height > 0) + { + ReplaceTexture(width, height); + } + else if (m_textureId > 0) + { + glDeleteTextures(1, &m_textureId); + m_textureId = 0; + } +} + +void TextureAttachment::ReplaceTexture(int width, int height) +{ + if (m_textureId > 0) + { + glDeleteTextures(1, &m_textureId); + } + + GLint internalFormat; + GLint textureFormat; + GLenum pixelFormat; + + switch(m_attachmentType) + { + case AttachmentType::Color: + internalFormat = GL_RGBA; + textureFormat = GL_RGBA; + pixelFormat = GL_TEXTURE_2D; + break; + case AttachmentType::Depth: + internalFormat = GL_DEPTH_COMPONENT; + textureFormat = GL_DEPTH_COMPONENT; + pixelFormat = GL_FLOAT; + break; + case AttachmentType::Stencil: + internalFormat = GL_STENCIL_INDEX8; + textureFormat = GL_STENCIL_INDEX; + pixelFormat = GL_UNSIGNED_BYTE; + break; + case AttachmentType::DepthStencil: + internalFormat = GL_DEPTH24_STENCIL8; + textureFormat = GL_DEPTH_STENCIL; + pixelFormat = GL_UNSIGNED_INT_24_8; + break; + + default: + return; + } + + glGenTextures(1, &m_textureId); + glBindTexture(GL_TEXTURE_2D, m_textureId); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, textureFormat, pixelFormat, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); +} \ No newline at end of file diff --git a/src/libprojectM/Renderer/TextureAttachment.hpp b/src/libprojectM/Renderer/TextureAttachment.hpp new file mode 100644 index 000000000..be754b58d --- /dev/null +++ b/src/libprojectM/Renderer/TextureAttachment.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "projectM-opengl.h" + +#include + +/** + * @brief Framebuffer texture attachment. Stores the texture and attachment type. + */ +class TextureAttachment +{ +public: + enum class AttachmentType + { + Color, + Depth, + Stencil, + DepthStencil + }; + + TextureAttachment() = delete; + + explicit TextureAttachment(AttachmentType attachmentType, int width, int height); + + ~TextureAttachment(); + + auto Type() const -> AttachmentType; + + auto TextureId() const -> GLuint; + + /** + * @brief Sets a new texture size. + * Effectively reallocates the texture. + * @param width The new width. + * @param height The new height. + */ + void SetSize(int width, int height); + +private: + void ReplaceTexture(int width, int height); + + AttachmentType m_attachmentType{AttachmentType::Color}; //!< Attachment type of this texture. + GLuint m_textureId{}; //!< The Texture ID. +};