From 1b4e32482521c3ebf57c9faece769333906aa1f3 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Thu, 7 Aug 2025 20:06:12 +0200 Subject: [PATCH 01/19] Implement a mesh class for rendering geometry --- src/libprojectM/Renderer/CMakeLists.txt | 11 + src/libprojectM/Renderer/Color.hpp | 178 +++++++++++++++ src/libprojectM/Renderer/Mesh.cpp | 169 ++++++++++++++ src/libprojectM/Renderer/Mesh.hpp | 187 +++++++++++++++ src/libprojectM/Renderer/Point.hpp | 74 ++++++ src/libprojectM/Renderer/TextureUV.hpp | 74 ++++++ src/libprojectM/Renderer/VertexArray.hpp | 42 ++++ src/libprojectM/Renderer/VertexBuffer.hpp | 212 ++++++++++++++++++ .../Renderer/VertexBufferUsage.cpp | 42 ++++ .../Renderer/VertexBufferUsage.hpp | 24 ++ src/libprojectM/Renderer/VertexIndexArray.cpp | 115 ++++++++++ src/libprojectM/Renderer/VertexIndexArray.hpp | 60 +++++ 12 files changed, 1188 insertions(+) create mode 100644 src/libprojectM/Renderer/Color.hpp create mode 100644 src/libprojectM/Renderer/Mesh.cpp create mode 100644 src/libprojectM/Renderer/Mesh.hpp create mode 100644 src/libprojectM/Renderer/Point.hpp create mode 100644 src/libprojectM/Renderer/TextureUV.hpp create mode 100644 src/libprojectM/Renderer/VertexArray.hpp create mode 100644 src/libprojectM/Renderer/VertexBuffer.hpp create mode 100644 src/libprojectM/Renderer/VertexBufferUsage.cpp create mode 100644 src/libprojectM/Renderer/VertexBufferUsage.hpp create mode 100644 src/libprojectM/Renderer/VertexIndexArray.cpp create mode 100644 src/libprojectM/Renderer/VertexIndexArray.hpp diff --git a/src/libprojectM/Renderer/CMakeLists.txt b/src/libprojectM/Renderer/CMakeLists.txt index b98c049686..18f9300285 100644 --- a/src/libprojectM/Renderer/CMakeLists.txt +++ b/src/libprojectM/Renderer/CMakeLists.txt @@ -17,6 +17,7 @@ generate_shader_resources(${CMAKE_CURRENT_BINARY_DIR}/BuiltInTransitionsResource add_library(Renderer OBJECT ${CMAKE_CURRENT_BINARY_DIR}/BuiltInTransitionsResources.hpp + Color.hpp CopyTexture.cpp CopyTexture.hpp FileScanner.cpp @@ -24,8 +25,11 @@ add_library(Renderer OBJECT Framebuffer.cpp Framebuffer.hpp IdleTextures.hpp + Mesh.cpp + Mesh.hpp MilkdropNoise.cpp MilkdropNoise.hpp + Point.hpp PresetTransition.cpp PresetTransition.hpp RenderContext.hpp @@ -45,8 +49,15 @@ add_library(Renderer OBJECT TextureManager.hpp TextureSamplerDescriptor.cpp TextureSamplerDescriptor.hpp + TextureUV.hpp TransitionShaderManager.cpp TransitionShaderManager.hpp + VertexBuffer.hpp + VertexBufferUsage.cpp + VertexBufferUsage.hpp + VertexIndexArray.cpp + VertexIndexArray.hpp + VertexArray.hpp ) target_include_directories(Renderer diff --git a/src/libprojectM/Renderer/Color.hpp b/src/libprojectM/Renderer/Color.hpp new file mode 100644 index 0000000000..87c8e388bb --- /dev/null +++ b/src/libprojectM/Renderer/Color.hpp @@ -0,0 +1,178 @@ +#pragma once + +#include + +#include +#include + +namespace libprojectM { +namespace Renderer { + +/** + * @brief A color with RGBA channels. + */ +class Color +{ +public: + Color() = default; + + /** + * Constructs a color with the given values. + * @param r The color's r value. + * @param g The color's g value. + * @param b The color's b value. + * @param a The color's a value. + */ + Color(float r, float g, float b, float a) + : m_r(r) + , m_g(g) + , m_b(b) + , m_a(a) {}; + + /** + * Returns the color's r value. + * @return The color's r value. + */ + auto R() const -> float + { + return m_r; + } + + /** + * Sets the color's r value. + * @param r The new r value. + */ + void SetR(float r) + { + m_r = r; + } + + /** + * Returns the color's g value. + * @return The color's g value. + */ + auto G() const -> float + { + return m_g; + } + + /** + * Sets the color's g value. + * @param g The new g value. + */ + void SetG(float g) + { + m_g = g; + } + + /** + * Returns the color's b value. + * @return The color's b value. + */ + auto B() const -> float + { + return m_b; + } + + /** + * Sets the color's b value. + * @param b The new b value. + */ + void SetB(float b) + { + m_b = b; + } + + /** + * Returns the color's a value. + * @return The color's a value. + */ + auto A() const -> float + { + return m_a; + } + + /** + * Sets the color's a value. + * @param a The new a value. + */ + void SetA(float a) + { + m_a = a; + } + + /** + * @brief Computes the modulus to wrap float values into the range of [0.0, 1.0]. + * + * This code mimics the following equations used by the original Milkdrop code: + * + * v[0].Diffuse = + * ((((int)(*pState->m_shape[i].var_pf_a * 255 * alpha_mult)) & 0xFF) << 24) | + * ((((int)(*pState->m_shape[i].var_pf_r * 255)) & 0xFF) << 16) | + * ((((int)(*pState->m_shape[i].var_pf_g * 255)) & 0xFF) << 8) | + * ((((int)(*pState->m_shape[i].var_pf_b * 255)) & 0xFF) ); + * + * In projectM, we use float values when drawing primitives or configuring vertices. + * Converting the above back to a float looks like this: + * + * d = (f * 255.0) & 0xFF = int((f * 255.0) % 256.0); * + * f' = float(d)/255.0; + * + * * Here % represents the Euclidean modulus, not the traditional (signed) fractional + * remainder. + * + * To avoid limiting ourselves to 8 bits, we combine the above equations into one that + * does not discard any information: + * + * f' = ((f * 255.0) % 256.0) / 255.0; + * = f % (256.0/255.0); + * + * Since we're using the Euclidean modulus we need to generate it from the fractional + * remainder using a standard equation. + * @param x The color channel value to clamp. + * @return The color value clamped to the range of [0.0, 1.0]. + */ + static auto Modulo(float x) -> float + { + const float m = 256.0f / 255.0f; + return std::fmod(std::fmod(x, m) + m, m); + } + + /** + * @brief Computes the modulus to wrap float values into the range of [0.0, 1.0]. + * @param x The color channel value to clamp. + * @return The color value clamped to the range of [0.0, 1.0]. + */ + static auto Modulo(double x) -> float + { + // Convert the input to float before performing the computation. + return Modulo(static_cast(x)); + } + + /** + * @brief Computes the modulus to wrap float values into the range of [0.0, 1.0]. + * @param col The Color instance to clamp. + * @return The color value clamped to the range of [0.0, 1.0]. + */ + static auto Modulo(Color col) -> Color + { + return Color(Modulo(col.R()), + Modulo(col.G()), + Modulo(col.B()), + Modulo(col.A())); + } + + static void InitializeAttributePointer(uint32_t attributeIndex) + { + glVertexAttribPointer(attributeIndex, sizeof(Color) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(Color), nullptr); + } + +private: + float m_r{}; //!< The color's r value. + float m_g{}; //!< The color's g value. + float m_b{}; //!< The color's b value. + float m_a{}; //!< The color's a value. +}; + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/Mesh.cpp b/src/libprojectM/Renderer/Mesh.cpp new file mode 100644 index 0000000000..83236a1069 --- /dev/null +++ b/src/libprojectM/Renderer/Mesh.cpp @@ -0,0 +1,169 @@ +#include "Mesh.hpp" + +namespace libprojectM { +namespace Renderer { + +Mesh::Mesh() +{ + Initialize(); +} + +Mesh::Mesh(VertexBufferUsage usage) + : m_vertices(usage) + , m_colors(usage) + , m_textureUVs(usage) +{ + Initialize(); +} + +Mesh::Mesh(VertexBufferUsage usage, bool useColor, bool useTextureUVs) + : m_vertices(usage) + , m_colors(usage) + , m_textureUVs(usage) + , m_useColorAttributes(useColor) + , m_useUVAttributes(useTextureUVs) +{ + Initialize(); +} + +void Mesh::SetVertexCount(uint32_t vertexCount) +{ + m_vertices.Resize(vertexCount); + + if (m_useColorAttributes) + { + m_colors.Resize(vertexCount); + } + + if (m_useUVAttributes) + { + m_textureUVs.Resize(vertexCount); + } +} + +void Mesh::SetUseColor(bool useColor) +{ + m_useColorAttributes = useColor; + if (m_useColorAttributes) + { + m_colors.Resize(m_vertices.Size()); + } + else + { + m_colors.Resize(0); + } +} + +void Mesh::SetUseUV(bool useUV) +{ + m_useUVAttributes = useUV; + if (m_useUVAttributes) + { + m_textureUVs.Resize(m_vertices.Size()); + } + else + { + m_textureUVs.Resize(0); + } +} + +auto Mesh::Indices() -> VertexIndexArray& +{ + return m_indices; +} + +auto Mesh::Indices() const -> const VertexIndexArray& +{ + return m_indices; +} + +void Mesh::Bind() const +{ + m_vertexArray.Bind(); +} + +void Mesh::Unbind() +{ + VertexArray::Unbind(); + VertexBuffer::Unbind(); + VertexIndexArray::Unbind(); +} + +void Mesh::Update() +{ + m_vertexArray.Bind(); + m_vertices.Update(); + m_colors.Update(); + m_textureUVs.Update(); + + VertexBuffer::SetEnableAttributeArray(1, m_useColorAttributes); + VertexBuffer::SetEnableAttributeArray(2, m_useUVAttributes); + + m_indices.Update(); +} + +void Mesh::Draw() +{ + m_vertexArray.Bind(); + + // Assume each vertex is drawn once, in order. + if (m_indices.Empty()) + { + m_indices.Resize(m_vertices.Size()); + for (size_t index = 0; index < m_vertices.Size(); index++) + { + m_indices[index] = index; + } + m_indices.Update(); + } + + GLuint primitiveType{GL_LINES}; + switch (m_primitiveType) + { + case PrimitiveType::Points: + primitiveType = GL_POINTS; + break; + case PrimitiveType::Lines: + primitiveType = GL_LINES; + break; + case PrimitiveType::LineLoop: + primitiveType = GL_LINE_LOOP; + break; + case PrimitiveType::LineStrip: + primitiveType = GL_LINE_STRIP; + break; + case PrimitiveType::Triangles: + primitiveType = GL_TRIANGLES; + break; + case PrimitiveType::TriangleStrip: + primitiveType = GL_TRIANGLE_STRIP; + break; + case PrimitiveType::TriangleFan: + primitiveType = GL_TRIANGLE_FAN; + break; + } + + glDrawElements(primitiveType, m_indices.Size(), GL_UNSIGNED_INT, nullptr); + + VertexArray::Unbind(); +} + +void Mesh::Initialize() +{ + m_vertexArray.Bind(); + + // Set up the attribute pointers for use in shaders + m_vertices.InitializeAttributePointer(0); + m_colors.InitializeAttributePointer(1); + m_textureUVs.InitializeAttributePointer(2); + + VertexBuffer::SetEnableAttributeArray(0, true); + + m_indices.Bind(); + + VertexArray::Unbind(); + VertexBuffer::Unbind(); +} + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/Mesh.hpp b/src/libprojectM/Renderer/Mesh.hpp new file mode 100644 index 0000000000..c18d89c875 --- /dev/null +++ b/src/libprojectM/Renderer/Mesh.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include "VertexArray.hpp" +#include "VertexIndexArray.hpp" + + +#include +#include +#include +#include +#include + +#include +#include + +namespace libprojectM { +namespace Renderer { + +/** + * @brief A 2D mesh class for drawing lines and polygons. + * + * It keeps separate buffers/streams for optional attributes like color and UV coordinates. + * + * Vertex attributes are always stored in the following location indices for use in vertex shaders: + * - 0: Vertex position (2 elements, X/Y) + * - 1: Color (4 elements, R/G/B/A) + * - 2: U/V coordinate (2 elements, U/V) + */ +class Mesh +{ +public: + enum class PrimitiveType : uint8_t + { + Points, + Lines, + LineStrip, + LineLoop, + Triangles, + TriangleStrip, + TriangleFan + }; + + Mesh(); + + explicit Mesh(VertexBufferUsage usage); + + Mesh(VertexBufferUsage usage, bool useColor, bool useTextureUVs); + + virtual ~Mesh() = default; + + auto RenderPrimitiveType() const -> PrimitiveType + { + return m_primitiveType; + } + + void SetRenderPrimitiveType(PrimitiveType primitiveType) + { + m_primitiveType = primitiveType; + } + + auto VertexCount() const -> uint32_t + { + return m_vertices.Size(); + } + + void SetVertexCount(uint32_t vertexCount); + + auto Vertices() const -> const VertexBuffer& + { + return m_vertices; + } + + auto Vertices() -> VertexBuffer& + { + return m_vertices; + } + + auto Vertex(uint32_t index) const -> const Point& + { + return m_vertices[index]; + } + + auto Vertex(uint32_t index) -> Point& + { + return m_vertices[index]; + } + + void SetVertex(uint32_t index, const Point& vertex) + { + m_vertices[index] = vertex; + } + + auto Colors() const -> const VertexBuffer& + { + return m_colors; + } + + auto Colors() -> VertexBuffer& + { + return m_colors; + } + + auto Color(uint32_t index) const -> const Color& + { + return m_colors[index]; + } + + auto Color(uint32_t index) -> class Color& + { + return m_colors[index]; + } + + auto UseColor() const -> bool + { + return m_useColorAttributes; + } + + void SetUseColor(bool useColor); + + void SetColor(uint32_t index, const class Color& vertex) + { + m_colors[index] = vertex; + } + + auto UVs() const -> const VertexBuffer& + { + return m_textureUVs; + } + + auto UVs() -> VertexBuffer& + { + return m_textureUVs; + } + + auto UV(uint32_t index) const -> const TextureUV& + { + return m_textureUVs[index]; + } + + auto UV(uint32_t index) -> TextureUV& + { + return m_textureUVs[index]; + } + + void SetUV(uint32_t index, const TextureUV& vertex) + { + m_textureUVs[index] = vertex; + } + + auto UseUV() const -> bool + { + return m_useUVAttributes; + } + + void SetUseUV(bool useUV); + + auto Indices() -> VertexIndexArray&; + + auto Indices() const -> const VertexIndexArray&; + + void Bind() const; + + static void Unbind(); + + void Update(); + + void Draw(); + +private: + void Initialize(); + + PrimitiveType m_primitiveType{PrimitiveType::Lines}; //!< Mesh render primitive type. + + bool m_useColorAttributes{false}; //!< If true, the color attribute array is enabled and populated. + bool m_useUVAttributes{false}; //!< If true, the UV attribute array is enabled and populated. + + VertexArray m_vertexArray; //!< The vertex array object used to store all data of the mesh. + + VertexBuffer m_vertices; //!< The vertex coordinates. + VertexBuffer m_colors; //!< Vertex color values. + VertexBuffer m_textureUVs; //!< Vertex texture coordinates. + + VertexIndexArray m_indices; //!< The vertex indices used for rendering. +}; + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/Point.hpp b/src/libprojectM/Renderer/Point.hpp new file mode 100644 index 0000000000..68aa613328 --- /dev/null +++ b/src/libprojectM/Renderer/Point.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include + +namespace libprojectM { +namespace Renderer { + +/** + * @brief A 2D point. + */ +class Point +{ +public: + Point() = default; + + /** + * Constructs a point with the given coordinates. + * @param x The X coordinate. + * @param y The Y coordinate. + */ + Point(float x, float y) + : m_x(x) + , m_y(y) {}; + + /** + * Returns the X coordinate of the point. + * @return The X coordinate of the point. + */ + auto X() const -> float + { + return m_x; + } + + /** + * Sets the X coordinate of the point. + * @param x The new X coordinate. + */ + void SetX(float x) + { + m_x = x; + } + + /** + * Returns the Y coordinate of the point. + * @return The Y coordinate of the point. + */ + auto Y() const -> float + { + return m_y; + } + + /** + * Sets the Y coordinate of the point. + * @param y The new Y coordinate. + */ + void SetY(float y) + { + m_y = y; + } + + static void InitializeAttributePointer(uint32_t attributeIndex) + { + glVertexAttribPointer(attributeIndex, sizeof(Point) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(Point), nullptr); + } + +private: + float m_x{}; //!< Vertex X coordinate. + float m_y{}; //!< Vertex Y coordinate. +}; + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/TextureUV.hpp b/src/libprojectM/Renderer/TextureUV.hpp new file mode 100644 index 0000000000..292b6e751a --- /dev/null +++ b/src/libprojectM/Renderer/TextureUV.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include + +namespace libprojectM { +namespace Renderer { + +/** + * @brief A texture U/V coordinate. + */ +class TextureUV +{ +public: + TextureUV() = default; + + /** + * Constructs a UV coordinate with the given U/V values. + * @param u The U coordinate. + * @param v The V coordinate. + */ + TextureUV(float u, float v) + : m_u(u) + , m_v(v) {}; + + /** + * Returns the U coordinate of the texture. + * @return The U coordinate of the texture. + */ + auto U() const -> float + { + return m_u; + } + + /** + * Sets the U coordinate of the texture. + * @param u The new U coordinate. + */ + void SetU(float u) + { + m_u = u; + } + + /** + * Returns the V coordinate of the texture. + * @return The V coordinate of the texture. + */ + auto V() const -> float + { + return m_v; + } + + /** + * Sets the V coordinate of the texture. + * @param v The new V coordinate. + */ + void SetV(float v) + { + m_v = v; + } + + static void InitializeAttributePointer(uint32_t attributeIndex) + { + glVertexAttribPointer(attributeIndex, sizeof(TextureUV) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(TextureUV), nullptr); + } + +private: + float m_u{}; //!< Texture U coordinate. + float m_v{}; //!< Texture V coordinate. +}; + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/VertexArray.hpp b/src/libprojectM/Renderer/VertexArray.hpp new file mode 100644 index 0000000000..ad507c809d --- /dev/null +++ b/src/libprojectM/Renderer/VertexArray.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "VertexBuffer.hpp" + + +#include + +namespace libprojectM { +namespace Renderer { + +class VertexArray +{ +public: + VertexArray() + { + glGenVertexArrays(1, &m_vaoID); + } + + virtual ~VertexArray() + { + glDeleteVertexArrays(1, &m_vaoID); + m_vaoID = 0; + } + + void Bind() const + { + glBindVertexArray(m_vaoID); + } + + static void Unbind() + { + glBindVertexArray(0); + } + +private: + GLuint m_vaoID{0}; //!< The vertex array object ID for this mesh's vertex data. + +}; + + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/VertexBuffer.hpp b/src/libprojectM/Renderer/VertexBuffer.hpp new file mode 100644 index 0000000000..472bd7221c --- /dev/null +++ b/src/libprojectM/Renderer/VertexBuffer.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include "Renderer/VertexBufferUsage.hpp" + +#include + +#include +#include + +namespace libprojectM { +namespace Renderer { + +template +class VertexBuffer +{ +public: + VertexBuffer(); + + explicit VertexBuffer(VertexBufferUsage usage); + + virtual ~VertexBuffer(); + + void Bind() const; + + static void Unbind(); + + void InitializeAttributePointer(uint32_t attributeIndex) const; + + static void SetEnableAttributeArray(uint32_t attributeIndex, bool enable); + + auto Get() -> std::vector&; + + auto Get() const -> const std::vector&; + + void Set(const std::vector& buffer); + + auto Vertex(size_t index) -> VT&; + + auto Vertex(size_t index) const -> const VT&; + + void SetVertex(size_t index, const VT& vertex); + + auto Empty() const -> bool; + + auto Size() const -> size_t; + + void Resize(size_t size); + + void Resize(size_t size, const VT& elem); + + + void Update(); + + auto operator[](size_t index) -> VT&; + + auto operator[](size_t index) const -> const VT&; + +private: + GLuint m_vboID{}; + size_t m_vboSize{}; + + VertexBufferUsage m_vboUsage{VertexBufferUsage::StaticDraw}; + + std::vector m_vertices; +}; + +template +VertexBuffer::VertexBuffer() +{ + glGenBuffers(1, &m_vboID); +} + +template +VertexBuffer::VertexBuffer(VertexBufferUsage usage) + : m_vboUsage(usage) +{ + glGenBuffers(1, &m_vboID); +} + +template +VertexBuffer::~VertexBuffer() +{ + glDeleteBuffers(1, &m_vboID); +} + +template +void VertexBuffer::Bind() const +{ + glBindBuffer(GL_ARRAY_BUFFER, m_vboID); +} + +template +void VertexBuffer::Unbind() +{ + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +template +void VertexBuffer::InitializeAttributePointer(uint32_t attributeIndex) const +{ + Bind(); + VT::InitializeAttributePointer(attributeIndex); +} + +template +void VertexBuffer::SetEnableAttributeArray(uint32_t attributeIndex, bool enable) +{ + if (enable) + { + glEnableVertexAttribArray(attributeIndex); + } + else + { + glDisableVertexAttribArray(attributeIndex); + } +} + +template +auto VertexBuffer::Get() -> std::vector& +{ + return m_vertices; +} + +template +auto VertexBuffer::Get() const -> const std::vector& +{ + return m_vertices; +} + +template +void VertexBuffer::Set(const std::vector& buffer) +{ + m_vertices = buffer; +} + +template +auto VertexBuffer::Vertex(size_t index) -> VT& +{ + return m_vertices.at(index); +} + +template +auto VertexBuffer::Vertex(size_t index) const -> const VT& +{ + return m_vertices.at(index); +} + +template +void VertexBuffer::SetVertex(size_t index, const VT& vertex) +{ + m_vertices.at(index) = vertex; +} + +template +auto VertexBuffer::Empty() const -> bool +{ + return m_vertices.empty(); +} + +template +auto VertexBuffer::Size() const -> size_t +{ + return m_vertices.size(); +} + +template +void VertexBuffer::Resize(size_t size) +{ + m_vertices.resize(size); +} + +template +void VertexBuffer::Resize(size_t size, const VT& elem) +{ + m_vertices.resize(size, elem); +} + +template +void VertexBuffer::Update() +{ + Bind(); + + if (m_vertices.empty()) + { + return; + } + + if (m_vboSize == m_vertices.size()) + { + glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(sizeof(VT) * m_vertices.size()), m_vertices.data()); + } + else + { + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(VT) * m_vertices.size()), m_vertices.data(), VertexBufferUsageToGL(m_vboUsage)); + m_vboSize = m_vertices.size(); + } +} + +template +auto VertexBuffer::operator[](size_t index) const -> const VT& +{ + return m_vertices.at(index); +} + +template +auto VertexBuffer::operator[](size_t index) -> VT& +{ + return m_vertices.at(index); +} + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/VertexBufferUsage.cpp b/src/libprojectM/Renderer/VertexBufferUsage.cpp new file mode 100644 index 0000000000..1ec00c9cb3 --- /dev/null +++ b/src/libprojectM/Renderer/VertexBufferUsage.cpp @@ -0,0 +1,42 @@ +#include "Renderer/VertexBufferUsage.hpp" + +namespace libprojectM { +namespace Renderer { + +GLuint VertexBufferUsageToGL(VertexBufferUsage usage) +{ + switch (usage) + { + case VertexBufferUsage::StreamDraw: + default: + return GL_STREAM_DRAW; + break; + case VertexBufferUsage::StreamRead: + return GL_STREAM_READ; + break; + case VertexBufferUsage::StreamCopy: + return GL_STREAM_COPY; + break; + case VertexBufferUsage::StaticDraw: + return GL_STATIC_DRAW; + break; + case VertexBufferUsage::StaticRead: + return GL_STATIC_READ; + break; + case VertexBufferUsage::StaticCopy: + return GL_STATIC_COPY; + break; + case VertexBufferUsage::DynamicDraw: + return GL_DYNAMIC_DRAW; + break; + case VertexBufferUsage::DynamicRead: + return GL_DYNAMIC_READ; + break; + case VertexBufferUsage::DynamicCopy: + return GL_DYNAMIC_COPY; + break; + } +} + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/VertexBufferUsage.hpp b/src/libprojectM/Renderer/VertexBufferUsage.hpp new file mode 100644 index 0000000000..9cf5d2131a --- /dev/null +++ b/src/libprojectM/Renderer/VertexBufferUsage.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace libprojectM { +namespace Renderer { + +enum class VertexBufferUsage : char +{ + StreamDraw, + StreamRead, + StreamCopy, + StaticDraw, + StaticRead, + StaticCopy, + DynamicDraw, + DynamicRead, + DynamicCopy +}; + +auto VertexBufferUsageToGL(VertexBufferUsage usage) -> GLuint; + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/VertexIndexArray.cpp b/src/libprojectM/Renderer/VertexIndexArray.cpp new file mode 100644 index 0000000000..b3104d54de --- /dev/null +++ b/src/libprojectM/Renderer/VertexIndexArray.cpp @@ -0,0 +1,115 @@ +#include "VertexIndexArray.hpp" + +namespace libprojectM { +namespace Renderer { + +VertexIndexArray::VertexIndexArray() +{ + glGenBuffers(1, &m_veabID); +} + +VertexIndexArray::~VertexIndexArray() +{ + glDeleteBuffers(1, &m_veabID); +} + +void VertexIndexArray::Bind() const +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_veabID); +} + +void VertexIndexArray::Unbind() +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +auto VertexIndexArray::Get() -> std::vector& +{ + return m_indices; +} + +auto VertexIndexArray::Get() const -> const std::vector& +{ + return m_indices; +} + +void VertexIndexArray::Set(const std::vector& buffer) +{ + m_indices = buffer; +} + +auto VertexIndexArray::VertexIndex(size_t index) -> uint32_t +{ + return m_indices.at(index); +} + +auto VertexIndexArray::VertexIndex(size_t index) const -> uint32_t +{ + return m_indices.at(index); +} + +void VertexIndexArray::SetVertexIndex(size_t index, uint32_t value) +{ + m_indices.at(index) = value; +} + +auto VertexIndexArray::Empty() const -> bool +{ + return m_indices.empty(); +} + +auto VertexIndexArray::Size() const -> size_t +{ + return m_indices.size(); +} + +void VertexIndexArray::Resize(size_t size) +{ + m_indices.resize(size); +} + +void VertexIndexArray::Resize(size_t size, uint32_t value) +{ + m_indices.resize(size, value); +} + +void VertexIndexArray::MakeContinuous() +{ + for (size_t index = 0; index < m_indices.size(); index++) + { + m_indices.at(index) = index; + } +} + +void VertexIndexArray::Update() +{ + Bind(); + + if (m_indices.empty()) + { + return; + } + + if (m_veabSize == m_indices.size()) + { + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(sizeof(uint32_t) * m_indices.size()), m_indices.data()); + } + else + { + glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast(sizeof(uint32_t) * m_indices.size()), m_indices.data(), VertexBufferUsageToGL(m_vboUsage)); + m_veabSize = m_indices.size(); + } +} + +auto VertexIndexArray::operator[](size_t index) -> uint32_t& +{ + return m_indices.at(index); +} + +auto VertexIndexArray::operator[](size_t index) const -> uint32_t +{ + return m_indices.at(index); +} + +} // namespace Renderer +} // namespace libprojectM diff --git a/src/libprojectM/Renderer/VertexIndexArray.hpp b/src/libprojectM/Renderer/VertexIndexArray.hpp new file mode 100644 index 0000000000..c129f5ebef --- /dev/null +++ b/src/libprojectM/Renderer/VertexIndexArray.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "Renderer/VertexBufferUsage.hpp" + +#include +#include + +namespace libprojectM { +namespace Renderer { + +class VertexIndexArray +{ +public: + VertexIndexArray(); + + virtual ~VertexIndexArray(); + + void Bind() const; + + static void Unbind(); + + auto Get() -> std::vector&; + + auto Get() const -> const std::vector&; + + void Set(const std::vector& buffer); + + auto VertexIndex(size_t index) -> uint32_t; + + auto VertexIndex(size_t index) const -> uint32_t; + + void SetVertexIndex(size_t index, uint32_t value); + + auto Empty() const -> bool; + + auto Size() const -> size_t; + + void Resize(size_t size); + + void Resize(size_t size, uint32_t value); + + void MakeContinuous(); + + void Update(); + + auto operator[](size_t index) -> uint32_t&; + + auto operator[](size_t index) const -> uint32_t; + +private: + GLuint m_veabID{}; + size_t m_veabSize{}; + + VertexBufferUsage m_vboUsage{VertexBufferUsage::StaticDraw}; + + std::vector m_indices; +}; + +} // namespace Renderer +} // namespace libprojectM From 01b77b43ae47a3313311d7e5194cd96ed3fc241d Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 02:43:02 +0200 Subject: [PATCH 02/19] Use Mesh class in Custom Wave effect --- .../MilkdropPreset/CustomWaveform.cpp | 115 +++++++++--------- .../MilkdropPreset/CustomWaveform.hpp | 23 ++-- 2 files changed, 64 insertions(+), 74 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp index 9f046d5335..eef31bbb6a 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp @@ -9,40 +9,29 @@ namespace libprojectM { namespace MilkdropPreset { -static constexpr int CustomWaveformMaxSamples = std::max(libprojectM::Audio::WaveformSamples, libprojectM::Audio::SpectrumSamples); +static constexpr int CustomWaveformMaxSamples = std::max(Audio::WaveformSamples, Audio::SpectrumSamples); CustomWaveform::CustomWaveform(PresetState& presetState) - : RenderItem() - , m_presetState(presetState) + : m_presetState(presetState) , m_perFrameContext(presetState.globalMemory, &presetState.globalRegisters) , m_perPointContext(presetState.globalMemory, &presetState.globalRegisters) + , m_mesh(Renderer::VertexBufferUsage::StreamDraw, true, false) { - RenderItem::Init(); - m_perFrameContext.RegisterBuiltinVariables(); m_perPointContext.RegisterBuiltinVariables(); -} - -void CustomWaveform::InitVertexAttrib() -{ - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ColoredPoint), nullptr); // points - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ColoredPoint), reinterpret_cast(sizeof(float) * 2)); // colors - - std::vector vertexData; - vertexData.resize(std::max(libprojectM::Audio::SpectrumSamples, libprojectM::Audio::WaveformSamples) * 2 + 2); - glBufferData(GL_ARRAY_BUFFER, sizeof(ColoredPoint) * vertexData.size(), vertexData.data(), GL_STREAM_DRAW); + // Allocate space for max number of vertices possible, so we won't have to resize the vertex + // buffers, which may change on each frame. + m_mesh.SetVertexCount(std::max(Audio::SpectrumSamples, Audio::WaveformSamples) * 2 + 2); } -void CustomWaveform::Initialize(::libprojectM::PresetFileParser& parsedFile, int index) +void CustomWaveform::Initialize(PresetFileParser& parsedFile, int index) { std::string const wavecodePrefix = "wavecode_" + std::to_string(index) + "_"; std::string const wavePrefix = "wave_" + std::to_string(index) + "_"; m_index = index; - m_enabled = parsedFile.GetInt(wavecodePrefix + "enabled", m_enabled); + m_enabled = parsedFile.GetBool(wavecodePrefix + "enabled", m_enabled); m_samples = parsedFile.GetInt(wavecodePrefix + "samples", m_samples); m_sep = parsedFile.GetInt(wavecodePrefix + "sep", m_sep); m_spectrum = parsedFile.GetBool(wavecodePrefix + "bSpectrum", m_spectrum); @@ -56,6 +45,7 @@ void CustomWaveform::Initialize(::libprojectM::PresetFileParser& parsedFile, int m_b = parsedFile.GetFloat(wavecodePrefix + "b", m_b); m_a = parsedFile.GetFloat(wavecodePrefix + "a", m_a); + m_mesh.SetRenderPrimitiveType(m_useDots ? Renderer::Mesh::PrimitiveType::Points : Renderer::Mesh::PrimitiveType::LineStrip); } void CustomWaveform::CompileCodeAndRunInitExpressions(const PerFrameContext& presetPerFrameContext) @@ -74,15 +64,15 @@ void CustomWaveform::CompileCodeAndRunInitExpressions(const PerFrameContext& pre void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) { - static_assert(libprojectM::Audio::WaveformSamples <= WaveformMaxPoints, "WaveformMaxPoints is larger than WaveformSamples"); - static_assert(libprojectM::Audio::SpectrumSamples <= WaveformMaxPoints, "WaveformMaxPoints is larger than SpectrumSamples"); + static_assert(Audio::WaveformSamples <= WaveformMaxPoints, "WaveformMaxPoints is larger than WaveformSamples"); + static_assert(Audio::SpectrumSamples <= WaveformMaxPoints, "WaveformMaxPoints is larger than SpectrumSamples"); if (!m_enabled) { return; } - int const maxSampleCount{m_spectrum ? libprojectM::Audio::SpectrumSamples : libprojectM::Audio::WaveformSamples}; + int const maxSampleCount{m_spectrum ? Audio::SpectrumSamples : Audio::WaveformSamples}; // Initialize and execute per-frame code LoadPerFrameEvaluationVariables(presetPerFrameContext); @@ -108,7 +98,6 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) : m_presetState.audioData.waveformRight.data(); const float mult = m_scaling * m_presetState.waveScale * (m_spectrum ? 0.15f : 0.004f); - //const float mult = m_scaling * m_presetState.waveScale * (m_spectrum ? 0.05f : 1.0f); // PCM data smoothing const int offset1 = m_spectrum ? 0 : (maxSampleCount - sampleCount) / 2 - m_sep / 2; @@ -144,7 +133,8 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) sampleDataR[sample] *= mult; } - std::vector pointsTransformed(sampleCount); + std::vector points(sampleCount); + std::vector colors(sampleCount); float const sampleMultiplicator = sampleCount > 1 ? 1.0f / static_cast(sampleCount - 1) : 0.0f; for (int sample = 0; sample < sampleCount; sample++) @@ -154,17 +144,16 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) m_perPointContext.ExecutePerPointCode(); - pointsTransformed[sample].x = static_cast((*m_perPointContext.x * 2.0 - 1.0) * m_presetState.renderContext.invAspectX); - pointsTransformed[sample].y = static_cast((*m_perPointContext.y * -2.0 + 1.0) * m_presetState.renderContext.invAspectY); + points[sample] = Renderer::Point(static_cast((*m_perPointContext.x * 2.0 - 1.0) * m_presetState.renderContext.invAspectX), + static_cast((*m_perPointContext.y * -2.0 + 1.0) * m_presetState.renderContext.invAspectY)); - pointsTransformed[sample].r = Renderer::color_modulo(*m_perPointContext.r); - pointsTransformed[sample].g = Renderer::color_modulo(*m_perPointContext.g); - pointsTransformed[sample].b = Renderer::color_modulo(*m_perPointContext.b); - pointsTransformed[sample].a = Renderer::color_modulo(*m_perPointContext.a); + colors[sample] = Renderer::Color::Modulo(Renderer::Color(static_cast(*m_perPointContext.r), + static_cast(*m_perPointContext.g), + static_cast(*m_perPointContext.b), + static_cast(*m_perPointContext.a))); } - std::vector pointsSmoothed(sampleCount * 2); - auto smoothedVertexCount = SmoothWave(pointsTransformed.data(), sampleCount, pointsSmoothed.data()); + SmoothWave(points, colors); #ifndef USE_GLES glDisable(GL_LINE_SMOOTH); @@ -192,10 +181,9 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) auto incrementX = 1.0f / static_cast(m_presetState.renderContext.viewportSizeX); auto incrementY = 1.0f / static_cast(m_presetState.renderContext.viewportSizeX); - GLuint drawType = m_useDots ? GL_POINTS : GL_LINE_STRIP; + size_t smoothedVertexCount = m_mesh.Indices().Size(); - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); + auto& vertices = m_mesh.Vertices(); // If thick outline is used, draw the shape four times with slight offsets // (top left, top right, bottom right, bottom left). @@ -208,34 +196,32 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) break; case 1: - for (auto j = 0; j < smoothedVertexCount; j++) + for (size_t j = 0; j < smoothedVertexCount; j++) { - pointsSmoothed[j].x += incrementX; + vertices[j].SetX(vertices[j].X() + incrementX); } break; case 2: - for (auto j = 0; j < smoothedVertexCount; j++) + for (size_t j = 0; j < smoothedVertexCount; j++) { - pointsSmoothed[j].y += incrementY; + vertices[j].SetY(vertices[j].Y() + incrementY); } break; case 3: - for (auto j = 0; j < smoothedVertexCount; j++) + for (size_t j = 0; j < smoothedVertexCount; j++) { - pointsSmoothed[j].x -= incrementX; + vertices[j].SetX(vertices[j].X() - incrementX); } break; } - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(ColoredPoint) * smoothedVertexCount, pointsSmoothed.data()); - glDrawArrays(drawType, 0, smoothedVertexCount); + m_mesh.Update(); + m_mesh.Draw(); } - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - + m_mesh.Unbind(); Renderer::Shader::Unbind(); glDisable(GL_BLEND); @@ -272,9 +258,7 @@ void CustomWaveform::LoadPerPointEvaluationVariables(float sample, float value1, *m_perPointContext.a = *m_perFrameContext.a; } -int CustomWaveform::SmoothWave(const CustomWaveform::ColoredPoint* inputVertices, - int vertexCount, - CustomWaveform::ColoredPoint* outputVertices) +void CustomWaveform::SmoothWave(const std::vector& points, const std::vector& colors) { constexpr float c1{-0.15f}; constexpr float c2{1.15f}; @@ -282,25 +266,36 @@ int CustomWaveform::SmoothWave(const CustomWaveform::ColoredPoint* inputVertices constexpr float c4{-0.15f}; constexpr float inverseSum{1.0f / (c1 + c2 + c3 + c4)}; - int outputIndex = 0; - int iBelow = 0; - int iAbove2 = 1; + size_t outputIndex = 0; + size_t iBelow = 0; + size_t iAbove2 = 1; + + size_t vertexCount = points.size(); + + auto& outVertices = m_mesh.Vertices(); + auto& outColors = m_mesh.Colors(); - for (auto inputIndex = 0; inputIndex < vertexCount - 1; inputIndex++) + for (size_t inputIndex = 0; inputIndex < vertexCount - 1; inputIndex++) { - int const iAbove = iAbove2; + size_t const iAbove = iAbove2; iAbove2 = std::min(vertexCount - 1, inputIndex + 2); - outputVertices[outputIndex] = inputVertices[inputIndex]; - outputVertices[outputIndex + 1] = inputVertices[inputIndex]; - outputVertices[outputIndex + 1].x = (c1 * inputVertices[iBelow].x + c2 * inputVertices[inputIndex].x + c3 * inputVertices[iAbove].x + c4 * inputVertices[iAbove2].x) * inverseSum; - outputVertices[outputIndex + 1].y = (c1 * inputVertices[iBelow].y + c2 * inputVertices[inputIndex].y + c3 * inputVertices[iAbove].y + c4 * inputVertices[iAbove2].y) * inverseSum; + outVertices[outputIndex] = points[inputIndex]; + outColors[outputIndex] = colors[inputIndex]; + outColors[outputIndex + 1] = colors[inputIndex]; + auto& smoothedPoint = outVertices[outputIndex + 1]; + smoothedPoint = points[inputIndex]; + smoothedPoint.SetX((c1 * points[iBelow].X() + c2 * points[inputIndex].X() + c3 * points[iAbove].X() + c4 * points[iAbove2].X()) * inverseSum); + smoothedPoint.SetY((c1 * points[iBelow].Y() + c2 * points[inputIndex].Y() + c3 * points[iAbove].Y() + c4 * points[iAbove2].Y()) * inverseSum); iBelow = inputIndex; outputIndex += 2; } - outputVertices[outputIndex] = inputVertices[vertexCount - 1]; + outVertices[outputIndex] = points[vertexCount - 1]; + outColors[outputIndex] = colors[vertexCount - 1]; - return outputIndex + 1; + auto& indices = m_mesh.Indices(); + indices.Resize(outputIndex + 1); + indices.MakeContinuous(); } } // namespace MilkdropPreset diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.hpp b/src/libprojectM/MilkdropPreset/CustomWaveform.hpp index 31d2bfae0c..e82687ae91 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.hpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.hpp @@ -3,7 +3,9 @@ #include "WaveformPerFrameContext.hpp" #include "WaveformPerPointContext.hpp" -#include +#include +#include +#include "Renderer/Point.hpp" #include @@ -13,7 +15,7 @@ class PresetFileParser; namespace MilkdropPreset { -class CustomWaveform : public Renderer::RenderItem +class CustomWaveform { public: @@ -23,11 +25,6 @@ class CustomWaveform : public Renderer::RenderItem */ explicit CustomWaveform(PresetState& presetState); - /** - * @brief Initializes the waveform's vertex buffer and attribute data. - */ - void InitVertexAttrib() override; - /** * @brief Loads the initial values and code from the preset file. * @param parsedFile The file parser with the preset data. @@ -73,15 +70,13 @@ class CustomWaveform : public Renderer::RenderItem * * Roughly doubles the number of points. * - * @param inputVertices Pointer to an array of vertices to be smoothed. - * @param vertexCount Number of vertices/points in the input data. - * @param outputVertices Pointer to a buffer that will receive the smoothed data. Must be able to hold 2 * vertexCount vertices. - * @return The number of vertices in outputVertices after smoothing. + * @param points A vector of points to be smoothed. + * @param colors A vector of colors for the points. */ - static int SmoothWave(const ColoredPoint* inputVertices, int vertexCount, ColoredPoint* outputVertices); + void SmoothWave(const std::vector& points, const std::vector& colors); int m_index{0}; //!< Custom waveform index in the preset. - int m_enabled{0}; //!< Render waveform if non-zero. + bool m_enabled{false}; //!< Render waveform if non-zero. int m_samples{WaveformMaxPoints}; //!< Number of samples/vertices in the waveform. int m_sep{0}; //!< Separation distance of dual waveforms. float m_scaling{1.0f}; //!< Scale factor of waveform. @@ -103,7 +98,7 @@ class CustomWaveform : public Renderer::RenderItem WaveformPerFrameContext m_perFrameContext; //!< Holds the code execution context for per-frame expressions WaveformPerPointContext m_perPointContext; //!< Holds the code execution context for per-point expressions - std::vector m_points; //!< Points in this waveform. + Renderer::Mesh m_mesh; //!< Points in this waveform. friend class WaveformPerFrameContext; friend class WaveformPerPointContext; From 524b7b1fd6898713ded5c8429c3a242572da1b6e Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 03:21:50 +0200 Subject: [PATCH 03/19] Use Mesh class in Custom Shape effect --- .../MilkdropPreset/CustomShape.cpp | 168 ++++++------------ .../MilkdropPreset/CustomShape.hpp | 22 +-- 2 files changed, 61 insertions(+), 129 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/CustomShape.cpp b/src/libprojectM/MilkdropPreset/CustomShape.cpp index 9eaef3784c..6a744f44c7 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.cpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.cpp @@ -3,7 +3,6 @@ #include "PresetFileParser.hpp" #include -#include #include @@ -11,67 +10,20 @@ namespace libprojectM { namespace MilkdropPreset { CustomShape::CustomShape(PresetState& presetState) - : m_presetState(presetState) + : m_outlineMesh(Renderer::VertexBufferUsage::StreamDraw) + , m_fillMesh(Renderer::VertexBufferUsage::StreamDraw, true, false) + , m_presetState(presetState) , m_perFrameContext(presetState.globalMemory, &presetState.globalRegisters) { - std::vector vertexData; - vertexData.resize(102); + m_outlineMesh.SetVertexCount(100); + m_outlineMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::LineLoop); - glGenVertexArrays(1, &m_vaoIdTextured); - glGenBuffers(1, &m_vboIdTextured); - - glGenVertexArrays(1, &m_vaoIdUntextured); - glGenBuffers(1, &m_vboIdUntextured); - - glBindVertexArray(m_vaoIdTextured); - glBindBuffer(GL_ARRAY_BUFFER, m_vboIdTextured); - - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, x))); // Position - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, r))); // Color - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, u))); // Texture coordinate - - glBufferData(GL_ARRAY_BUFFER, sizeof(TexturedPoint) * vertexData.size(), vertexData.data(), GL_STREAM_DRAW); - - glBindVertexArray(m_vaoIdUntextured); - glBindBuffer(GL_ARRAY_BUFFER, m_vboIdUntextured); - - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, x))); // Position - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, r))); // Color - - glBufferData(GL_ARRAY_BUFFER, sizeof(TexturedPoint) * vertexData.size(), vertexData.data(), GL_STREAM_DRAW); - - RenderItem::Init(); + m_fillMesh.SetVertexCount(102); + m_fillMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::TriangleFan); m_perFrameContext.RegisterBuiltinVariables(); } -CustomShape::~CustomShape() -{ - glDeleteBuffers(1, &m_vboIdTextured); - glDeleteVertexArrays(1, &m_vaoIdTextured); - - glDeleteBuffers(1, &m_vboIdUntextured); - glDeleteVertexArrays(1, &m_vaoIdUntextured); -} - -void CustomShape::InitVertexAttrib() -{ - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); // points - glDisableVertexAttribArray(1); - - std::vector vertexData; - vertexData.resize(100); - glBufferData(GL_ARRAY_BUFFER, sizeof(Point) * vertexData.size(), vertexData.data(), GL_STREAM_DRAW); -} - void CustomShape::Initialize(::libprojectM::PresetFileParser& parsedFile, int index) { std::string const shapecodePrefix = "shapecode_" + std::to_string(index) + "_"; @@ -148,13 +100,11 @@ void CustomShape::Draw() // Additive Drawing or Overwrite glBlendFunc(GL_SRC_ALPHA, static_cast(*m_perFrameContext.additive) != 0 ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA); - std::vector vertexData(sides + 2); - - vertexData[0].x = static_cast(*m_perFrameContext.x * 2.0 - 1.0); - vertexData[0].y = static_cast(*m_perFrameContext.y * -2.0 + 1.0); + auto& vertexData = m_fillMesh.Vertices(); + auto& colorData = m_fillMesh.Colors(); - vertexData[0].u = 0.5f; - vertexData[0].v = 0.5f; + vertexData[0] = Renderer::Point(static_cast(*m_perFrameContext.x * 2.0 - 1.0), + static_cast(*m_perFrameContext.y * -2.0 + 1.0)); // x = f*255.0 & 0xFF = (f*255.0) % 256 // f' = x/255.0 = f % (256/255) @@ -162,15 +112,15 @@ void CustomShape::Draw() // 2.0 -> 254 (0xFE) // -1.0 -> 0x01 - vertexData[0].r = Renderer::color_modulo(*m_perFrameContext.r); - vertexData[0].g = Renderer::color_modulo(*m_perFrameContext.g); - vertexData[0].b = Renderer::color_modulo(*m_perFrameContext.b); - vertexData[0].a = Renderer::color_modulo(*m_perFrameContext.a); + colorData[0] = Renderer::Color::Modulo(Renderer::Color(static_cast(*m_perFrameContext.r), + static_cast(*m_perFrameContext.g), + static_cast(*m_perFrameContext.b), + static_cast(*m_perFrameContext.a))); - vertexData[1].r = Renderer::color_modulo(*m_perFrameContext.r2); - vertexData[1].g = Renderer::color_modulo(*m_perFrameContext.g2); - vertexData[1].b = Renderer::color_modulo(*m_perFrameContext.b2); - vertexData[1].a = Renderer::color_modulo(*m_perFrameContext.a2); + colorData[1] = Renderer::Color::Modulo(Renderer::Color(static_cast(*m_perFrameContext.r2), + static_cast(*m_perFrameContext.g2), + static_cast(*m_perFrameContext.b2), + static_cast(*m_perFrameContext.a2))); for (int i = 1; i < sides + 1; i++) { @@ -178,19 +128,19 @@ void CustomShape::Draw() const float angle = cornerProgress * pi * 2.0f + static_cast(*m_perFrameContext.ang) + pi * 0.25f; // Todo: There's still some issue with aspect ratio here, as everything gets squashed horizontally if Y > x. - vertexData[i].x = vertexData[0].x + static_cast(*m_perFrameContext.rad) * cosf(angle) * m_presetState.renderContext.aspectY; - vertexData[i].y = vertexData[0].y + static_cast(*m_perFrameContext.rad) * sinf(angle); + vertexData[i] = Renderer::Point(vertexData[0].X() + static_cast(*m_perFrameContext.rad) * cosf(angle) * m_presetState.renderContext.aspectY, + vertexData[0].Y() + static_cast(*m_perFrameContext.rad) * sinf(angle)); - vertexData[i].r = vertexData[1].r; - vertexData[i].g = vertexData[1].g; - vertexData[i].b = vertexData[1].b; - vertexData[i].a = vertexData[1].a; + colorData[i] = colorData[1]; } // Duplicate last vertex. vertexData[sides + 1] = vertexData[1]; + colorData[sides + 1] = colorData[1]; - if (static_cast(*m_perFrameContext.textured) != 0) + m_fillMesh.SetUseUV(static_cast(*m_perFrameContext.textured) != 0); + + if (m_fillMesh.UseUV()) { auto shader = m_presetState.texturedShader.lock(); shader->Bind(); @@ -223,58 +173,55 @@ void CustomShape::Draw() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + auto& uvs = m_fillMesh.UVs(); + + uvs[0] = Renderer::TextureUV(0.5f, 0.5f); + for (int i = 1; i < sides + 1; i++) { const float cornerProgress = static_cast(i - 1) / static_cast(sides); const float angle = cornerProgress * pi * 2.0f + static_cast(*m_perFrameContext.tex_ang) + pi * 0.25f; - vertexData[i].u = 0.5f + 0.5f * cosf(angle) / static_cast(*m_perFrameContext.tex_zoom) * textureAspectY; - vertexData[i].v = 1.0f - (0.5f - 0.5f * sinf(angle) / static_cast(*m_perFrameContext.tex_zoom)); // Vertical flip required! + uvs[i] = Renderer::TextureUV(0.5f + 0.5f * cosf(angle) / static_cast(*m_perFrameContext.tex_zoom) * textureAspectY, + 1.0f - (0.5f - 0.5f * sinf(angle) / static_cast(*m_perFrameContext.tex_zoom))); // Vertical flip required! } - vertexData[sides + 1] = vertexData[1]; - - glBindBuffer(GL_ARRAY_BUFFER, m_vboIdTextured); - - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(TexturedPoint) * (sides + 2), vertexData.data()); - - glBindVertexArray(m_vaoIdTextured); - glDrawArrays(GL_TRIANGLE_FAN, 0, sides + 2); - glBindVertexArray(0); - - glBindTexture(GL_TEXTURE_2D, 0); - Renderer::Sampler::Unbind(0); + uvs[sides + 1] = uvs[1]; } else { // Untextured (creates a color gradient: center=r/g/b/a to border=r2/b2/g2/a2) - glBindBuffer(GL_ARRAY_BUFFER, m_vboIdUntextured); - - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(TexturedPoint) * (sides + 2), vertexData.data()); - auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - - glBindVertexArray(m_vaoIdUntextured); - glDrawArrays(GL_TRIANGLE_FAN, 0, sides + 2); - glBindVertexArray(0); } + m_fillMesh.Indices().Resize(sides + 2); + m_fillMesh.Indices().MakeContinuous(); + m_fillMesh.Update(); + m_fillMesh.Draw(); + + glBindTexture(GL_TEXTURE_2D, 0); + Renderer::Sampler::Unbind(0); + if (*m_perFrameContext.border_a > 0.0001f) { - std::vector points(sides); + m_outlineMesh.Indices().Resize(sides); + m_outlineMesh.Indices().MakeContinuous(); + + auto& points = m_outlineMesh.Vertices(); for (int i = 0; i < sides; i++) { - points[i].x = vertexData[i + 1].x; - points[i].y = vertexData[i + 1].y; + points[i] = m_fillMesh.Vertex(i + 1); } auto shader = m_presetState.untexturedShader.lock(); shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); + m_outlineMesh.Bind(); + glVertexAttrib4f(1, static_cast(*m_perFrameContext.border_r), static_cast(*m_perFrameContext.border_g), @@ -285,9 +232,6 @@ void CustomShape::Draw() glEnable(GL_LINE_SMOOTH); #endif - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - const auto iterations = m_thickOutline ? 4 : 1; // Need to use +/- 1.0 here instead of 2.0 used in Milkdrop to achieve the same rendering result. @@ -306,40 +250,38 @@ void CustomShape::Draw() case 1: for (auto j = 0; j < sides; j++) { - points[j].x += incrementX; + points[j].SetX(points[j].X() + incrementX); } break; case 2: for (auto j = 0; j < sides; j++) { - points[j].y += incrementY; + points[j].SetY(points[j].Y() + incrementY); } break; case 3: for (auto j = 0; j < sides; j++) { - points[j].x -= incrementX; + points[j].SetX(points[j].X() - incrementX); } break; } - glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(sizeof(Point) * sides), points.data()); - glDrawArrays(GL_LINE_LOOP, 0, sides); + m_outlineMesh.Update(); + m_outlineMesh.Draw(); } } } - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); + Renderer::Mesh::Unbind(); + Renderer::Shader::Unbind(); #ifndef USE_GLES glDisable(GL_LINE_SMOOTH); #endif glDisable(GL_BLEND); - - Renderer::Shader::Unbind(); } } // namespace MilkdropPreset diff --git a/src/libprojectM/MilkdropPreset/CustomShape.hpp b/src/libprojectM/MilkdropPreset/CustomShape.hpp index ea59d34e48..326790b2af 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.hpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.hpp @@ -4,7 +4,7 @@ #include "PresetState.hpp" #include "ShapePerFrameContext.hpp" -#include +#include #include @@ -19,14 +19,12 @@ class PresetFileParser; * The class creates two sets of VBO/VAO as it's only known later (in the Draw() call) whether the shape is textured * or not. */ -class CustomShape : public Renderer::RenderItem +class CustomShape { public: CustomShape(PresetState& presetState); - ~CustomShape() override; - - void InitVertexAttrib() override; + virtual ~CustomShape() = default; /** * @brief Loads the initial values and code from the preset file. @@ -47,12 +45,10 @@ class CustomShape : public Renderer::RenderItem void Draw(); private: - struct ShapeVertex { - float x{.0f}; //!< The vertex X coordinate. - float y{.0f}; //!< The vertex Y coordinate. - }; + Renderer::Mesh m_outlineMesh; //!< The shape's border/outline mesh. + Renderer::Mesh m_fillMesh; //!< The shape's color/texture mesh. - std::string m_image; //!< Texture filename to be rendered on this shape + std::string m_image; //!< Texture filename to be rendered on this shape. int m_index{0}; //!< The custom shape index in the preset. bool m_enabled{false}; //!< If false, the shape isn't drawn. @@ -93,12 +89,6 @@ class CustomShape : public Renderer::RenderItem PresetState& m_presetState; //!< The global preset state. ShapePerFrameContext m_perFrameContext; - GLuint m_vboIdTextured{0}; //!< Vertex buffer object ID for a textured shape. - GLuint m_vaoIdTextured{0}; //!< Vertex array object ID for a textured shape. - - GLuint m_vboIdUntextured{0}; //!< Vertex buffer object ID for an untextured shape. - GLuint m_vaoIdUntextured{0}; //!< Vertex array object ID for an untextured shape. - friend class ShapePerFrameContext; }; From 80bee18dae008d3c4d3684003ee8d33772f43030 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 15:39:54 +0200 Subject: [PATCH 04/19] Use Mesh class in Blur effect --- .../MilkdropPreset/BlurTexture.cpp | 48 +++++++------------ .../MilkdropPreset/BlurTexture.hpp | 8 ++-- .../Shaders/BlurVertexShaderGlsl330.vert | 2 +- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/BlurTexture.cpp b/src/libprojectM/MilkdropPreset/BlurTexture.cpp index 6cc33660a8..47c529144b 100644 --- a/src/libprojectM/MilkdropPreset/BlurTexture.cpp +++ b/src/libprojectM/MilkdropPreset/BlurTexture.cpp @@ -5,7 +5,8 @@ #include "MilkdropStaticShaders.hpp" -#include "Renderer/ShaderCache.hpp" +#include +#include #include @@ -13,33 +14,25 @@ namespace libprojectM { namespace MilkdropPreset { BlurTexture::BlurTexture() - : m_blurSampler(std::make_shared(GL_CLAMP_TO_EDGE, GL_LINEAR)) + : m_blurMesh(Renderer::VertexBufferUsage::StaticDraw, false, true) + , m_blurSampler(std::make_shared(GL_CLAMP_TO_EDGE, GL_LINEAR)) { m_blurFramebuffer.CreateColorAttachment(0, 0); - // Initialize Blur VAO/VBO with a single fullscreen quad. - static constexpr std::array pointsBlur{ - -1.0, -1.0, 0.0, 0.0, - 1.0, -1.0, 1.0, 0.0, - -1.0, 1.0, 0.0, 1.0, - 1.0, 1.0, 1.0, 1.0}; + // Initialize blur mesh with a single fullscreen quad. + m_blurMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::TriangleStrip); - glGenBuffers(1, &m_vboBlur); - glGenVertexArrays(1, &m_vaoBlur); + m_blurMesh.Vertices().Set({{-1.0f, -1.0f}, + {1.0f, -1.0f}, + {-1.0f, 1.0f}, + {1.0f, 1.0f}}); - glBindVertexArray(m_vaoBlur); - glBindBuffer(GL_ARRAY_BUFFER, m_vboBlur); + m_blurMesh.UVs().Set({{0.0, 0.0}, + {1.0, 0.0}, + {0.0, 1.0}, + {1.0, 1.0}}); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * pointsBlur.size(), pointsBlur.data(), GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, nullptr); // Position at index 0 and 1 - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, reinterpret_cast(sizeof(float) * 2)); // Texture coord at index 2 and 3 - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + m_blurMesh.Update(); // Initialize with empty textures. for (size_t i = 0; i < m_blurTextures.size(); i++) @@ -54,12 +47,6 @@ BlurTexture::BlurTexture() } } -BlurTexture::~BlurTexture() -{ - glDeleteBuffers(1, &m_vboBlur); - glDeleteVertexArrays(1, &m_vaoBlur); -} - void BlurTexture::Initialize(const Renderer::RenderContext& renderContext) { auto staticShaders = libprojectM::MilkdropPreset::MilkdropStaticShaders::Get(); @@ -164,7 +151,6 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC m_blurFramebuffer.Bind(0); glBlendFunc(GL_ONE, GL_ZERO); - glBindVertexArray(m_vaoBlur); for (unsigned int pass = 0; pass < passes; pass++) { @@ -268,7 +254,7 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC } // Draw fullscreen quad - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + m_blurMesh.Draw(); // Save to blur texture m_blurTextures[pass]->Bind(0); @@ -276,7 +262,7 @@ void BlurTexture::Update(const Renderer::Texture& sourceTexture, const PerFrameC m_blurTextures[pass]->Unbind(0); } - glBindVertexArray(0); + Renderer::Mesh::Unbind(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Bind previous framebuffer and reset viewport size diff --git a/src/libprojectM/MilkdropPreset/BlurTexture.hpp b/src/libprojectM/MilkdropPreset/BlurTexture.hpp index edf63660ef..04647a5a80 100644 --- a/src/libprojectM/MilkdropPreset/BlurTexture.hpp +++ b/src/libprojectM/MilkdropPreset/BlurTexture.hpp @@ -4,6 +4,9 @@ */ #pragma once +#include "Renderer/Mesh.hpp" + + #include #include #include @@ -48,7 +51,7 @@ class BlurTexture /** * Destructor. */ - ~BlurTexture(); + virtual ~BlurTexture() = default; /** * @brief Initializes the blur texture. @@ -105,8 +108,7 @@ class BlurTexture */ void AllocateTextures(const Renderer::Texture& sourceTexture); - GLuint m_vboBlur; //!< Vertex buffer object for the fullscreen blur quad. - GLuint m_vaoBlur; //!< Vertex array object for the fullscreen blur quad. + Renderer::Mesh m_blurMesh; //!< The blur mesh (a simple quad). std::weak_ptr m_blur1Shader; //!< The shader used on the first blur pass. std::weak_ptr m_blur2Shader; //!< The shader used for subsequent blur passes after the initial pass. diff --git a/src/libprojectM/MilkdropPreset/Shaders/BlurVertexShaderGlsl330.vert b/src/libprojectM/MilkdropPreset/Shaders/BlurVertexShaderGlsl330.vert index 38bdeacc20..e31d4253e5 100644 --- a/src/libprojectM/MilkdropPreset/Shaders/BlurVertexShaderGlsl330.vert +++ b/src/libprojectM/MilkdropPreset/Shaders/BlurVertexShaderGlsl330.vert @@ -1,7 +1,7 @@ precision mediump float; layout(location = 0) in vec2 vertex_position; -layout(location = 1) in vec2 vertex_texture; +layout(location = 2) in vec2 vertex_texture; uniform int flipVertical; From 69fd2e03345bfefcafe9459925010c5e47c92239 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 16:08:14 +0200 Subject: [PATCH 05/19] Use Mesh class in Border effect Reduced draw calls from 4/8 to 1/2 by specifying 8 vertices accessed via indices to draw each border in a single call. --- src/libprojectM/MilkdropPreset/Border.cpp | 72 +++++++++-------------- src/libprojectM/MilkdropPreset/Border.hpp | 7 +-- 2 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/Border.cpp b/src/libprojectM/MilkdropPreset/Border.cpp index 2cebe2a541..0a37953153 100644 --- a/src/libprojectM/MilkdropPreset/Border.cpp +++ b/src/libprojectM/MilkdropPreset/Border.cpp @@ -4,20 +4,23 @@ namespace libprojectM { namespace MilkdropPreset { Border::Border(PresetState& presetState) - : RenderItem() - , m_presetState(presetState) + : m_presetState(presetState) + , m_borderMesh(Renderer::VertexBufferUsage::StreamDraw) { - RenderItem::Init(); -} - -void Border::InitVertexAttrib() -{ - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - glDisableVertexAttribArray(1); + m_borderMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::Triangles); + m_borderMesh.SetVertexCount(8); - std::array vertices{}; - glBufferData(GL_ARRAY_BUFFER, sizeof(Point) * 4, vertices.data(), GL_STREAM_DRAW); + m_borderMesh.Indices().Set({ + { + 0, 1, 4, + 1, 4, 5, + 2, 3, 6, + 3, 7, 6, + 2, 0, 6, + 0, 4, 6, + 3, 7, 5, + 1, 3, 5 + }}); } void Border::Draw(const PerFrameContext& presetPerFrameContext) @@ -26,9 +29,6 @@ void Border::Draw(const PerFrameContext& presetPerFrameContext) float const outerBorderSize = static_cast(*presetPerFrameContext.ob_size); float const innerBorderSize = static_cast(*presetPerFrameContext.ib_size); - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - // No additive drawing for borders glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -37,7 +37,8 @@ void Border::Draw(const PerFrameContext& presetPerFrameContext) shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - std::array vertices{}; + m_borderMesh.Bind(); + for (int border = 0; border < 2; border++) { float r = (border == 0) ? static_cast(*presetPerFrameContext.ob_r) : static_cast(*presetPerFrameContext.ib_r); @@ -52,39 +53,24 @@ void Border::Draw(const PerFrameContext& presetPerFrameContext) float innerRadius = (border == 0) ? 1.0f - outerBorderSize : 1.0f - outerBorderSize - innerBorderSize; float outerRadius = (border == 0) ? 1.0f : 1.0f - outerBorderSize; - vertices[0].x = innerRadius; - vertices[1].x = outerRadius; - vertices[2].x = outerRadius; - vertices[3].x = innerRadius; - vertices[0].y = innerRadius; - vertices[1].y = outerRadius; - vertices[2].y = -outerRadius; - vertices[3].y = -innerRadius; - - for (int rot = 0; rot < 4; rot++) - { - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Point) * 4, vertices.data()); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - - // Rotate 90 degrees - // Milkdrop code calculates cos(PI/2) and sin(PI/2), which is 0 and 1 respectively. - // Our code here simplifies the expressions accordingly. - for (int vertex = 0; vertex < 4; vertex++) - { - float const x = vertices[vertex].x; - float const y = vertices[vertex].y; - vertices[vertex].x = -y; // x * cos(PI/2) - y * sin(PI/2) == x * 0 - y * 1 - vertices[vertex].y = x; // x * sin(PI/2) + y * cos(PI/2) == x * 1 + y * 0 - } - } + m_borderMesh.Vertices().Set({{outerRadius, outerRadius}, + {outerRadius, -outerRadius}, + {-outerRadius, outerRadius}, + {-outerRadius, -outerRadius}, + {innerRadius, innerRadius}, + {innerRadius, -innerRadius}, + {-innerRadius, innerRadius}, + {-innerRadius, -innerRadius}}); + + m_borderMesh.Update(); + m_borderMesh.Draw(); } } + Renderer::Mesh::Unbind(); Renderer::Shader::Unbind(); glDisable(GL_BLEND); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); } } // namespace MilkdropPreset diff --git a/src/libprojectM/MilkdropPreset/Border.hpp b/src/libprojectM/MilkdropPreset/Border.hpp index 8b258f6d08..b8f8f7eeb8 100644 --- a/src/libprojectM/MilkdropPreset/Border.hpp +++ b/src/libprojectM/MilkdropPreset/Border.hpp @@ -3,7 +3,7 @@ #include "PerFrameContext.hpp" #include "PresetState.hpp" -#include "Renderer/RenderItem.hpp" +#include namespace libprojectM { namespace MilkdropPreset { @@ -12,15 +12,13 @@ namespace MilkdropPreset { /** * @brief Renders a border around the screen. */ -class Border : public Renderer::RenderItem +class Border { public: Border() = delete; explicit Border(PresetState& presetState); - void InitVertexAttrib() override; - /** * Draws the border. * @param presetPerFrameContext The per-frame context variables. @@ -29,6 +27,7 @@ class Border : public Renderer::RenderItem private: PresetState& m_presetState; //!< The global preset state. + Renderer::Mesh m_borderMesh; //!< The border geometry. }; } // namespace MilkdropPreset From b36276a3b25281c79d031f6fb35f4b3f83a49e02 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 16:27:51 +0200 Subject: [PATCH 06/19] Use Mesh class in Darken Center effect --- .../MilkdropPreset/DarkenCenter.cpp | 48 ++++++++----------- .../MilkdropPreset/DarkenCenter.hpp | 9 ++-- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/DarkenCenter.cpp b/src/libprojectM/MilkdropPreset/DarkenCenter.cpp index 64899b3242..752e9b086f 100644 --- a/src/libprojectM/MilkdropPreset/DarkenCenter.cpp +++ b/src/libprojectM/MilkdropPreset/DarkenCenter.cpp @@ -4,41 +4,35 @@ namespace libprojectM { namespace MilkdropPreset { DarkenCenter::DarkenCenter(PresetState& presetState) - : RenderItem() - , m_presetState(presetState) + : m_presetState(presetState) + , m_mesh(Renderer::VertexBufferUsage::StaticDraw, true, false) { - RenderItem::Init(); -} - -void DarkenCenter::InitVertexAttrib() -{ - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ColoredPoint), nullptr); // points - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(ColoredPoint), reinterpret_cast(offsetof(ColoredPoint, r))); // colors + m_mesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::TriangleFan); + m_mesh.SetVertexCount(6); + m_mesh.Colors().Set({{0.0f, 0.0f, 0.0f, 3.0f / 32.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}}); + m_mesh.Update(); } void DarkenCenter::Draw() { - glBindVertexArray(m_vaoID); - if (m_presetState.renderContext.aspectY != m_aspectY) { m_aspectY = m_presetState.renderContext.aspectY; // Update mesh with new aspect ratio if needed - float const halfSize = 0.05f; - std::array vertices = {{{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 3.0f / 32.0f}, - {0.0f - halfSize * m_aspectY, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, - {0.0f, 0.0f - halfSize, 0.0f, 0.0f, 0.0f, 0.0f}, - {0.0f + halfSize * m_aspectY, 0.0, 0.0f, 0.0f, 0.0f, 0.0f}, - {0.0f, 0.0f + halfSize, 0.0f, 0.0f, 0.0f, 0.0f}, - {0.0f - halfSize * m_aspectY, 0.0, 0.0f, 0.0f, 0.0f, 0.0f}}}; - - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - glBufferData(GL_ARRAY_BUFFER, sizeof(ColoredPoint) * vertices.size(), vertices.data(), GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); + constexpr float halfSize = 0.05f; + m_mesh.Vertices().Set({{0.0f, 0.0f}, + {0.0f - halfSize * m_aspectY, 0.0f}, + {0.0f, 0.0f - halfSize}, + {0.0f + halfSize * m_aspectY, 0.0f}, + {0.0f, 0.0f + halfSize}, + {0.0f - halfSize * m_aspectY, 0.0f}}); + m_mesh.Update(); } glEnable(GL_BLEND); @@ -48,10 +42,10 @@ void DarkenCenter::Draw() shader->Bind(); shader->SetUniformMat4x4("vertex_transformation", PresetState::orthogonalProjection); - glDrawArrays(GL_TRIANGLE_FAN, 0, 6); + m_mesh.Draw(); glDisable(GL_BLEND); - glBindVertexArray(0); + Renderer::Mesh::Unbind(); Renderer::Shader::Unbind(); } diff --git a/src/libprojectM/MilkdropPreset/DarkenCenter.hpp b/src/libprojectM/MilkdropPreset/DarkenCenter.hpp index 3f04012f7b..b6921d1a07 100644 --- a/src/libprojectM/MilkdropPreset/DarkenCenter.hpp +++ b/src/libprojectM/MilkdropPreset/DarkenCenter.hpp @@ -2,7 +2,7 @@ #include "PresetState.hpp" -#include +#include namespace libprojectM { namespace MilkdropPreset { @@ -10,15 +10,13 @@ namespace MilkdropPreset { /** * @brief Darkens the screen center a bit on each frame. */ -class DarkenCenter : public Renderer::RenderItem +class DarkenCenter { public: DarkenCenter() = delete; explicit DarkenCenter(PresetState& presetState); - void InitVertexAttrib(); - /** * Applies the darkening area. */ @@ -26,7 +24,8 @@ class DarkenCenter : public Renderer::RenderItem private: PresetState& m_presetState; //!< The global preset state. - float m_aspectY{}; //!< Previous Y aspect ration. + Renderer::Mesh m_mesh; //!< The "diamond" mesh. + float m_aspectY{}; //!< Previous Y aspect ratio. }; } // namespace MilkdropPreset From c77e98c9456f7a3467864f7c9b04dbf2477cd607 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 17:16:09 +0200 Subject: [PATCH 07/19] Use Mesh class in Final Composite effect --- .../MilkdropPreset/FinalComposite.cpp | 117 +++++++++--------- .../MilkdropPreset/FinalComposite.hpp | 12 +- 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/FinalComposite.cpp b/src/libprojectM/MilkdropPreset/FinalComposite.cpp index 549c950437..974cf2cdbd 100644 --- a/src/libprojectM/MilkdropPreset/FinalComposite.cpp +++ b/src/libprojectM/MilkdropPreset/FinalComposite.cpp @@ -14,28 +14,23 @@ namespace MilkdropPreset { static std::string const defaultCompositeShader = "shader_body\n{\nret = tex2D(sampler_main, uv).xyz;\n}"; FinalComposite::FinalComposite() + : m_compositeMesh(Renderer::VertexBufferUsage::StreamDraw, true, true) { - RenderItem::Init(); -} - -void FinalComposite::InitVertexAttrib() -{ - glGenBuffers(1, &m_elementBuffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBuffer); + m_compositeMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::Triangles); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - glEnableVertexAttribArray(3); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), nullptr); // Positions - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, r))); // Colors - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, u))); // Textures - glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, radius))); // Radius/Angle + // Add attribute array for radius and angle information to the mesh. + m_compositeMesh.Bind(); + m_radiusAngle.Bind(); + m_radiusAngle.Resize(vertexCount); + m_radiusAngle.InitializeAttributePointer(3); + Renderer::VertexBuffer::SetEnableAttributeArray(3, true); // Pre-allocate vertex and index buffers - glBufferData(GL_ARRAY_BUFFER, sizeof(MeshVertex) * vertexCount, m_vertices.data(), GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * m_indices.size(), m_indices.data(), GL_STREAM_DRAW); + m_compositeMesh.SetVertexCount(vertexCount); + m_compositeMesh.Indices().Resize(indexCount); + m_compositeMesh.Update(); + + Renderer::Mesh::Unbind(); } void FinalComposite::LoadCompositeShader(const PresetState& presetState) @@ -58,7 +53,7 @@ void FinalComposite::LoadCompositeShader(const PresetState& presetState) std::cerr << "[Composite Shader] Error loading composite warp shader code:" << ex.message() << std::endl; std::cerr << "[Composite Shader] Using fallback shader." << std::endl; #else - (void)ex; // silence unused parameter warning + (void) ex; // silence unused parameter warning #endif // Fall back to default shader m_compositeShader = std::make_unique(MilkdropShader::ShaderType::CompositeShader); @@ -104,7 +99,7 @@ void FinalComposite::CompileCompositeShader(PresetState& presetState) std::cerr << "[Composite Shader] Error compiling composite warp shader code:" << ex.message() << std::endl; std::cerr << "[Composite Shader] Using fallback shader." << std::endl; #else - (void)ex; // silence unused parameter warning + (void) ex; // silence unused parameter warning #endif // Fall back to default shader m_compositeShader = std::make_unique(MilkdropShader::ShaderType::CompositeShader); @@ -123,14 +118,10 @@ void FinalComposite::Draw(const PresetState& presetState, const PerFrameContext& // Render the grid glDisable(GL_BLEND); - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(MeshVertex) * vertexCount, m_vertices.data()); - glBindBuffer(GL_ARRAY_BUFFER, 0); - m_compositeShader->LoadVariables(presetState, perFrameContext); - glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr); + m_compositeMesh.Draw(); + Renderer::Mesh::Unbind(); } else { @@ -142,7 +133,6 @@ void FinalComposite::Draw(const PresetState& presetState, const PerFrameContext& } } - glBindVertexArray(0); Renderer::Shader::Unbind(); } @@ -167,6 +157,9 @@ void FinalComposite::InitializeMesh(const PresetState& presetState) constexpr float PI = 3.1415926535898f; + auto& vertices = m_compositeMesh.Vertices(); + auto& uvs = m_compositeMesh.UVs(); + for (int gridY = 0; gridY < compositeGridHeight; gridY++) { int const gridY2 = gridY - gridY / (compositeGridHeight / 2); @@ -179,13 +172,12 @@ void FinalComposite::InitializeMesh(const PresetState& presetState) float const u = SquishToCenter(gridX2 * dividedByX, 3.0f); float const sx = u * 2.0f - 1.0f; - auto& vertex = m_vertices.at(gridX + gridY * compositeGridWidth); + const size_t vertexIndex = gridX + gridY * compositeGridWidth; - vertex.x = sx; - vertex.y = sy; + vertices[vertexIndex] = {sx, sy}; - float rad; - float ang; + float rad{}; + float ang{}; UvToMathSpace(presetState.renderContext.aspectX, presetState.renderContext.aspectY, u, v, rad, ang); @@ -251,16 +243,16 @@ void FinalComposite::InitializeMesh(const PresetState& presetState) ang = PI * 0.0f; } } - vertex.u = u + halfTexelWidth; - vertex.v = v + halfTexelHeight; + uvs[vertexIndex] = {u + halfTexelWidth, + v + halfTexelHeight}; - vertex.radius = rad; - vertex.angle = ang; + m_radiusAngle[vertexIndex] = {rad, ang}; } } // build index list for final composite blit - // order should be friendly for interpolation of 'ang' value! + auto& indices = m_compositeMesh.Indices(); int currentIndex = 0; for (int gridY = 0; gridY < compositeGridHeight - 1; gridY++) { @@ -283,32 +275,30 @@ void FinalComposite::InitializeMesh(const PresetState& presetState) if ((static_cast(leftHalf) + static_cast(topHalf) + static_cast(center4)) % 2 == 1) { - m_indices[currentIndex + 0] = gridY * compositeGridWidth + gridX; - m_indices[currentIndex + 1] = gridY * compositeGridWidth + gridX + 1; - m_indices[currentIndex + 2] = (gridY + 1) * compositeGridWidth + gridX + 1; - m_indices[currentIndex + 3] = (gridY + 1) * compositeGridWidth + gridX + 1; - m_indices[currentIndex + 4] = (gridY + 1) * compositeGridWidth + gridX; - m_indices[currentIndex + 5] = gridY * compositeGridWidth + gridX; + indices[currentIndex + 0] = gridY * compositeGridWidth + gridX; + indices[currentIndex + 1] = gridY * compositeGridWidth + gridX + 1; + indices[currentIndex + 2] = (gridY + 1) * compositeGridWidth + gridX + 1; + indices[currentIndex + 3] = (gridY + 1) * compositeGridWidth + gridX + 1; + indices[currentIndex + 4] = (gridY + 1) * compositeGridWidth + gridX; + indices[currentIndex + 5] = gridY * compositeGridWidth + gridX; } else { - m_indices[currentIndex + 0] = (gridY + 1) * compositeGridWidth + (gridX); - m_indices[currentIndex + 1] = (gridY) *compositeGridWidth + (gridX); - m_indices[currentIndex + 2] = (gridY) *compositeGridWidth + (gridX + 1); - m_indices[currentIndex + 3] = (gridY) *compositeGridWidth + (gridX + 1); - m_indices[currentIndex + 4] = (gridY + 1) * compositeGridWidth + (gridX + 1); - m_indices[currentIndex + 5] = (gridY + 1) * compositeGridWidth + (gridX); + indices[currentIndex + 0] = (gridY + 1) * compositeGridWidth + (gridX); + indices[currentIndex + 1] = (gridY) *compositeGridWidth + (gridX); + indices[currentIndex + 2] = (gridY) *compositeGridWidth + (gridX + 1); + indices[currentIndex + 3] = (gridY) *compositeGridWidth + (gridX + 1); + indices[currentIndex + 4] = (gridY + 1) * compositeGridWidth + (gridX + 1); + indices[currentIndex + 5] = (gridY + 1) * compositeGridWidth + (gridX); } currentIndex += 6; } } - // Store indices. - // ToDo: Probably don't need to store m_indices - glBindVertexArray(m_vaoID); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(int) * m_indices.size(), m_indices.data()); - glBindVertexArray(0); + // Update mesh geometry and indices. + m_compositeMesh.Update(); + m_radiusAngle.Update(); } float FinalComposite::SquishToCenter(float x, float exponent) @@ -366,13 +356,16 @@ void FinalComposite::ApplyHueShaderColors(const PresetState& presetState) } // Interpolate and apply to all grid vertices. + auto& vertices = m_compositeMesh.Vertices(); + auto& colors = m_compositeMesh.Colors(); + for (int gridY = 0; gridY < compositeGridHeight; gridY++) { for (int gridX = 0; gridX < compositeGridWidth; gridX++) { - auto& vertex = m_vertices[gridX + gridY * compositeGridWidth]; - float x = vertex.x * 0.5f + 0.5f; - float y = vertex.y * 0.5f + 0.5f; + auto vertexIndex = gridX + gridY * compositeGridWidth; + float x = vertices[vertexIndex].X() * 0.5f + 0.5f; + float y = vertices[vertexIndex].Y() * 0.5f + 0.5f; std::array color{{1.0f, 1.0f, 1.0f}}; for (int col = 0; col < 3; col++) @@ -383,12 +376,16 @@ void FinalComposite::ApplyHueShaderColors(const PresetState& presetState) shade[3][col] * (1 - x) * (1 - y); } - vertex.r = color[0]; - vertex.g = color[1]; - vertex.b = color[2]; - vertex.a = 1.0f; + colors[vertexIndex] = {color[0], + color[1], + color[2], + 1.0f}; } } + + // Only update color buffer. + m_compositeMesh.Bind(); + m_compositeMesh.Colors().Update(); } } // namespace MilkdropPreset diff --git a/src/libprojectM/MilkdropPreset/FinalComposite.hpp b/src/libprojectM/MilkdropPreset/FinalComposite.hpp index b9cf50b400..8bbf9706b7 100644 --- a/src/libprojectM/MilkdropPreset/FinalComposite.hpp +++ b/src/libprojectM/MilkdropPreset/FinalComposite.hpp @@ -4,9 +4,8 @@ #include "MilkdropShader.hpp" #include "VideoEcho.hpp" -#include +#include -#include #include namespace libprojectM { @@ -15,13 +14,11 @@ namespace MilkdropPreset { /** * @brief Draws the final composite effect, either a shader or Milkdrop 1 effects. */ -class FinalComposite : public Renderer::RenderItem +class FinalComposite { public: FinalComposite(); - void InitVertexAttrib() override; - /** * @brief Loads the composite shader, if the preset uses one. * @param presetState The preset state to retrieve the shader from. @@ -90,9 +87,8 @@ class FinalComposite : public Renderer::RenderItem static constexpr int vertexCount{compositeGridWidth * compositeGridHeight}; static constexpr int indexCount{(compositeGridWidth - 2) * (compositeGridHeight - 2) * 6}; - GLuint m_elementBuffer{}; //!< Element buffer holding the draw indices. - std::array m_vertices{}; //!< Composite grid vertices - std::array m_indices{}; //!< Composite grid draw indices + Renderer::Mesh m_compositeMesh; //!< The composite shader mesh. + Renderer::VertexBuffer m_radiusAngle; //!< Additional vertex attribute array for radius and angle. int m_viewportWidth{}; //!< Last known viewport width. int m_viewportHeight{}; //!< Last known viewport height. From f61fb572be803b2c2e1c30d6110f5932e0c857b2 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 17:42:07 +0200 Subject: [PATCH 08/19] Use Mesh class in Motion Vector effect Also get rid of the additional index attribute array, as gl_VertexID contains the same information and is free. --- .../MilkdropPreset/MotionVectors.cpp | 68 ++++--------------- .../MilkdropPreset/MotionVectors.hpp | 25 +++---- ...resetMotionVectorsVertexShaderGlsl330.vert | 3 +- 3 files changed, 24 insertions(+), 72 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.cpp b/src/libprojectM/MilkdropPreset/MotionVectors.cpp index 3ec34e17e6..c2a5a26ba6 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.cpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.cpp @@ -5,24 +5,16 @@ #include #include +#include + namespace libprojectM { namespace MilkdropPreset { MotionVectors::MotionVectors(PresetState& presetState) - : RenderItem() - , m_presetState(presetState) + : m_presetState(presetState) + , m_motionVectorMesh(Renderer::VertexBufferUsage::StreamDraw) { - RenderItem::Init(); -} - -void MotionVectors::InitVertexAttrib() -{ - glEnableVertexAttribArray(0); - glDisableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(MotionVectorVertex), reinterpret_cast(offsetof(MotionVectorVertex, x))); - glVertexAttribIPointer(2, 1, GL_INT, sizeof(MotionVectorVertex), reinterpret_cast(offsetof(MotionVectorVertex, index))); + m_motionVectorMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::Lines); } void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr motionTexture) @@ -59,22 +51,8 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shar auto const divertY2 = static_cast(*presetPerFrameContext.mv_dy); // Clamp X/Y diversions to 0..1 - if (divertX < 0.0f) - { - divertX = 0.0f; - } - if (divertX > 1.0f) - { - divertX = 1.0f; - } - if (divertY < 0.0f) - { - divertY = 0.0f; - } - if (divertY > 1.0f) - { - divertY = 1.0f; - } + divertX = std::min(1.0f, std::max(0.0f, divertX)); + divertY = std::min(1.0f, std::max(0.0f, divertY)); // Tweaked this a bit to ensure lines are always at least a bit more than 1px long. // Line smoothing makes some of them disappear otherwise. @@ -82,7 +60,7 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shar float const inverseHeight = 1.25f / static_cast(m_presetState.renderContext.viewportSizeY); float const minimumLength = sqrtf(inverseWidth * inverseWidth + inverseHeight * inverseHeight); - std::vector lineVertices(static_cast(countX + 1) * 2); // countX + 1 lines for each grid row, 2 vertices each. + m_motionVectorMesh.SetVertexCount(static_cast(countX + 1) * 2); // countX + 1 lines for each grid row, 2 vertices each. glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -103,14 +81,13 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shar static_cast(*presetPerFrameContext.mv_b), static_cast(*presetPerFrameContext.mv_a)); - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - glLineWidth(1); #ifndef USE_GLES glEnable(GL_LINE_SMOOTH); #endif + auto& lineVertices = m_motionVectorMesh.Vertices(); + for (int y = 0; y < countY; y++) { float const posY = (static_cast(y) + 0.25f) / (static_cast(countY) + divertY + 0.25f - 1.0f) - divertY2; @@ -124,40 +101,25 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shar if (posX > 0.0001f && posX < 0.9999f) { - lineVertices[vertex].x = posX; - lineVertices[vertex].y = posY; - lineVertices[vertex].index = vertex; - - lineVertices[vertex + 1] = lineVertices[vertex]; - lineVertices[vertex + 1].index++; + lineVertices[vertex + 1] = lineVertices[vertex] = {posX, posY}; vertex += 2; } } // Draw a row of lines. - if (m_lastVertexCount >= vertex) - { - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(MotionVectorVertex) * vertex, lineVertices.data()); - } - else - { - glBufferData(GL_ARRAY_BUFFER, sizeof(MotionVectorVertex) * vertex, lineVertices.data(), GL_STREAM_DRAW); - m_lastVertexCount = vertex; - } - glDrawArrays(GL_LINES, 0, static_cast(vertex)); + m_motionVectorMesh.Update(); + m_motionVectorMesh.Draw(); } } - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); + Renderer::Mesh::Unbind(); + Renderer::Shader::Unbind(); #ifndef USE_GLES glDisable(GL_LINE_SMOOTH); #endif - Renderer::Shader::Unbind(); - glDisable(GL_BLEND); } diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.hpp b/src/libprojectM/MilkdropPreset/MotionVectors.hpp index 4782e03ade..93e44124b2 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.hpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.hpp @@ -3,7 +3,7 @@ #include "PerFrameContext.hpp" #include "PresetState.hpp" -#include +#include #include @@ -13,21 +13,18 @@ namespace MilkdropPreset { /** * @brief Draws a flexible motion vector field. * - * This is broken right now, as it only renders a relatively static 1px point grid, with no apparent motion trails. - * Milkdrop renders this as lines with trails. - * - * @todo Reverse-engineer the original Milkdrop code and reimplement it properly. - * https://github.com/projectM-visualizer/milkdrop2/blob/f05b0d811a87a17c4624170c26c93bac39b05bde/src/vis_milk2/milkdropfs.cpp#L1239 + * Uses the same drawing logic as Milkdrop, but instead of reverse-propagating the motion data + * on the CPU, projectM does this within the Motion Vector vertex shader. The Warp effect draws the + * final U/V coordinates to a float texture, which is then used in the next frame to calculate the + * vector length at the location of the line origin. */ -class MotionVectors : public Renderer::RenderItem +class MotionVectors { public: MotionVectors() = delete; explicit MotionVectors(PresetState& presetState); - void InitVertexAttrib(); - /** * Renders the motion vectors. * @param presetPerFrameContext The per-frame context variables. @@ -36,20 +33,14 @@ class MotionVectors : public Renderer::RenderItem void Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr motionTexture); private: - struct MotionVectorVertex { - float x{}; - float y{}; - int32_t index{}; - }; - std::shared_ptr GetShader(); PresetState& m_presetState; //!< The global preset state. + Renderer::Mesh m_motionVectorMesh; //!< The Motion Vector geometry. + std::weak_ptr m_motionVectorShader; //!< The motion vector shader, calculates the trace positions in the GPU. std::shared_ptr m_sampler{std::make_shared(GL_CLAMP_TO_EDGE, GL_LINEAR)}; //!< The texture sampler. - - int m_lastVertexCount{}; //!< Number of vertices drawn in the previous draw call. }; } // namespace MilkdropPreset diff --git a/src/libprojectM/MilkdropPreset/Shaders/PresetMotionVectorsVertexShaderGlsl330.vert b/src/libprojectM/MilkdropPreset/Shaders/PresetMotionVectorsVertexShaderGlsl330.vert index 9de4ec60aa..c61d462b75 100644 --- a/src/libprojectM/MilkdropPreset/Shaders/PresetMotionVectorsVertexShaderGlsl330.vert +++ b/src/libprojectM/MilkdropPreset/Shaders/PresetMotionVectorsVertexShaderGlsl330.vert @@ -2,7 +2,6 @@ precision mediump float; layout(location = 0) in vec2 vertex_position; layout(location = 1) in vec4 vertex_color; -layout(location = 2) in int vertex_index; uniform mat4 vertex_transformation; uniform float length_multiplier; @@ -17,7 +16,7 @@ void main() { // screen coordinates. vec2 pos = vertex_position; - if (vertex_index % 2 == 1) + if (gl_VertexID % 2 == 1) { // Reverse propagation using the u/v texture written in the previous frame. // Milkdrop's original code did a simple bilinear interpolation, but here it was already From 99ee47edbb3d6fad60983e442d132851de7b19bb Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 18:40:21 +0200 Subject: [PATCH 09/19] Use Mesh class in Warp effect Now using the generated grid vertices and indices directly, drawing the warp mesh in a single draw call. This will increase the number of drawn vertices, but all GPUs made in the past decade can easily deal with 100K triangles or more. --- .../MilkdropPreset/PerPixelMesh.cpp | 196 ++++++++---------- .../MilkdropPreset/PerPixelMesh.hpp | 50 ++--- .../PresetWarpVertexShaderGlsl330.vert | 10 +- 3 files changed, 116 insertions(+), 140 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp index 5ad57c28d6..20efa53738 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.cpp @@ -18,44 +18,31 @@ namespace libprojectM { namespace MilkdropPreset { -static constexpr uint32_t VerticesPerDrawCall = 1024 * 3; - PerPixelMesh::PerPixelMesh() - : RenderItem() -{ - RenderItem::Init(); -} - -void PerPixelMesh::InitVertexAttrib() + : m_warpMesh(Renderer::VertexBufferUsage::StreamDraw) { - m_drawVertices.resize(VerticesPerDrawCall); // Fixed size, may scale it later depending on GPU caps. - - glGenVertexArrays(1, &m_vaoID); - glGenBuffers(1, &m_vboID); - - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - glEnableVertexAttribArray(3); - glEnableVertexAttribArray(4); - glEnableVertexAttribArray(5); - - // Only position & texture coordinates are per-vertex, colors are equal all over the grid (used for decay). - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, x))); // Position, radius & angle - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, radius))); // Position, radius & angle - glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, zoom))); // zoom, zoom exponent, rotation & warp - glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, centerX))); // Center coord - glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, distanceX))); // Distance - glVertexAttribPointer(5, 2, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), reinterpret_cast(offsetof(MeshVertex, stretchX))); // Stretch - - // Pre-allocate vertex buffer - glBufferData(GL_ARRAY_BUFFER, sizeof(MeshVertex) * m_drawVertices.size(), m_drawVertices.data(), GL_STREAM_DRAW); - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + m_warpMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::Triangles); + + m_warpMesh.Bind(); + m_radiusAngleBuffer.Bind(); + m_zoomRotWarpBuffer.Bind(); + m_centerBuffer.Bind(); + m_distanceBuffer.Bind(); + m_stretchBuffer.Bind(); + + m_radiusAngleBuffer.InitializeAttributePointer(3); + m_zoomRotWarpBuffer.InitializeAttributePointer(4); + m_centerBuffer.InitializeAttributePointer(5); + m_distanceBuffer.InitializeAttributePointer(6); + m_stretchBuffer.InitializeAttributePointer(7); + + Renderer::VertexBuffer::SetEnableAttributeArray(3, true); + Renderer::VertexBuffer::SetEnableAttributeArray(4, true); + Renderer::VertexBuffer::SetEnableAttributeArray(5, true); + Renderer::VertexBuffer::SetEnableAttributeArray(6, true); + Renderer::VertexBuffer::SetEnableAttributeArray(7, true); + + Renderer::Mesh::Unbind(); } void PerPixelMesh::LoadWarpShader(const PresetState& presetState) @@ -139,9 +126,17 @@ void PerPixelMesh::InitializeMesh(const PresetState& presetState) m_gridSizeX = presetState.renderContext.perPixelMeshX; m_gridSizeY = presetState.renderContext.perPixelMeshY; - // Grid size has changed, reallocate vertex buffers - m_vertices.resize((m_gridSizeX + 1) * (m_gridSizeY + 1)); - m_listIndices.resize(m_gridSizeX * m_gridSizeY * 6); + // Grid size has changed, resize buffers accordingly + const size_t vertexCount = (m_gridSizeX + 1) * (m_gridSizeY + 1); + + m_warpMesh.SetVertexCount(vertexCount); + m_radiusAngleBuffer.Resize(vertexCount); + m_zoomRotWarpBuffer.Resize(vertexCount); + m_centerBuffer.Resize(vertexCount); + m_distanceBuffer.Resize(vertexCount); + m_stretchBuffer.Resize(vertexCount); + + m_warpMesh.Indices().Resize(m_gridSizeX * m_gridSizeY * 6); } else if (m_viewportWidth == presetState.renderContext.viewportSizeX && m_viewportHeight == presetState.renderContext.viewportSizeY) @@ -150,29 +145,29 @@ void PerPixelMesh::InitializeMesh(const PresetState& presetState) return; } - float aspectX = static_cast(presetState.renderContext.aspectX); - float aspectY = static_cast(presetState.renderContext.aspectY); + const float aspectX = presetState.renderContext.aspectX; + const float aspectY = presetState.renderContext.aspectY; // Either viewport size or mesh size changed, reinitialize the vertices. + auto& vertices = m_warpMesh.Vertices(); int vertexIndex{0}; for (int gridY = 0; gridY <= m_gridSizeY; gridY++) { for (int gridX = 0; gridX <= m_gridSizeX; gridX++) { - auto& vertex = m_vertices.at(vertexIndex); - - vertex.x = static_cast(gridX) / static_cast(m_gridSizeX) * 2.0f - 1.0f; - vertex.y = static_cast(gridY) / static_cast(m_gridSizeY) * 2.0f - 1.0f; + const float x = static_cast(gridX) / static_cast(m_gridSizeX) * 2.0f - 1.0f; + const float y = static_cast(gridY) / static_cast(m_gridSizeY) * 2.0f - 1.0f; + vertices[vertexIndex] = {x, y}; // Milkdrop uses sqrtf, but hypotf is probably safer. - vertex.radius = hypotf(vertex.x * aspectX, vertex.y * aspectY); + m_radiusAngleBuffer[vertexIndex].radius = hypotf(x * aspectX, y * aspectY); if (gridY == m_gridSizeY / 2 && gridX == m_gridSizeX / 2) { - vertex.angle = 0.0f; + m_radiusAngleBuffer[vertexIndex].angle = 0.0f; } else { - vertex.angle = atan2f(vertex.y * aspectY, vertex.x * aspectX); + m_radiusAngleBuffer[vertexIndex].angle = atan2f(y * aspectY, x * aspectX); } vertexIndex++; @@ -204,12 +199,12 @@ void PerPixelMesh::InitializeMesh(const PresetState& presetState) // 0 - 1 3 // / / // 2 4 - 5 - m_listIndices.at(vertexListIndex++) = vertex; - m_listIndices.at(vertexListIndex++) = vertex + 1; - m_listIndices.at(vertexListIndex++) = vertex + m_gridSizeX + 1; - m_listIndices.at(vertexListIndex++) = vertex + 1; - m_listIndices.at(vertexListIndex++) = vertex + m_gridSizeX + 1; - m_listIndices.at(vertexListIndex++) = vertex + m_gridSizeX + 2; + m_warpMesh.Indices()[vertexListIndex++] = vertex; + m_warpMesh.Indices()[vertexListIndex++] = vertex + 1; + m_warpMesh.Indices()[vertexListIndex++] = vertex + m_gridSizeX + 1; + m_warpMesh.Indices()[vertexListIndex++] = vertex + 1; + m_warpMesh.Indices()[vertexListIndex++] = vertex + m_gridSizeX + 1; + m_warpMesh.Indices()[vertexListIndex++] = vertex + m_gridSizeX + 2; } } } @@ -232,19 +227,25 @@ void PerPixelMesh::CalculateMesh(const PresetState& presetState, const PerFrameC int vertex = 0; // Can't make this multithreaded as per-pixel code may use gmegabuf or regXX vars. + auto& vertices = m_warpMesh.Vertices(); for (int y = 0; y <= m_gridSizeY; y++) { for (int x = 0; x <= m_gridSizeX; x++) { - auto& curVertex = m_vertices[vertex]; + auto& curVertex = vertices[vertex]; + auto& curRadiusAngle = m_radiusAngleBuffer[vertex]; + auto& curZoomRotWarp = m_zoomRotWarpBuffer[vertex]; + auto& curCenter = m_centerBuffer[vertex]; + auto& curDistance = m_distanceBuffer[vertex]; + auto& curStretch = m_stretchBuffer[vertex]; // Execute per-vertex/per-pixel code if the preset uses it. if (perPixelContext.perPixelCodeHandle) { - *perPixelContext.x = static_cast(curVertex.x * 0.5f * presetState.renderContext.aspectX + 0.5f); - *perPixelContext.y = static_cast(curVertex.y * -0.5f * presetState.renderContext.aspectY + 0.5f); - *perPixelContext.rad = static_cast(curVertex.radius); - *perPixelContext.ang = static_cast(curVertex.angle); + *perPixelContext.x = static_cast(curVertex.X() * 0.5f * presetState.renderContext.aspectX + 0.5f); + *perPixelContext.y = static_cast(curVertex.Y() * -0.5f * presetState.renderContext.aspectY + 0.5f); + *perPixelContext.rad = static_cast(curRadiusAngle.radius); + *perPixelContext.ang = static_cast(curRadiusAngle.angle); *perPixelContext.zoom = static_cast(*perFrameContext.zoom); *perPixelContext.zoomexp = static_cast(*perFrameContext.zoomexp); *perPixelContext.rot = static_cast(*perFrameContext.rot); @@ -258,34 +259,38 @@ void PerPixelMesh::CalculateMesh(const PresetState& presetState, const PerFrameC perPixelContext.ExecutePerPixelCode(); - curVertex.zoom = static_cast(*perPixelContext.zoom); - curVertex.zoomExp = static_cast(*perPixelContext.zoomexp); - curVertex.rot = static_cast(*perPixelContext.rot); - curVertex.warp = static_cast(*perPixelContext.warp); - curVertex.centerX = static_cast(*perPixelContext.cx); - curVertex.centerY = static_cast(*perPixelContext.cy); - curVertex.distanceX = static_cast(*perPixelContext.dx); - curVertex.distanceY = static_cast(*perPixelContext.dy); - curVertex.stretchX = static_cast(*perPixelContext.sx); - curVertex.stretchY = static_cast(*perPixelContext.sy); + curZoomRotWarp.zoom = static_cast(*perPixelContext.zoom); + curZoomRotWarp.zoomExp = static_cast(*perPixelContext.zoomexp); + curZoomRotWarp.rot = static_cast(*perPixelContext.rot); + curZoomRotWarp.warp = static_cast(*perPixelContext.warp); + curCenter = {static_cast(*perPixelContext.cx), + static_cast(*perPixelContext.cy)}; + curDistance = {static_cast(*perPixelContext.dx), + static_cast(*perPixelContext.dy)}; + curStretch = {static_cast(*perPixelContext.sx), + static_cast(*perPixelContext.sy)}; } else { - curVertex.zoom = zoom; - curVertex.zoomExp = zoomExp; - curVertex.rot = rot; - curVertex.warp = warp; - curVertex.centerX = cx; - curVertex.centerY = cy; - curVertex.distanceX = dx; - curVertex.distanceY = dy; - curVertex.stretchX = sx; - curVertex.stretchY = sy; + curZoomRotWarp.zoom = zoom; + curZoomRotWarp.zoomExp = zoomExp; + curZoomRotWarp.rot = rot; + curZoomRotWarp.warp = warp; + curCenter = { cx, cy}; + curDistance = {dx, dy}; + curStretch = {sx, sy}; } vertex++; } } + + m_warpMesh.Update(); + m_radiusAngleBuffer.Update(); + m_zoomRotWarpBuffer.Update(); + m_centerBuffer.Update(); + m_distanceBuffer.Update(); + m_stretchBuffer.Update(); } void PerPixelMesh::WarpedBlit(const PresetState& presetState, @@ -362,38 +367,9 @@ void PerPixelMesh::WarpedBlit(const PresetState& presetState, } m_perPixelSampler.Bind(0); - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - - int trianglesPerBatch = static_cast(m_drawVertices.size() / 3 - 4); - int triangleCount = m_gridSizeX * m_gridSizeY * 2; // Two triangles per quad/grid cell. - int sourceIndex = 0; - - while (sourceIndex < triangleCount * 3) - { - int trianglesQueued = 0; - int vertex = 0; - while (trianglesQueued < trianglesPerBatch && sourceIndex < triangleCount * 3) - { - // Copy one triangle/3 vertices - for (int i = 0; i < 3; i++) - { - m_drawVertices[vertex++] = m_vertices[m_listIndices[sourceIndex++]]; - } - - trianglesQueued++; - } - - if (trianglesQueued > 0) - { - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(MeshVertex) * trianglesQueued * 3, m_drawVertices.data()); - glDrawArrays(GL_TRIANGLES, 0, trianglesQueued * 3); - } - } - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + m_warpMesh.Draw(); + Renderer::Mesh::Unbind(); Renderer::Sampler::Unbind(0); Renderer::Shader::Unbind(); } diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp index 98576d30a3..609053204d 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp @@ -1,10 +1,8 @@ #pragma once -#include -#include +#include "Renderer/Mesh.hpp" -#include -#include +#include namespace libprojectM { namespace MilkdropPreset { @@ -27,13 +25,11 @@ class MilkdropShader; * * The mesh size can be changed between frames, the class will reallocate the buffers if needed. */ -class PerPixelMesh : public Renderer::RenderItem +class PerPixelMesh { public: PerPixelMesh(); - void InitVertexAttrib() override; - /** * @brief Loads the warp shader, if the preset uses one. * @param presetState The preset state to retrieve the shader from. @@ -59,31 +55,33 @@ class PerPixelMesh : public Renderer::RenderItem private: /** - * Warp mesh vertex with all required attributes. + * Vertex attributes for radius and angle. */ - struct MeshVertex { - float x{}; - float y{}; + struct RadiusAngle { float radius{}; float angle{}; + static void InitializeAttributePointer(uint32_t attributeIndex) + { + glVertexAttribPointer(attributeIndex, sizeof(RadiusAngle) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(RadiusAngle), nullptr); + } + }; + + /** + * Vertex attributes for zoom, zoom exponent, rotation and warp strength. + */ + struct ZoomRotWarp { float zoom{}; float zoomExp{}; float rot{}; float warp{}; - float centerX{}; - float centerY{}; - - float distanceX{}; - float distanceY{}; - - float stretchX{}; - float stretchY{}; + static void InitializeAttributePointer(uint32_t attributeIndex) + { + glVertexAttribPointer(attributeIndex, sizeof(ZoomRotWarp) / sizeof(float), GL_FLOAT, GL_FALSE, sizeof(ZoomRotWarp), nullptr); + } }; - using VertexList = std::vector; - /** * @brief Initializes the vertex array and fills in static data if needed. * @@ -124,10 +122,12 @@ class PerPixelMesh : public Renderer::RenderItem int m_viewportWidth{}; //!< Last known viewport width. int m_viewportHeight{}; //!< Last known viewport height. - VertexList m_vertices; //!< The calculated mesh vertices. - - std::vector m_listIndices; //!< List of vertex indices to render. - VertexList m_drawVertices; //!< Temp data buffer for the vertices to be drawn. + Renderer::Mesh m_warpMesh; //!< The Warp effect mesh + Renderer::VertexBuffer m_radiusAngleBuffer; //!< Vertex attribute buffer for radius and angle values. + Renderer::VertexBuffer m_zoomRotWarpBuffer; //!< Vertex attribute buffer for zoom, roation and warp values. + Renderer::VertexBuffer m_centerBuffer; //!< Vertex attribute buffer for center coordinate values. + Renderer::VertexBuffer m_distanceBuffer; //!< Vertex attribute buffer for distance values. + Renderer::VertexBuffer m_stretchBuffer; //!< Vertex attribute buffer for stretch values. std::weak_ptr m_perPixelMeshShader; //!< Special shader which calculates the per-pixel UV coordinates. std::unique_ptr m_warpShader; //!< The warp shader. Either preset-defined or a default shader. diff --git a/src/libprojectM/MilkdropPreset/Shaders/PresetWarpVertexShaderGlsl330.vert b/src/libprojectM/MilkdropPreset/Shaders/PresetWarpVertexShaderGlsl330.vert index c52ff6089d..5012604650 100644 --- a/src/libprojectM/MilkdropPreset/Shaders/PresetWarpVertexShaderGlsl330.vert +++ b/src/libprojectM/MilkdropPreset/Shaders/PresetWarpVertexShaderGlsl330.vert @@ -14,11 +14,11 @@ precision mediump float; #define invAspectY aspect.w layout(location = 0) in vec2 vertex_position; -layout(location = 1) in vec2 rad_ang; -layout(location = 2) in vec4 transforms; -layout(location = 3) in vec2 warp_center; -layout(location = 4) in vec2 warp_distance; -layout(location = 5) in vec2 stretch; +layout(location = 3) in vec2 rad_ang; +layout(location = 4) in vec4 transforms; +layout(location = 5) in vec2 warp_center; +layout(location = 6) in vec2 warp_distance; +layout(location = 7) in vec2 stretch; uniform mat4 vertex_transformation; uniform vec4 aspect; From d8340ded9174c33819aa6943f32f314db198a3b9 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 18:55:56 +0200 Subject: [PATCH 10/19] Use Mesh class in Video Echo effect --- src/libprojectM/MilkdropPreset/VideoEcho.cpp | 116 ++++++++----------- src/libprojectM/MilkdropPreset/VideoEcho.hpp | 10 +- 2 files changed, 50 insertions(+), 76 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/VideoEcho.cpp b/src/libprojectM/MilkdropPreset/VideoEcho.cpp index 9bf9ae4f26..f62846ab12 100644 --- a/src/libprojectM/MilkdropPreset/VideoEcho.cpp +++ b/src/libprojectM/MilkdropPreset/VideoEcho.cpp @@ -4,21 +4,11 @@ namespace libprojectM { namespace MilkdropPreset { VideoEcho::VideoEcho(const PresetState& presetState) - : RenderItem() - , m_presetState(presetState) + : m_presetState(presetState) + , m_echoMesh(Renderer::VertexBufferUsage::DynamicDraw, true, true) { - RenderItem::Init(); -} - -void VideoEcho::InitVertexAttrib() -{ - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, x))); // Position - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, r))); // Color - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedPoint), reinterpret_cast(offsetof(TexturedPoint, u))); // Texture coordinates + m_echoMesh.SetRenderPrimitiveType(Renderer::Mesh::PrimitiveType::TriangleStrip); + m_echoMesh.SetVertexCount(4); } void VideoEcho::Draw() @@ -38,15 +28,12 @@ void VideoEcho::Draw() float const fOnePlusInvWidth = 1.0f + 1.0f / static_cast(m_presetState.renderContext.viewportSizeX); float const fOnePlusInvHeight = 1.0f + 1.0f / static_cast(m_presetState.renderContext.viewportSizeY); - m_vertices[0].x = -fOnePlusInvWidth * aspectMultX; - m_vertices[1].x = fOnePlusInvWidth * aspectMultX; - m_vertices[2].x = -fOnePlusInvWidth * aspectMultX; - m_vertices[3].x = fOnePlusInvWidth * aspectMultX; - m_vertices[0].y = fOnePlusInvHeight * aspectMultY; - m_vertices[1].y = fOnePlusInvHeight * aspectMultY; - m_vertices[2].y = -fOnePlusInvHeight * aspectMultY; - m_vertices[3].y = -fOnePlusInvHeight * aspectMultY; + m_echoMesh.Vertices().Set({{-fOnePlusInvWidth * aspectMultX, fOnePlusInvHeight * aspectMultY}, + {fOnePlusInvWidth * aspectMultX, fOnePlusInvHeight * aspectMultY}, + {-fOnePlusInvWidth * aspectMultX, -fOnePlusInvHeight * aspectMultY}, + {fOnePlusInvWidth * aspectMultX, -fOnePlusInvHeight * aspectMultY}}); + auto& colors = m_echoMesh.Colors(); for (int i = 0; i < 4; i++) { auto const indexFloat = static_cast(i); @@ -62,10 +49,10 @@ void VideoEcho::Draw() m_shade[i][k] = 0.5f + 0.5f * m_shade[i][k]; } - m_vertices[i].r = m_shade[i][0]; - m_vertices[i].g = m_shade[i][1]; - m_vertices[i].b = m_shade[i][2]; - m_vertices[i].a = 1.0f; + colors[i] = {m_shade[i][0], + m_shade[i][1], + m_shade[i][2], + 1.0f}; } auto shader = m_presetState.texturedShader.lock(); @@ -80,9 +67,6 @@ void VideoEcho::Draw() m_sampler.Bind(0); } - glBindVertexArray(m_vaoID); - glBindBuffer(GL_ARRAY_BUFFER, m_vboID); - if (m_presetState.videoEchoAlpha > 0.001f) { DrawVideoEcho(); @@ -92,11 +76,9 @@ void VideoEcho::Draw() DrawGammaAdjustment(); } - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - glDisable(GL_BLEND); + Renderer::Mesh::Unbind(); Renderer::Shader::Unbind(); if (mainTexture) @@ -121,15 +103,12 @@ void VideoEcho::DrawVideoEcho() float const zoom = (pass == 0) ? 1.0f : videoEchoZoom; float const tempLow = 0.5f - 0.5f / zoom; - float const temphigh = 0.5f + 0.5f / zoom; - m_vertices[0].u = tempLow; - m_vertices[0].v = tempLow; - m_vertices[1].u = temphigh; - m_vertices[1].v = tempLow; - m_vertices[2].u = tempLow; - m_vertices[2].v = temphigh; - m_vertices[3].u = temphigh; - m_vertices[3].v = temphigh; + float const tempHigh = 0.5f + 0.5f / zoom; + + m_echoMesh.UVs().Set({{tempLow, tempLow}, + {tempHigh, tempLow}, + {tempLow, tempHigh}, + {tempHigh, tempHigh}}); // Flipping if (pass == 1) @@ -138,11 +117,11 @@ void VideoEcho::DrawVideoEcho() { if (videoEchoOrientation % 2 == 1) { - m_vertices[vertex].u = 1.0f - m_vertices[vertex].u; + m_echoMesh.UVs()[vertex].SetU(1.0f - m_echoMesh.UVs()[vertex].U()); } if (videoEchoOrientation >= 2) { - m_vertices[vertex].v = 1.0f - m_vertices[vertex].v; + m_echoMesh.UVs()[vertex].SetV(1.0f - m_echoMesh.UVs()[vertex].V()); } } } @@ -150,14 +129,15 @@ void VideoEcho::DrawVideoEcho() float const mix = (pass == 1) ? videoEchoAlpha : 1.0f - videoEchoAlpha; for (int vertex = 0; vertex < 4; vertex++) { - m_vertices[vertex].r = mix * m_shade[vertex][0]; - m_vertices[vertex].g = mix * m_shade[vertex][1]; - m_vertices[vertex].b = mix * m_shade[vertex][2]; - m_vertices[vertex].a = 1.0f; + m_echoMesh.Colors()[vertex] = { + mix * m_shade[vertex][0], + mix * m_shade[vertex][1], + mix * m_shade[vertex][2], + 1.0f}; } - glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(TexturedPoint) * m_vertices.size()), m_vertices.data(), GL_DYNAMIC_DRAW); - glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast(m_vertices.size())); + m_echoMesh.Update(); + m_echoMesh.Draw(); if (pass == 0) { @@ -182,14 +162,15 @@ void VideoEcho::DrawVideoEcho() for (int vertex = 0; vertex < 4; vertex++) { - m_vertices[vertex].r = gamma * mix * m_shade[vertex][0]; - m_vertices[vertex].g = gamma * mix * m_shade[vertex][1]; - m_vertices[vertex].b = gamma * mix * m_shade[vertex][2]; - m_vertices[vertex].a = 1.0f; + m_echoMesh.Colors()[vertex] = { + gamma * mix * m_shade[vertex][0], + gamma * mix * m_shade[vertex][1], + gamma * mix * m_shade[vertex][2], + 1.0f}; } - glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(TexturedPoint) * m_vertices.size()), m_vertices.data(), GL_DYNAMIC_DRAW); - glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast(m_vertices.size())); + m_echoMesh.Colors().Update(); + m_echoMesh.Draw(); } } } @@ -197,14 +178,10 @@ void VideoEcho::DrawVideoEcho() void VideoEcho::DrawGammaAdjustment() { - m_vertices[0].u = 0.0f; - m_vertices[0].v = 0.0f; - m_vertices[1].u = 1.0f; - m_vertices[1].v = 0.0f; - m_vertices[2].u = 0.0f; - m_vertices[2].v = 1.0f; - m_vertices[3].u = 1.0f; - m_vertices[3].v = 1.0f; + m_echoMesh.UVs().Set({{0.0f, 0.0f}, + {1.0f, 0.0f}, + {0.0f, 1.0f}, + {1.0f, 1.0f}}); glDisable(GL_BLEND); glBlendFunc(GL_ONE, GL_ZERO); @@ -226,14 +203,15 @@ void VideoEcho::DrawGammaAdjustment() for (int vertex = 0; vertex < 4; vertex++) { - m_vertices[vertex].r = gamma * m_shade[vertex][0]; - m_vertices[vertex].g = gamma * m_shade[vertex][1]; - m_vertices[vertex].b = gamma * m_shade[vertex][2]; - m_vertices[vertex].a = 1.0f; + m_echoMesh.Colors()[vertex] = { + gamma * m_shade[vertex][0], + gamma * m_shade[vertex][1], + gamma * m_shade[vertex][2], + 1.0f}; } - glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(TexturedPoint) * m_vertices.size()), m_vertices.data(), GL_DYNAMIC_DRAW); - glDrawArrays(GL_TRIANGLE_STRIP, 0, static_cast(m_vertices.size())); + m_echoMesh.Update(); + m_echoMesh.Draw(); if (redraw == 0) { diff --git a/src/libprojectM/MilkdropPreset/VideoEcho.hpp b/src/libprojectM/MilkdropPreset/VideoEcho.hpp index dce3cace9f..1077ba4f66 100644 --- a/src/libprojectM/MilkdropPreset/VideoEcho.hpp +++ b/src/libprojectM/MilkdropPreset/VideoEcho.hpp @@ -3,9 +3,7 @@ #include "PerFrameContext.hpp" #include "PresetState.hpp" -#include - -#include +#include namespace libprojectM { namespace MilkdropPreset { @@ -13,14 +11,12 @@ namespace MilkdropPreset { /** * @brief Renders a video "echo" (ghost image) effect and gamma adjustments. */ -class VideoEcho: public Renderer::RenderItem +class VideoEcho { public: VideoEcho() = delete; explicit VideoEcho(const PresetState& presetState); - void InitVertexAttrib() override; - void Draw(); private: @@ -31,7 +27,7 @@ class VideoEcho: public Renderer::RenderItem const PresetState& m_presetState; //!< The global preset state. std::array, 4> m_shade; // !< Random, changing color values for the four corners - std::array m_vertices; //!< The video echo/gamma adj mesh + Renderer::Mesh m_echoMesh; //!< The video echo/gamma adj mesh Renderer::Sampler m_sampler{GL_CLAMP_TO_EDGE, GL_LINEAR}; }; From 96d8771d632c2aa05a6ee95a7dfe18279aa35efa Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Tue, 30 Sep 2025 19:22:19 +0200 Subject: [PATCH 11/19] Use Mesh class in Waveform effect --- src/libprojectM/MilkdropPreset/Waveform.cpp | 47 +++++++------------ src/libprojectM/MilkdropPreset/Waveform.hpp | 12 ++--- .../Waveforms/CenteredSpiro.cpp | 4 +- .../MilkdropPreset/Waveforms/Circle.cpp | 5 +- .../Waveforms/DerivativeLine.cpp | 11 +++-- .../MilkdropPreset/Waveforms/DoubleLine.cpp | 20 ++++---- .../Waveforms/ExplosiveHash.cpp | 5 +- .../MilkdropPreset/Waveforms/Line.cpp | 5 +- .../Waveforms/Milkdrop2077Wave11.cpp | 10 ++-- .../Waveforms/Milkdrop2077Wave9.cpp | 5 +- .../Waveforms/Milkdrop2077WaveFlower.cpp | 5 +- .../Waveforms/Milkdrop2077WaveLasso.cpp | 5 +- .../Waveforms/Milkdrop2077WaveSkewed.cpp | 4 +- .../Waveforms/Milkdrop2077WaveStar.cpp | 4 +- .../Waveforms/Milkdrop2077WaveX.cpp | 10 ++-- .../MilkdropPreset/Waveforms/SpectrumLine.cpp | 5 +- .../MilkdropPreset/Waveforms/WaveformMath.cpp | 11 +++-- .../MilkdropPreset/Waveforms/WaveformMath.hpp | 4 +- .../Waveforms/XYOscillationSpiral.cpp | 4 +- 19 files changed, 86 insertions(+), 90 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/Waveform.cpp b/src/libprojectM/MilkdropPreset/Waveform.cpp index ce4351784c..6d6cd17cd9 100644 --- a/src/libprojectM/MilkdropPreset/Waveform.cpp +++ b/src/libprojectM/MilkdropPreset/Waveform.cpp @@ -7,33 +7,16 @@ #include -#include