From 904292e577e062bf6d802029f7a1ffdf5c6bfce3 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Wed, 15 Mar 2023 20:26:12 +0100 Subject: [PATCH] Implemented motion vector grid. Have to do the motion reverse-propagation on the GPU, as the warp mesh is also calculated there. This is done storing the U/V warp coordinates in a separate texture attachment and then read the (already interpolated) coordinate in the next frame. Added a second color buffer output to the HLSL shader to accomplish it with both the default warp shader and preset-supplied ones. Luckily, the HLSL transpiler knows what to do. Since all framebuffer textures must match in size, this adds a bit more VRAM usage. We also need to mask all other draw calls from writing to the second texture expect in the warp shader. --- .../MilkdropPreset/MilkdropPreset.cpp | 29 ++++- .../MilkdropPreset/MilkdropPreset.hpp | 2 + .../MilkdropPreset/MilkdropShader.cpp | 10 +- .../MilkdropPreset/MotionVectors.cpp | 103 ++++++++---------- .../MilkdropPreset/MotionVectors.hpp | 13 ++- .../MilkdropPreset/PerPixelMesh.hpp | 55 +++++----- src/libprojectM/Renderer/Framebuffer.cpp | 20 +++- src/libprojectM/Renderer/Framebuffer.hpp | 22 +++- src/libprojectM/Renderer/StaticGlShaders.cpp | 68 +++++++++++- src/libprojectM/Renderer/StaticGlShaders.hpp | 1 + src/libprojectM/Renderer/Texture.cpp | 45 ++++++-- src/libprojectM/Renderer/Texture.hpp | 30 +++-- .../Renderer/TextureAttachment.cpp | 29 ++++- .../Renderer/TextureAttachment.hpp | 15 +++ 14 files changed, 321 insertions(+), 121 deletions(-) diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp index 027ec4863..3e165c836 100755 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp @@ -74,25 +74,36 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio m_state.renderContext = renderContext; // Update framebuffer size if needed - m_framebuffer.SetSize(renderContext.viewportSizeX, renderContext.viewportSizeY); + if (m_framebuffer.SetSize(renderContext.viewportSizeX, renderContext.viewportSizeY)) + { + m_isFirstFrame = true; + } m_state.mainTexture = m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0); // First evaluate per-frame code PerFrameUpdate(); // Motion vector field. Drawn to the previous frame texture before warping it. - m_framebuffer.Bind(m_previousFrameBuffer); - m_motionVectors.Draw(m_perFrameContext); + // Only do it after drawing one frame after init or resize. + if (!m_isFirstFrame) + { + m_framebuffer.Bind(m_previousFrameBuffer); + m_motionVectors.Draw(m_perFrameContext, m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 1)); + } // We now draw to the first framebuffer, but read from the second one for warping and textured shapes. m_framebuffer.BindRead(m_previousFrameBuffer); m_framebuffer.BindDraw(m_currentFrameBuffer); + // Unmask the motion vector u/v texture for the warp mesh draw and clean both buffers. + m_framebuffer.MaskDrawBuffer(1, false); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Draw previous frame image warped via per-pixel mesh and warp shader m_perPixelMesh.Draw(m_state, m_perFrameContext, m_perPixelContext); + m_framebuffer.MaskDrawBuffer(1, true); // Update blur textures m_state.blurTexture.Update(m_state, m_perFrameContext); @@ -135,6 +146,8 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio // Swap framebuffers for the next frame. std::swap(m_currentFrameBuffer, m_previousFrameBuffer); + + m_isFirstFrame = false; } @@ -189,8 +202,14 @@ void MilkdropPreset::Load(std::istream& stream) void MilkdropPreset::InitializePreset(PresetFileParser& parsedFile) { // Create the offscreen rendering surfaces. - m_framebuffer.CreateColorAttachment(0, 0); - m_framebuffer.CreateColorAttachment(1, 0); + m_framebuffer.CreateColorAttachment(0, 0); // Main image + m_framebuffer.CreateColorAttachment(0, 1, GL_RG32F, GL_RG, GL_FLOAT); // Motion vector u/v + m_framebuffer.CreateColorAttachment(1, 0); // Main image + m_framebuffer.CreateColorAttachment(1, 1, GL_RG32F, GL_RG, GL_FLOAT); // Motion vector u/v + + // Mask the motion vector buffer by default. + m_framebuffer.MaskDrawBuffer(1, true); + Framebuffer::Unbind(); // Load global init variables into the state diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp index 9d4444e29..3b0b70f07 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp @@ -122,4 +122,6 @@ private: Border m_border; //!< Inner/outer borders. FinalComposite m_finalComposite; //!< Final composite shader or filters. + + bool m_isFirstFrame{true}; //!< Controls drawing the motion vectors starting with the second frame. }; diff --git a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp index 50210d651..d4d3f023f 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropShader.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropShader.cpp @@ -327,7 +327,7 @@ void MilkdropShader::PreprocessPresetShader(std::string& program) if (m_type == ShaderType::WarpShader) { - program.replace(int(found), 11, "void PS(float4 _vDiffuse : COLOR, float4 _uv : TEXCOORD0, float2 _rad_ang : TEXCOORD1, out float4 _return_value : COLOR)\n"); + program.replace(int(found), 11, "void PS(float4 _vDiffuse : COLOR, float4 _uv : TEXCOORD0, float2 _rad_ang : TEXCOORD1, out float4 _return_value : COLOR0, out float4 _mv_tex_coords : COLOR1)\n"); } else { @@ -346,9 +346,11 @@ void MilkdropShader::PreprocessPresetShader(std::string& program) #ifdef MILKDROP_PRESET_DEBUG std::cerr << "[MilkdropShader] First '{' found at: " << int(found) << std::endl; #endif - const char* progMain = - "{\n" - "float3 ret = 0;\n"; + std::string progMain = "{\nfloat3 ret = 0;\n"; + if (m_type == ShaderType::WarpShader) + { + progMain.append("_mv_tex_coords.xy = _uv.xy;\n"); + } program.replace(int(found), 1, progMain); } else diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.cpp b/src/libprojectM/MilkdropPreset/MotionVectors.cpp index c6aa3e747..479b2aead 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.cpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.cpp @@ -1,23 +1,36 @@ #include "MotionVectors.hpp" -#include +#include +#include MotionVectors::MotionVectors(PresetState& presetState) : RenderItem() , m_presetState(presetState) { + auto staticShaders = StaticGlShaders::Get(); + m_motionVectorShader.CompileProgram(staticShaders->GetPresetMotionVectorsVertexShader(), + staticShaders->GetV2fC4fFragmentShader()); RenderItem::Init(); } void MotionVectors::InitVertexAttrib() { glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); 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))); } -void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext) +void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr motionTexture) { + // Don't draw if invisible. + if (*presetPerFrameContext.mv_a < 0.0001f) + { + return; + } + int countX = static_cast(*presetPerFrameContext.mv_x); int countY = static_cast(*presetPerFrameContext.mv_y); @@ -40,10 +53,8 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext) divertY = 0.0f; } - auto divertX2 = static_cast(*presetPerFrameContext.mv_dx); - auto divertY2 = static_cast(*presetPerFrameContext.mv_dy); - - auto lengthMultiplier = static_cast(*presetPerFrameContext.mv_l); + auto const divertX2 = static_cast(*presetPerFrameContext.mv_dx); + auto const divertY2 = static_cast(*presetPerFrameContext.mv_dy); // Clamp X/Y diversions to 0..1 if (divertX < 0.0f) @@ -63,16 +74,25 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext) divertY = 1.0f; } - float const inverseWidth = 1.0f / static_cast(m_presetState.renderContext.viewportSizeX); - float const minimalLength = 1.0f * inverseWidth; + // 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. + float const inverseWidth = 1.25f / static_cast(m_presetState.renderContext.viewportSizeX); + 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. + std::vector lineVertices(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); - m_presetState.untexturedShader.Bind(); - m_presetState.untexturedShader.SetUniformMat4x4("vertex_transformation", m_presetState.orthogonalProjection); + m_motionVectorShader.Bind(); + m_motionVectorShader.SetUniformMat4x4("vertex_transformation", m_presetState.orthogonalProjection); + m_motionVectorShader.SetUniformFloat("length_multiplier", static_cast(*presetPerFrameContext.mv_l)); + m_motionVectorShader.SetUniformFloat("minimum_length", minimumLength); + + m_motionVectorShader.SetUniformInt("warp_coordinates", 0); + + motionTexture->Bind(0, m_sampler); glVertexAttrib4f(1, static_cast(*presetPerFrameContext.mv_r), @@ -83,6 +103,10 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext) glBindVertexArray(m_vaoID); glBindBuffer(GL_ARRAY_BUFFER, m_vboID); + glLineWidth(1); +#if USE_GLES == 0 + glEnable(GL_LINE_SMOOTH); +#endif for (int y = 0; y < countY; y++) { @@ -97,64 +121,31 @@ void MotionVectors::Draw(const PerFrameContext& presetPerFrameContext) if (posX > 0.0001f && posX < 0.9999f) { - float posX2{}; - float posY2{}; + lineVertices[vertex].x = posX; + lineVertices[vertex].y = posY; + lineVertices[vertex].index = vertex; - // Uses the warp mesh texture transformation to get the motion direction of this point. - ReversePropagatePoint(posX, posY, posX2, posY2); - - // Enforce minimum trail length - { - float distX = posX2 - posX; - float distY = posY2 - posY; - - distX *= lengthMultiplier; - distY *= lengthMultiplier; - - float length = sqrtf(distX * distX + distY * distY); - if (length > minimalLength) - { - } - else if (length > 0.00000001f) - { - length = minimalLength / length; - distX *= length; - distY *= length; - } - else - { - distX = minimalLength; - distY = minimalLength; - } - - posX2 = posX + distX; - posY2 = posY + distY; - } - - // Assign line vertices - lineVertices.at(vertex).x = posX * 2.0f - 1.0f; - lineVertices.at(vertex).y = posY * 2.0f - 1.0f; - lineVertices.at(vertex + 1).x = posX2 * 2.0f - 1.0f; - lineVertices.at(vertex + 1).y = posY2 * 2.0f - 1.0f; + lineVertices[vertex + 1] = lineVertices[vertex]; + lineVertices[vertex + 1].index++; vertex += 2; } } // Draw a row of lines. - glBufferData(GL_ARRAY_BUFFER, sizeof(Point) * lineVertices.size(), lineVertices.data(), GL_DYNAMIC_DRAW); - glDrawArrays(GL_LINES, 0, static_cast(lineVertices.size())); + glBufferData(GL_ARRAY_BUFFER, sizeof(MotionVectorVertex) * vertex, lineVertices.data(), GL_DYNAMIC_DRAW); + glDrawArrays(GL_LINES, 0, static_cast(vertex)); } } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); +#if USE_GLES == 0 + glDisable(GL_LINE_SMOOTH); +#endif + Shader::Unbind(); glDisable(GL_BLEND); } - -void MotionVectors::ReversePropagatePoint(float posX1, float posY1, float& posX2, float& posY2) -{ -} diff --git a/src/libprojectM/MilkdropPreset/MotionVectors.hpp b/src/libprojectM/MilkdropPreset/MotionVectors.hpp index d8f1a736d..5720d3add 100644 --- a/src/libprojectM/MilkdropPreset/MotionVectors.hpp +++ b/src/libprojectM/MilkdropPreset/MotionVectors.hpp @@ -26,11 +26,20 @@ public: /** * Redners the motion vectors. * @param presetPerFrameContext The per-frame context variables. + * @param motionTexture The u/v "motion" texture written by the warp shader. */ - void Draw(const PerFrameContext& presetPerFrameContext); + void Draw(const PerFrameContext& presetPerFrameContext, std::shared_ptr motionTexture); private: - void ReversePropagatePoint(float posX1, float posY1, float& posX2, float&posY2); + struct MotionVectorVertex + { + float x{}; + float y{}; + int32_t index{}; + }; PresetState& m_presetState; //!< The global preset state. + + Shader 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. }; diff --git a/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp b/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp index 7f55fb057..8247530b3 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp +++ b/src/libprojectM/MilkdropPreset/PerPixelMesh.hpp @@ -53,7 +53,34 @@ public: const PerFrameContext& perFrameContext, PerPixelContext& perPixelContext); + private: + /** + * Warp mesh vertex with all required attributes. + */ + struct MeshVertex { + float x{}; + float y{}; + float radius{}; + float angle{}; + + float zoom{}; + float zoomExp{}; + float rot{}; + float warp{}; + + float centerX{}; + float centerY{}; + + float distanceX{}; + float distanceY{}; + + float stretchX{}; + float stretchY{}; + }; + + using VertexList = std::vector; + /** * @brief Initializes the vertex array and fills in static data if needed. * @@ -81,40 +108,16 @@ private: */ void WarpedBlit(const PresetState& presetState, const PerFrameContext& perFrameContext); - /** - * Warp mesh vertex with all required attributes. - */ - struct MeshVertex { - float x{}; - float y{}; - float radius{}; - float angle{}; - - float zoom{}; - float zoomExp{}; - float rot{}; - float warp{}; - - float centerX{}; - float centerY{}; - - float distanceX{}; - float distanceY{}; - - float stretchX{}; - float stretchY{}; - }; - int m_gridSizeX{}; //!< Warp mesh X resolution. int m_gridSizeY{}; //!< Warp mesh Y resolution. int m_viewportWidth{}; //!< Last known viewport width. int m_viewportHeight{}; //!< Last known viewport height. - std::vector m_vertices; //!< The calculated mesh vertices. + VertexList m_vertices; //!< The calculated mesh vertices. std::vector m_listIndices; //!< List of vertex indices to render. - std::vector m_drawVertices; //!< Temp data buffer for the vertices to be drawn. + VertexList m_drawVertices; //!< Temp data buffer for the vertices to be drawn. Shader 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/Renderer/Framebuffer.cpp b/src/libprojectM/Renderer/Framebuffer.cpp index 9aca5eb14..919f28762 100644 --- a/src/libprojectM/Renderer/Framebuffer.cpp +++ b/src/libprojectM/Renderer/Framebuffer.cpp @@ -71,12 +71,12 @@ void Framebuffer::Unbind() glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } -void Framebuffer::SetSize(int width, int height) +bool Framebuffer::SetSize(int width, int height) { if (width == 0 || height == 0 || (width == m_width && height == m_height)) { - return; + return false; } m_width = width; @@ -92,16 +92,23 @@ void Framebuffer::SetSize(int width, int height) } } glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; } void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentIndex) +{ + CreateColorAttachment(framebufferIndex, attachmentIndex, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE); +} + +void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentIndex, GLint internalFormat, GLenum format, GLenum type) { if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) { return; } - TextureAttachment textureAttachment{TextureAttachment::AttachmentType::Color, m_width, m_height}; + TextureAttachment textureAttachment{internalFormat, format, type, m_width, m_height }; const auto texture = textureAttachment.Texture(); m_attachments.at(framebufferIndex).insert({GL_COLOR_ATTACHMENT0 + attachmentIndex, std::move(textureAttachment)}); @@ -190,6 +197,13 @@ void Framebuffer::CreateDepthStencilAttachment(int framebufferIndex) glBindFramebuffer(GL_FRAMEBUFFER, 0); } +void Framebuffer::MaskDrawBuffer(int bufferIndex, bool masked) +{ + // Invert the flag, as "true" means the color channel *will* be written. + auto glMasked = static_cast(!masked); + glColorMaski(bufferIndex, glMasked, glMasked, glMasked, glMasked); +} + void Framebuffer::UpdateDrawBuffers(int framebufferIndex) { if (framebufferIndex < 0 || framebufferIndex >= static_cast(m_framebufferIds.size())) diff --git a/src/libprojectM/Renderer/Framebuffer.hpp b/src/libprojectM/Renderer/Framebuffer.hpp index 494fa45d6..9d304a192 100644 --- a/src/libprojectM/Renderer/Framebuffer.hpp +++ b/src/libprojectM/Renderer/Framebuffer.hpp @@ -89,8 +89,9 @@ public: * won't be changed. * @param width The width of the framebuffer. * @param height The height of the framebuffer. + * @return true if the framebuffer was resized, false if it's contents remain unchanged. */ - void SetSize(int width, int height); + bool SetSize(int width, int height); /** * @brief Adds a new color attachment to the framebuffer. @@ -100,6 +101,17 @@ public: */ void CreateColorAttachment(int framebufferIndex, int attachmentIndex); + /** + * @brief Adds a new color attachment to the framebuffer with the specified format. + * @param framebufferIndex The framebuffer index. + * @param index The index of the attachment, at least indices 0-7 are guaranteed to be available. + * @param internalFormat OpenGL internal format, e.g. GL_RGBA8 + * @param format OpenGL color format, e.g. GL_RGBA + * @param type OpenGL component storage type, e.g. GL_UNSIGNED _BYTE + */ + void CreateColorAttachment(int framebufferIndex, int attachmentIndex, + GLint internalFormat, GLenum format, GLenum type); + /** * @brief Returns the texture ID of the given framebuffer and color attachment. * @param framebufferIndex The framebuffer index. @@ -126,6 +138,14 @@ public: */ void CreateDepthStencilAttachment(int framebufferIndex); + /** + * @brief Sets the masked flag for a specific draw buffer. + * This can be used to enable or disable rendering to specific color attachments. + * @param bufferIndex The index of the buffer to set the mask flag on. + * @param masked true if the attachment should be masked, false if not. + */ + void MaskDrawBuffer(int bufferIndex, bool masked); + private: /** * @brief Updates the draw buffer list for the fragment shader outputs of the given framebuffer. diff --git a/src/libprojectM/Renderer/StaticGlShaders.cpp b/src/libprojectM/Renderer/StaticGlShaders.cpp index a41442d26..9e1be95b4 100644 --- a/src/libprojectM/Renderer/StaticGlShaders.cpp +++ b/src/libprojectM/Renderer/StaticGlShaders.cpp @@ -8,6 +8,9 @@ namespace { // Variants of shaders for GLSL1.2 +const std::string kPresetMotionVectorsVertexShaderGlsl120 = R"( +)"; + const std::string kPresetWarpVertexShaderGlsl120 = R"( #define pos vertex_position.xy #define radius vertex_position.z @@ -444,6 +447,63 @@ void main(){ )"; // Variants of shaders for GLSL3.3 +const std::string kPresetMotionVectorsVertexShaderGlsl330 = R"( +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; +uniform float minimum_length; + +uniform sampler2D warp_coordinates; + +out vec4 fragment_color; + +void main() { + // Input positions are given in texture coordinates (0...1), not the usual + // screen coordinates. + vec2 pos = vertex_position; + + if (vertex_index % 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 + // done by the fragment shader during the warp mesh drawing. We just need to look up the + // motion vector coordinate. + vec2 oldUV = texture(warp_coordinates, pos.xy).xy; + + // Enforce minimum trail length + vec2 dist = oldUV - pos; + dist *= length_multiplier; + float len = length(dist); + if (len > minimum_length) + {} + else if (len > 0.00000001f) + { + len = minimum_length / len; + dist *= len; + } + else + { + dist = vec2(minimum_length); + } + + pos += dist; + } + + // Transform positions from 0...1 to -1...1 in each direction. + pos = pos * 2.0 - 1.0; + + // Flip final Y, as we draw this top-down, which is bottom-up in OpenGL. + pos.y = -pos.y; + + // Now we've got the usual coordinates, apply our orthogonal transformation. + gl_Position = vertex_transformation * vec4(pos, 0.0, 1.0); + fragment_color = vertex_color; +} +)"; + const std::string kPresetWarpVertexShaderGlsl330 = R"( #define pos vertex_position.xy #define radius vertex_position.z @@ -529,10 +589,14 @@ in vec2 frag_TEXCOORD1; uniform sampler2D texture_sampler; -out vec4 color; +layout(location = 0) out vec4 color; +layout(location = 1) out vec2 texCoords; void main() { + // Main image color = frag_COLOR * texture(texture_sampler, frag_TEXCOORD0.xy); + // Motion vector grid u/v coords for the next frame + texCoords = frag_TEXCOORD0.xy; } )"; @@ -575,6 +639,7 @@ const std::string kV2fC4fFragmentShaderGlsl330 = R"( precision mediump float; in vec4 fragment_color; + out vec4 color; void main(){ @@ -1024,6 +1089,7 @@ std::string StaticGlShaders::AddVersionHeader(std::string shader_text) { return k##name##Glsl330; \ } +DECLARE_SHADER_ACCESSOR(PresetMotionVectorsVertexShader); DECLARE_SHADER_ACCESSOR(PresetWarpVertexShader); DECLARE_SHADER_ACCESSOR(PresetWarpFragmentShader); DECLARE_SHADER_ACCESSOR(PresetCompVertexShader); diff --git a/src/libprojectM/Renderer/StaticGlShaders.hpp b/src/libprojectM/Renderer/StaticGlShaders.hpp index 1ce750e42..b3f6cd08f 100644 --- a/src/libprojectM/Renderer/StaticGlShaders.hpp +++ b/src/libprojectM/Renderer/StaticGlShaders.hpp @@ -26,6 +26,7 @@ class StaticGlShaders { } // Returns the named static GL shader resource. + std::string GetPresetMotionVectorsVertexShader(); std::string GetPresetWarpVertexShader(); std::string GetPresetWarpFragmentShader(); std::string GetPresetCompVertexShader(); diff --git a/src/libprojectM/Renderer/Texture.cpp b/src/libprojectM/Renderer/Texture.cpp index 6e68cafa1..67056893d 100644 --- a/src/libprojectM/Renderer/Texture.cpp +++ b/src/libprojectM/Renderer/Texture.cpp @@ -3,23 +3,36 @@ #include Texture::Texture(std::string name, const int width, const int height, const bool isUserTexture) - : m_type(GL_TEXTURE_2D) + : m_target(GL_TEXTURE_2D) , m_name(std::move(name)) , m_width(width) , m_height(height) , m_isUserTexture(isUserTexture) + , m_internalFormat(GL_RGB) + , m_format(GL_RGB) + , m_type(GL_UNSIGNED_BYTE) { - glGenTextures(1, &m_textureId); - glBindTexture(GL_TEXTURE_2D, m_textureId); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); - glBindTexture(GL_TEXTURE_2D, 0); + CreateEmptyTexture(); } -Texture::Texture(std::string name, const GLuint texID, const GLenum type, - const int width, const int height, - const bool isUserTexture) - : m_textureId(texID) +Texture::Texture(std::string name, int width, int height, + GLint internalFormat, GLenum format, GLenum type, bool isUserTexture) + : m_target(GL_TEXTURE_2D) + , m_name(std::move(name)) + , m_width(width) + , m_height(height) + , m_isUserTexture(isUserTexture) + , m_internalFormat(internalFormat) + , m_format(format) , m_type(type) +{ + CreateEmptyTexture(); +} + +Texture::Texture(std::string name, const GLuint texID, const GLenum target, + const int width, const int height, const bool isUserTexture) + : m_textureId(texID) + , m_target(target) , m_name(std::move(name)) , m_width(width) , m_height(height) @@ -39,7 +52,7 @@ Texture::~Texture() void Texture::Bind(GLint slot, const Sampler::Ptr& sampler) const { glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(m_type, m_textureId); + glBindTexture(m_target, m_textureId); if (sampler) { @@ -50,7 +63,7 @@ void Texture::Bind(GLint slot, const Sampler::Ptr& sampler) const void Texture::Unbind(GLint slot) const { glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(m_type, 0); + glBindTexture(m_target, 0); } auto Texture::TextureID() const -> GLuint @@ -65,7 +78,7 @@ auto Texture::Name() const -> const std::string& auto Texture::Type() const -> GLenum { - return m_type; + return m_target; } auto Texture::Width() const -> int @@ -82,3 +95,11 @@ auto Texture::IsUserTexture() const -> bool { return m_isUserTexture; } + +void Texture::CreateEmptyTexture() +{ + glGenTextures(1, &m_textureId); + glBindTexture(m_target, m_textureId); + glTexImage2D(m_target, 0, m_internalFormat, m_width, m_height, 0, m_format, m_type, nullptr); + glBindTexture(m_target, 0); +} diff --git a/src/libprojectM/Renderer/Texture.hpp b/src/libprojectM/Renderer/Texture.hpp index 122eaeee8..5506e2b66 100644 --- a/src/libprojectM/Renderer/Texture.hpp +++ b/src/libprojectM/Renderer/Texture.hpp @@ -36,16 +36,26 @@ public: explicit Texture(std::string name, int width, int height, bool isUserTexture); /** - * @brief Constructor. Creates a new texture instance from an existing OpenGL texture. - * The class will take ownership of the texture, e.g. freeing it when destroyed! + * @brief Constructor. Allocates a new, empty texture with the given size and format. * @param name Optional name of the texture for referencing in Milkdrop shaders. - * @param texID The OpenGL texture name (ID). - * @param type The texture type, e.g. GL_TEXTURE_2D. * @param width Width in pixels. * @param height Height in pixels. * @param isUserTexture true if the texture is an externally-loaded image, false if it's an internal texture. */ - explicit Texture(std::string name, GLuint texID, GLenum type, + explicit Texture(std::string name, int width, int height, + GLint internalFormat, GLenum format, GLenum type, bool isUserTexture); + + /** + * @brief Constructor. Creates a new texture instance from an existing OpenGL texture. + * The class will take ownership of the texture, e.g. freeing it when destroyed! + * @param name Optional name of the texture for referencing in Milkdrop shaders. + * @param texID The OpenGL texture name (ID). + * @param target The texture target type, e.g. GL_TEXTURE_2D. + * @param width Width in pixels. + * @param height Height in pixels. + * @param isUserTexture true if the texture is an externally-loaded image, false if it's an internal texture. + */ + explicit Texture(std::string name, GLuint texID, GLenum target, int width, int height, bool isUserTexture); @@ -105,11 +115,17 @@ public: auto IsUserTexture() const -> bool; private: - GLuint m_textureId{0}; //!< The OpenGL texture name/ID. - GLenum m_type{GL_NONE}; //!< The OpenGL texture type, e.g. GL_TEXTURE_2D. + void CreateEmptyTexture(); + + GLuint m_textureId{0}; //!< The OpenGL texture name/ID. + GLenum m_target{GL_NONE}; //!< The OpenGL texture target, e.g. GL_TEXTURE_2D. std::string m_name; //!< The texture name for identifying it in shaders. int m_width{0}; //!< Texture width in pixels. int m_height{0}; //!< Texture height in pixels. bool m_isUserTexture{false}; //!< true if it's a user texture, false if an internal one. + + GLint m_internalFormat{}; //!< OpenGL internal format, e.g. GL_RGBA8 + GLenum m_format{}; //!< OpenGL color format, e.g. GL_RGBA + GLenum m_type{}; //!< OpenGL component storage type, e.g. GL_UNSIGNED _BYTE }; diff --git a/src/libprojectM/Renderer/TextureAttachment.cpp b/src/libprojectM/Renderer/TextureAttachment.cpp index 945f0119f..24299a6e3 100644 --- a/src/libprojectM/Renderer/TextureAttachment.cpp +++ b/src/libprojectM/Renderer/TextureAttachment.cpp @@ -9,6 +9,18 @@ TextureAttachment::TextureAttachment(AttachmentType attachment, int width, int h } } +TextureAttachment::TextureAttachment(GLint internalFormat, GLenum format, GLenum type, int width, int height) + : m_attachmentType(AttachmentType::Color) + , m_internalFormat(internalFormat) + , m_format(format) + , m_type(type) +{ + if (width > 0 && height > 0) + { + ReplaceTexture(width, height); + } +} + auto TextureAttachment::Type() const -> TextureAttachment::AttachmentType { return m_attachmentType; @@ -40,9 +52,18 @@ void TextureAttachment::ReplaceTexture(int width, int height) switch(m_attachmentType) { case AttachmentType::Color: - internalFormat = GL_RGBA; - textureFormat = GL_RGBA; - pixelFormat = GL_UNSIGNED_BYTE; + if (m_internalFormat == 0) + { + internalFormat = GL_RGBA; + textureFormat = GL_RGBA; + pixelFormat = GL_UNSIGNED_BYTE; + } + else + { + internalFormat = m_internalFormat; + textureFormat = m_format; + pixelFormat = m_type; + } break; case AttachmentType::Depth: internalFormat = GL_DEPTH_COMPONENT16; @@ -79,4 +100,4 @@ void TextureAttachment::ReplaceTexture(int width, int height) glBindTexture(GL_TEXTURE_2D, 0); m_texture = std::make_shared("", textureId, GL_TEXTURE_2D, width, height, false); -} \ No newline at end of file +} diff --git a/src/libprojectM/Renderer/TextureAttachment.hpp b/src/libprojectM/Renderer/TextureAttachment.hpp index 147f4666f..7044badff 100644 --- a/src/libprojectM/Renderer/TextureAttachment.hpp +++ b/src/libprojectM/Renderer/TextureAttachment.hpp @@ -39,6 +39,17 @@ public: */ explicit TextureAttachment(AttachmentType attachmentType, int width, int height); + /** + * @brief Creates a new 2D color texture attachment with the given format and size. + * Must have the same size as the framebuffer. + * @param internalFormat OpenGL internal format, e.g. GL_RGBA8 + * @param format OpenGL color format, e.g. GL_RGBA + * @param type OpenGL component storage type, e.g. GL_UNSIGNED _BYTE + * @param width The width of the texture in pixels. + * @param height The height of the texture in pixels. + */ + explicit TextureAttachment(GLint internalFormat, GLenum format, GLenum type, int width, int height); + TextureAttachment(TextureAttachment&& other) = default; auto operator=(TextureAttachment&& other) -> TextureAttachment& = default; @@ -74,4 +85,8 @@ private: AttachmentType m_attachmentType{AttachmentType::Color}; //!< Attachment type of this texture. std::shared_ptr m_texture{std::make_shared()}; //!< The texture. + + GLint m_internalFormat{}; //!< OpenGL internal format, e.g. GL_RGBA8 + GLenum m_format{}; //!< OpenGL color format, e.g. GL_RGBA + GLenum m_type{}; //!< OpenGL component storage type, e.g. GL_UNSIGNED _BYTE };