diff --git a/src/api/include/libprojectM/debug.h b/src/api/include/libprojectM/debug.h index 0070d87a0..df4af95eb 100644 --- a/src/api/include/libprojectM/debug.h +++ b/src/api/include/libprojectM/debug.h @@ -32,13 +32,23 @@ extern "C" { #endif /** - * @brief Writes a .bmp framedump after rendering the next main texture, before shaders are applied. + * @brief Writes a .bmp main texture dump after rendering the next main texture, before shaders are applied. * - * The image is written to the current working directory and is named "frame_texture_contents-[date].bmp". + * If no file name is given, the image is written to the current working directory + * and will be named named "frame_texture_contents-YYYY-mm-dd-HH:MM:SS-frame.bmp". + * + * Note this is the main texture contents, not the final rendering result. If the active preset + * uses a composite shader, the dumped image will not have it applied. The main texture is what is + * passed over to the next frame, the composite shader is only applied to the display framebuffer + * after updating the main texture. + * + * To capture the actual output, dump the contents of the main framebuffer after calling + * @a projectm_render_frame() on the application side. * * @param instance The projectM instance handle. + * @param output_file The filename to write the dump to or NULL. */ -PROJECTM_EXPORT void projectm_write_debug_image_on_next_frame(projectm_handle instance); +PROJECTM_EXPORT void projectm_write_debug_image_on_next_frame(projectm_handle instance, const char* output_file); #ifdef __cplusplus } // extern "C" diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index a7b934fc4..16bd16f60 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -115,8 +115,9 @@ void ProjectM::ResetTextures() m_renderer->ResetTextures(); } -void ProjectM::DumpDebugImageOnNextFrame() +void ProjectM::DumpDebugImageOnNextFrame(const std::string& outputFile) { + m_renderer->frameDumpOutputFile = outputFile; m_renderer->writeNextFrameToFile = true; } diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index 8579989b0..47e2383d5 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -208,7 +208,7 @@ public: * * The main texture is dumped after render pass 1, e.g. before shaders are applied. */ - void DumpDebugImageOnNextFrame(); + void DumpDebugImageOnNextFrame(const std::string& outputFile); private: void EvaluateSecondPreset(); diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index 82a068f64..8e2d96003 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -388,9 +388,15 @@ auto projectm_pcm_add_uint8(projectm_handle instance, const uint8_t* samples, un PcmAdd(instance, samples, count, channels); } -auto projectm_write_debug_image_on_next_frame(projectm_handle instance) -> void +auto projectm_write_debug_image_on_next_frame(projectm_handle instance, const char* output_file) -> void { auto* projectMInstance = handle_to_instance(instance); - projectMInstance->DumpDebugImageOnNextFrame(); + std::string outputFile; + if (output_file) + { + outputFile = output_file; + } + + projectMInstance->DumpDebugImageOnNextFrame(outputFile); } \ No newline at end of file diff --git a/src/libprojectM/Renderer/Renderer.cpp b/src/libprojectM/Renderer/Renderer.cpp index 805e03511..847188df1 100644 --- a/src/libprojectM/Renderer/Renderer.cpp +++ b/src/libprojectM/Renderer/Renderer.cpp @@ -204,11 +204,13 @@ void Renderer::RenderTouch(const Pipeline& pipeline, const PipelineContext& pipe void Renderer::FinishPass1() { m_textureManager->updateMainTexture(); - if(writeNextFrameToFile) { - debugWriteMainTextureToFile(); - writeNextFrameToFile = false; - } + if (writeNextFrameToFile) + { + debugWriteMainTextureToFile(); + writeNextFrameToFile = false; + frameDumpOutputFile = ""; + } } void Renderer::Pass2(const Pipeline& pipeline, const PipelineContext& pipelineContext) @@ -530,36 +532,41 @@ void Renderer::touchDestroyAll() m_waveformList.clear(); } -void Renderer::debugWriteMainTextureToFile() const { - GLuint fbo; - auto mainTexture = m_textureManager->getMainTexture(); +void Renderer::debugWriteMainTextureToFile() const +{ + std::string outputFile = frameDumpOutputFile; + if (outputFile.empty()) + { + static constexpr char prefix[] = "frame_texture_contents-"; + static constexpr auto prefixLen = sizeof(prefix) - 1; + constexpr auto fileNameMaxLength = 150; + constexpr auto fileExtensionLength = 4; + std::vector fileNameBuffer; + fileNameBuffer.resize(fileNameMaxLength); + std::memcpy(fileNameBuffer.data(), prefix, prefixLen); + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + const auto bytesWritten = std::strftime(fileNameBuffer.data() + prefixLen, fileNameMaxLength - prefixLen, "%Y-%m-%d-%H:%M:%S", &tm); + const auto offset = prefixLen + bytesWritten; + const auto spaceLeft = fileNameMaxLength - offset; + std::snprintf(fileNameBuffer.data() + offset, spaceLeft - fileExtensionLength, "-%d.bmp", totalframes); + outputFile = std::string(fileNameBuffer.data()); + } - const auto safeWriteFile = [](const auto frameNumber, auto data, const auto width, const auto height) { - static const std::string prefix{"frame_texture_contents-"}; - const auto prefixLen = prefix.size(); - constexpr auto fileNameMaxLength = 150; - constexpr auto fileExtensionLength = 4; - char fileNameBuffer[fileNameMaxLength]; - std::memcpy(fileNameBuffer, prefix.data(), prefixLen); - auto t = std::time(nullptr); - auto tm = *std::localtime(&t); - const auto bytesWritten = std::strftime(fileNameBuffer + prefixLen, fileNameMaxLength - prefixLen, "%Y-%m-%d-%H:%M:%S", &tm); - const auto offset = prefixLen + bytesWritten; - const auto spaceLeft = fileNameMaxLength - offset; - std::snprintf(fileNameBuffer + offset, spaceLeft - fileExtensionLength, "%d.bmp", frameNumber); - stbi_write_bmp( fileNameBuffer, width, height, 4, data); - }; + GLuint fbo; + auto mainTexture = m_textureManager->getMainTexture(); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mainTexture->texID, 0); + auto dataSize = mainTexture->width * mainTexture->height * 3; + GLubyte* pixels = new GLubyte[dataSize]; + glReadPixels(0, 0, mainTexture->width, mainTexture->height, GL_RGB, GL_UNSIGNED_BYTE, pixels); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mainTexture->texID, 0); - auto dataSize = mainTexture->width * mainTexture->height * 4; - GLubyte* pixels = new GLubyte[dataSize]; - glReadPixels(0, 0, mainTexture->width, mainTexture->height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - safeWriteFile(totalframes, pixels, mainTexture->width, mainTexture->height); - delete[] pixels; + stbi_write_bmp(outputFile.c_str(), mainTexture->width, mainTexture->height, 3, pixels); + + delete[] pixels; } void Renderer::UpdateContext(PipelineContext& context) diff --git a/src/libprojectM/Renderer/Renderer.hpp b/src/libprojectM/Renderer/Renderer.hpp index 513231019..42052af83 100644 --- a/src/libprojectM/Renderer/Renderer.hpp +++ b/src/libprojectM/Renderer/Renderer.hpp @@ -104,6 +104,7 @@ public: bool correction{ true }; bool writeNextFrameToFile{ false }; + std::string frameDumpOutputFile; milliseconds lastTimeFPS{ nowMilliseconds() }; milliseconds currentTimeFPS{ nowMilliseconds() };