diff --git a/CMakeLists.txt b/CMakeLists.txt index f4c624e54..da747d156 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,6 +278,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL Emscripten) endif() message(STATUS " Use system GLM: ${ENABLE_SYSTEM_GLM}") message(STATUS " Use system projectM-eval: ${ENABLE_SYSTEM_PROJECTM_EVAL}") +if(ENABLE_SYSTEM_PROJECTM_EVAL) + message(STATUS " projectM-eval version: ${projectM-Eval_VERSION}") +endif() message(STATUS " Link UI with shared lib: ${ENABLE_SHARED_LINKING}") message(STATUS "") message(STATUS "Targets and applications:") diff --git a/src/api/include/projectM-4/render_opengl.h b/src/api/include/projectM-4/render_opengl.h index c40c7bbdc..74027105b 100644 --- a/src/api/include/projectM-4/render_opengl.h +++ b/src/api/include/projectM-4/render_opengl.h @@ -48,6 +48,21 @@ PROJECTM_EXPORT void projectm_opengl_render_frame(projectm_handle instance); */ PROJECTM_EXPORT void projectm_opengl_render_frame_fbo(projectm_handle instance, uint32_t framebuffer_object_id); +/** + * @brief Burn-in the provided texture into the active preset(s) main texture. + * + * During transitions, the image is drawn onto both active presets. + * + * @param instance The projectM instance handle. + * @param texture The OpenGL texture ID to draw onto the current preset. + * @param left The left offset in screen coordinates. + * @param top The top offset in screen coordinates. + * @param width The width in screen coordinates. Negative values will flip the image horizontally. + * @param height The height in screen coordinates. Negative values will flip the image vertically. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_opengl_burn_texture(projectm_handle instance, uint32_t texture, int left, int top, int width, int height); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp index 24518ad5d..f5f5327f4 100755 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp @@ -105,7 +105,7 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio } // y-flip the previous frame and assign the flipped texture as "main" - m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0), nullptr, true, false); + m_flipTexture.Draw(*renderContext.shaderCache, m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0), nullptr, true, false); m_state.mainTexture = m_flipTexture.Texture(); // We now draw to the current framebuffer. @@ -146,7 +146,7 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio m_border.Draw(m_perFrameContext); // y-flip the image for final compositing again - m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_currentFrameBuffer, 0), nullptr, true, false); + m_flipTexture.Draw(*renderContext.shaderCache, m_framebuffer.GetColorAttachmentTexture(m_currentFrameBuffer, 0), nullptr, true, false); m_state.mainTexture = m_flipTexture.Texture(); // We no longer need the previous frame image, use it to render the final composite. @@ -158,7 +158,7 @@ void MilkdropPreset::RenderFrame(const libprojectM::Audio::FrameAudioData& audio if (!m_finalComposite.HasCompositeShader()) { // Flip texture again in "previous" framebuffer as old-school effects are still upside down. - m_flipTexture.Draw(m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0), m_framebuffer, m_previousFrameBuffer, true, false); + m_flipTexture.Draw(*renderContext.shaderCache, m_framebuffer.GetColorAttachmentTexture(m_previousFrameBuffer, 0), m_framebuffer, m_previousFrameBuffer, true, false); } // Swap framebuffer IDs for the next frame. @@ -178,7 +178,7 @@ void MilkdropPreset::DrawInitialImage(const std::shared_ptr& m_framebuffer.SetSize(renderContext.viewportSizeX, renderContext.viewportSizeY); // Render to previous framebuffer, as this is the image used to draw the next frame on. - m_flipTexture.Draw(image, m_framebuffer, m_previousFrameBuffer); + m_flipTexture.Draw(*renderContext.shaderCache, image, m_framebuffer, m_previousFrameBuffer); } void MilkdropPreset::BindFramebuffer() diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index dc73ea0f2..f307a44e4 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -29,8 +29,8 @@ #include #include -#include #include +#include #include #include @@ -177,11 +177,11 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/) } else { - m_textureCopier->Draw(m_activePreset->OutputTexture(), false, false); + m_textureCopier->Draw(*renderContext.shaderCache, m_activePreset->OutputTexture(), false, false); } // Draw user sprites - m_spriteManager->Draw(audioData, renderContext, targetFramebufferObject, { m_activePreset, m_transitioningPreset }); + m_spriteManager->Draw(audioData, renderContext, targetFramebufferObject, {m_activePreset, m_transitioningPreset}); m_frameCount++; m_previousFrameVolume = audioData.vol; @@ -312,6 +312,23 @@ auto ProjectM::UserSpriteIdentifiers() const -> std::vector return m_spriteManager->ActiveSpriteIdentifiers(); } +void ProjectM::BurnInTexture(uint32_t openGlTextureId, int left, int top, int width, int height) +{ + if (m_activePreset) + { + m_activePreset->BindFramebuffer(); + m_textureCopier->Draw(*m_shaderCache, openGlTextureId, m_windowWidth, m_windowHeight, left, top, width, height); + } + + if (m_transitioningPreset) + { + m_transitioningPreset->BindFramebuffer(); + m_textureCopier->Draw(*m_shaderCache, openGlTextureId, m_windowWidth, m_windowHeight, left, top, width, height); + } + + Renderer::Framebuffer::Unbind(); +} + void ProjectM::SetPresetLocked(bool locked) { // ToDo: Add a preset switch timer separate from the display timer and reset to 0 when diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index f83f9c49b..ca9afa6f6 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -246,6 +246,16 @@ class PROJECTM_EXPORT ProjectM */ auto UserSpriteIdentifiers() const -> std::vector; + /** + * @brief Draws the given texture on the active preset's main texture to get a "burn-in" effect. + * @param openGlTextureId The OpenGL texture to draw onto the active preset(s). + * @param left Left coordinate in pixels on the destination texture. + * @param top Top coordinate in pixels on the destination texture. + * @param width Width of the final image on the destination texture in pixels, can be negative to flip it horizontally. + * @param height Height of the final image on the destination texture in pixels, can be negative to flip it vertically. + */ + void BurnInTexture(uint32_t openGlTextureId, int left, int top, int width, int height); + private: void Initialize(); diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index ac6014aaf..506f12cee 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -180,6 +180,12 @@ void projectm_opengl_render_frame_fbo(projectm_handle instance, uint32_t framebu projectMInstance->RenderFrame(framebuffer_object_id); } +void projectm_opengl_burn_texture(projectm_handle instance, uint32_t texture, int left, int top, int width, int height) +{ + auto projectMInstance = handle_to_instance(instance); + projectMInstance->BurnInTexture(texture, left, top, width, height); +} + void projectm_set_frame_time(projectm_handle instance, double seconds_since_first_frame) { auto projectMInstance = handle_to_instance(instance); diff --git a/src/libprojectM/Renderer/CopyTexture.cpp b/src/libprojectM/Renderer/CopyTexture.cpp index 36602b08e..cf6801ba0 100644 --- a/src/libprojectM/Renderer/CopyTexture.cpp +++ b/src/libprojectM/Renderer/CopyTexture.cpp @@ -17,19 +17,11 @@ layout(location = 2) in vec2 tex_coord; out vec2 fragment_tex_coord; -uniform ivec2 flip; +uniform mat4 vertex_transformation; void main() { - gl_Position = vec4(position, 0.0, 1.0); + gl_Position = vec4(position, 0.0, 1.0) * vertex_transformation; fragment_tex_coord = tex_coord; - if (flip.x > 0) - { - fragment_tex_coord.s = 1.0 - fragment_tex_coord.s; - } - if (flip.y > 0) - { - fragment_tex_coord.t = 1.0 - fragment_tex_coord.t; - } } )"; @@ -53,13 +45,6 @@ CopyTexture::CopyTexture() { m_framebuffer.CreateColorAttachment(0, 0); - std::string vertexShader(ShaderVersion); - std::string fragmentShader(ShaderVersion); - vertexShader.append(CopyTextureVertexShader); - fragmentShader.append(CopyTextureFragmentShader); - - m_shader.CompileProgram(vertexShader, fragmentShader); - m_mesh.SetRenderPrimitiveType(Mesh::PrimitiveType::TriangleStrip); m_mesh.SetVertexCount(4); @@ -78,7 +63,9 @@ CopyTexture::CopyTexture() m_mesh.Update(); } -void CopyTexture::Draw(const std::shared_ptr& originalTexture, bool flipVertical, bool flipHorizontal) +void CopyTexture::Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + bool flipVertical, bool flipHorizontal) { if (originalTexture == nullptr) { @@ -87,10 +74,12 @@ void CopyTexture::Draw(const std::shared_ptr& originalTexture, bo // Just bind the texture and draw it to the currently bound buffer. originalTexture->Bind(0); - Copy(flipVertical, flipHorizontal); + Copy(shaderCache, flipVertical, flipHorizontal); } -void CopyTexture::Draw(const std::shared_ptr& originalTexture, const std::shared_ptr& targetTexture, +void CopyTexture::Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + const std::shared_ptr& targetTexture, bool flipVertical, bool flipHorizontal) { if (originalTexture == nullptr || @@ -128,7 +117,7 @@ void CopyTexture::Draw(const std::shared_ptr& originalTexture, co m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(targetTexture); } - Copy(flipVertical, flipHorizontal); + Copy(shaderCache, flipVertical, flipHorizontal); // Rebind our internal texture. if (targetTexture) @@ -139,7 +128,9 @@ void CopyTexture::Draw(const std::shared_ptr& originalTexture, co Framebuffer::Unbind(); } -void CopyTexture::Draw(const std::shared_ptr& originalTexture, Framebuffer& framebuffer, int framebufferIndex, +void CopyTexture::Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + Framebuffer& framebuffer, int framebufferIndex, bool flipVertical, bool flipHorizontal) { if (originalTexture == nullptr // @@ -162,7 +153,7 @@ void CopyTexture::Draw(const std::shared_ptr& originalTexture, Fr // Draw from unflipped texture originalTexture->Bind(0); - Copy(flipVertical, flipHorizontal); + Copy(shaderCache, flipVertical, flipHorizontal); // Swap texture attachments auto tempAttachment = framebuffer.GetAttachment(framebufferIndex, TextureAttachment::AttachmentType::Color, 0); @@ -174,6 +165,74 @@ void CopyTexture::Draw(const std::shared_ptr& originalTexture, Fr Framebuffer::Unbind(); } +void CopyTexture::Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + const std::shared_ptr& targetTexture, + int left, int top, int width, int height) +{ + if (originalTexture == nullptr || + originalTexture->Empty() || + targetTexture == nullptr || + targetTexture->Empty() || + originalTexture == targetTexture) + { + return; + } + + UpdateTextureSize(targetTexture->Width(), targetTexture->Height()); + + if (m_width == 0 || m_height == 0) + { + return; + } + + std::shared_ptr internalTexture; + + m_framebuffer.Bind(0); + + // Draw from original texture + originalTexture->Bind(0); + internalTexture = m_framebuffer.GetColorAttachmentTexture(0, 0); + m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(targetTexture); + + Copy(shaderCache, left, top, width, height); + + // Rebind our internal texture. + m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(internalTexture); + + Framebuffer::Unbind(); +} + +void CopyTexture::Draw(ShaderCache& shaderCache, + GLuint originalTexture, + int viewportWidth, int viewportHeight, + int left, int top, int width, int height) +{ + if (originalTexture == 0) + { + return; + } + + if (viewportWidth == 0 || viewportHeight == 0) + { + return; + } + + int oldWidth = m_width; + int oldHeight = m_height; + + m_width = viewportWidth; + m_height = viewportHeight; + + // Draw from original texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, originalTexture); + Copy(shaderCache, left, top, width, height); + + m_width = oldWidth; + m_height = oldHeight; +} + auto CopyTexture::Texture() -> std::shared_ptr { return m_framebuffer.GetColorAttachmentTexture(0, 0); @@ -193,11 +252,18 @@ void CopyTexture::UpdateTextureSize(int width, int height) m_framebuffer.SetSize(m_width, m_height); } -void CopyTexture::Copy(bool flipVertical, bool flipHorizontal) +void CopyTexture::Copy(ShaderCache& shaderCache, + bool flipVertical, bool flipHorizontal) { - m_shader.Bind(); - m_shader.SetUniformInt("texture_sampler", 0); - m_shader.SetUniformInt2("flip", {flipHorizontal ? 1 : 0, flipVertical ? 1 : 0}); + glm::mat4x4 flipMatrix(1.0); + + flipMatrix[0][0] = flipHorizontal ? -1.0 : 1.0; + flipMatrix[1][1] = flipVertical ? -1.0 : 1.0; + + std::shared_ptr shader = BindShader(shaderCache); + + shader->SetUniformInt("texture_sampler", 0); + shader->SetUniformMat4x4("vertex_transformation", flipMatrix); m_sampler.Bind(0); @@ -209,5 +275,57 @@ void CopyTexture::Copy(bool flipVertical, bool flipHorizontal) Shader::Unbind(); } +void CopyTexture::Copy(ShaderCache& shaderCache, + int left, int top, int width, int height) +{ + glm::mat4x4 translationMatrix(1.0); + translationMatrix[0][0] = static_cast(width) / static_cast(m_width); + translationMatrix[1][1] = static_cast(height) / static_cast(m_height); + + translationMatrix[3][0] = static_cast(left) / static_cast(m_width); + translationMatrix[3][1] = static_cast(top) / static_cast(m_height); + + std::shared_ptr shader = BindShader(shaderCache); + + shader->SetUniformInt("texture_sampler", 0); + shader->SetUniformMat4x4("vertex_transformation", translationMatrix); + + m_sampler.Bind(0); + + m_mesh.Draw(); + + Mesh::Unbind(); + Sampler::Unbind(0); + Shader::Unbind(); +} + +std::shared_ptr CopyTexture::BindShader(ShaderCache& shaderCache) +{ + auto shader = m_shader.lock(); + + if (!shader) + { + shader = shaderCache.Get("copy_texture"); + } + + if (!shader) + { + std::string vertexShader(ShaderVersion); + std::string fragmentShader(ShaderVersion); + vertexShader.append(CopyTextureVertexShader); + fragmentShader.append(CopyTextureFragmentShader); + + shader = std::make_shared(); + shader->CompileProgram(vertexShader, fragmentShader); + + m_shader = shader; + shaderCache.Insert("copy_texture", shader); + } + + shader->Bind(); + + return shader; +} + } // namespace Renderer } // namespace libprojectM diff --git a/src/libprojectM/Renderer/CopyTexture.hpp b/src/libprojectM/Renderer/CopyTexture.hpp index 9a7ea0cec..154324f52 100644 --- a/src/libprojectM/Renderer/CopyTexture.hpp +++ b/src/libprojectM/Renderer/CopyTexture.hpp @@ -2,7 +2,7 @@ #include "Renderer/Framebuffer.hpp" #include "Renderer/Mesh.hpp" -#include "Renderer/Shader.hpp" +#include "Renderer/ShaderCache.hpp" namespace libprojectM { namespace Renderer { @@ -20,37 +20,76 @@ class CopyTexture /** * @brief Copies the original texture into the currently bound framebuffer. + * @param shaderCache The global shader cache instance. * @param originalTexture The texture to be copied. * @param flipVertical Flip image on the y-axis when copying. * @param flipHorizontal Flip image on the x-axis when copying. */ - void Draw(const std::shared_ptr& originalTexture, + void Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, bool flipVertical = false, bool flipHorizontal = false); /** * @brief Copies the original texture either into the object's internal framebuffer or a given target texture. * The original and target textures must not be the same. + * @param shaderCache The global shader cache instance. * @param originalTexture The texture to be copied. * @param targetTexture Optional target texture to draw onto. * @param flipVertical Flip image on the y-axis when copying. * @param flipHorizontal Flip image on the x-axis when copying. */ - void Draw(const std::shared_ptr& originalTexture, const std::shared_ptr& targetTexture = {}, + void Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + const std::shared_ptr& targetTexture = {}, bool flipVertical = false, bool flipHorizontal = false); /** * @brief Copies the texture bound the given framebuffer's first color attachment. * This is done by drawing into a second framebuffer, then swapping the textures, so the original texture * can be the current color attachment of targetFramebuffer. + * @param shaderCache The global shader cache instance. * @param originalTexture The texture to be copied. * @param targetFramebuffer Optional target texture to draw onto. * @param framebufferIndex The index of the framebuffer to use. * @param flipVertical Flip image on the y-axis when copying. * @param flipHorizontal Flip image on the x-axis when copying. */ - void Draw(const std::shared_ptr& originalTexture, Framebuffer& framebuffer, int framebufferIndex, + void Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + Framebuffer& framebuffer, int framebufferIndex, bool flipVertical = false, bool flipHorizontal = false); + /** + * @brief Draws the original texture onto the specified target texture, using the provided screen coordinates to position it. + * @param shaderCache The global shader cache instance. + * @param originalTexture The texture to be copied. + * @param targetTexture The target texture to draw onto. + * @param left Left offset on the target texture in screen coordinates. + * @param top Top offset on the target texture in screen coordinates. + * @param width Width on the target texture in screen coordinates. Use a negative value to flip vertically. + * @param height Height on the target texture in screen coordinates. Use a negative value to flip horizontally. + */ + void Draw(ShaderCache& shaderCache, + const std::shared_ptr& originalTexture, + const std::shared_ptr& targetTexture, + int left, int top, int width, int height); + + /** + * @brief Draws a raw GL texture into the currently bound framebuffer, using the provided screen coordinates to position it. + * @param shaderCache The global shader cache instance. + * @param originalTexture The texture ID to be copied. + * @param viewportWidth The target surface width. + * @param viewportHeight The target surface height. + * @param left Left offset on the target texture in screen coordinates. + * @param top Top offset on the target texture in screen coordinates. + * @param width Width on the target texture in screen coordinates. Use a negative value to flip vertically. + * @param height Height on the target texture in screen coordinates. Use a negative value to flip horizontally. + */ + void Draw(ShaderCache& shaderCache, + GLuint originalTexture, + int viewportWidth, int viewportHeight, + int left, int top, int width, int height); + /** * @brief Returns the flipped texture. * @@ -64,15 +103,20 @@ class CopyTexture */ void UpdateTextureSize(int width, int height); - void Copy(bool flipVertical, bool flipHorizontal); + void Copy(ShaderCache& shaderCache, + bool flipVertical, bool flipHorizontal); + + void Copy(ShaderCache& shaderCache, + int left, int top, int width, int height); Mesh m_mesh; - Shader m_shader; //!< Simple textured shader + std::weak_ptr m_shader; //!< Simple textured shader Framebuffer m_framebuffer{1}; //!< Framebuffer for drawing the flipped texture Sampler m_sampler{GL_CLAMP_TO_EDGE, GL_NEAREST}; //!< Texture sampler settings int m_width{}; //!< Last known framebuffer/texture width int m_height{}; //!< Last known framebuffer/texture height + std::shared_ptr BindShader(ShaderCache& shaderCache); }; } // namespace Renderer