diff --git a/SofaGLFW/CMakeLists.txt b/SofaGLFW/CMakeLists.txt index 8b96c0e79f..695bb3f938 100644 --- a/SofaGLFW/CMakeLists.txt +++ b/SofaGLFW/CMakeLists.txt @@ -40,25 +40,6 @@ elseif( (DEFINED SOFA_ALLOW_FETCH_DEPENDENCIES AND SOFA_ALLOW_FETCH_DEPENDENCIES endif() -find_package(PkgConfig QUIET) -if(PkgConfig_FOUND) - - pkg_check_modules(FFMPEG IMPORTED_TARGET - libavcodec - libavformat - libavutil - libswscale - libswresample - ) -endif() - -if(TARGET PkgConfig::FFMPEG) - message("Enabling the Video Recorder for GLFW.") -else() - message("Disabling the Video Recorder for GLFW. PkgConfig and libav libraries are required to enable the Video Recorder.") -endif() - - set(SOFAGLFW_SOURCE_DIR src/SofaGLFW) set(HEADER_FILES @@ -69,7 +50,6 @@ set(HEADER_FILES ${SOFAGLFW_SOURCE_DIR}/BaseGUIEngine.h ${SOFAGLFW_SOURCE_DIR}/NullGUIEngine.h ${SOFAGLFW_SOURCE_DIR}/SofaGLFWMouseManager.h - ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoder.h ) set(SOURCE_FILES @@ -85,11 +65,6 @@ if(Sofa.GUI.Common_FOUND) LIST(APPEND SOURCE_FILES ${SOFAGLFW_SOURCE_DIR}/SofaGLFWGUI.cpp) endif() -if(TARGET PkgConfig::FFMPEG) - LIST(APPEND HEADER_FILES ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoderFFMPEG.h) - LIST(APPEND SOURCE_FILES ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoderFFMPEG.cpp) -endif() - add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} PUBLIC Sofa.GL Sofa.Simulation.Graph Sofa.Component.Visual) @@ -115,6 +90,26 @@ endif() add_definitions("-DSOFAGLFW_RESOURCES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/resources\"") +# FFMPEG_exec +sofa_find_package(FFMPEG_exec BOTH_SCOPES) +# FFMPEG +if(FFMPEG_EXEC_FOUND) + install(PROGRAMS "${FFMPEG_EXEC_FILE}" DESTINATION ${CMAKE_INSTALL_PREFIX}/bin COMPONENT applications) +endif() + +# Create build and install versions of .ini file for resources finding +#### For build tree +set(FFMPEG_EXEC_PATH "${FFMPEG_EXEC_FILE}") # absolute path for build dir, see .ini file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/etc/${PROJECT_NAME}.ini.in "${CMAKE_BINARY_DIR}/etc/${PROJECT_NAME}.ini") + +#### For install tree +get_filename_component(FFMPEG_EXEC_FILENAME "${FFMPEG_EXEC_FILE}" NAME) +set(FFMPEG_EXEC_PATH "../bin/${FFMPEG_EXEC_FILENAME}") # relative path for install dir, see .ini file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/etc/${PROJECT_NAME}.ini.in "${CMAKE_BINARY_DIR}/etc/installed${PROJECT_NAME}.ini") +install(FILES "${CMAKE_BINARY_DIR}/etc/installed${PROJECT_NAME}.ini" DESTINATION ${CMAKE_INSTALL_PREFIX}/etc RENAME ${PROJECT_NAME}.ini COMPONENT applications) + + + sofa_create_package_with_targets( PACKAGE_NAME ${PROJECT_NAME} PACKAGE_VERSION ${Sofa_VERSION} diff --git a/SofaGLFW/etc/SofaGLFW.ini.in b/SofaGLFW/etc/SofaGLFW.ini.in new file mode 100644 index 0000000000..b235bf80a0 --- /dev/null +++ b/SofaGLFW/etc/SofaGLFW.ini.in @@ -0,0 +1 @@ +FFMPEG_EXEC_PATH=@FFMPEG_EXEC_PATH@ diff --git a/SofaGLFW/src/SofaGLFW/NullGUIEngine.cpp b/SofaGLFW/src/SofaGLFW/NullGUIEngine.cpp index eec5e391eb..306106d15b 100644 --- a/SofaGLFW/src/SofaGLFW/NullGUIEngine.cpp +++ b/SofaGLFW/src/SofaGLFW/NullGUIEngine.cpp @@ -21,8 +21,8 @@ ******************************************************************************/ #include #include -#include #include +#include namespace sofaglfw { @@ -70,8 +70,8 @@ sofa::type::Vec2i NullGUIEngine::getFrameBufferPixels(std::vector& pixe { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); - pixels.resize(viewport[2] * viewport[3] * 3); - glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); + pixels.resize(viewport[2] * viewport[3] * 4); + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); return {viewport[2], viewport[3]}; } diff --git a/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp b/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp index 68ee16069e..56274d059b 100644 --- a/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp +++ b/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp @@ -41,21 +41,18 @@ #include #include #include -#include #include #include #include #include #include -#include - -#if SOFAGLFW_HAVE_FFMPEG == 1 -#include -#endif +#include +#include #include #include +#include using namespace sofa; using namespace sofa::gui::common; @@ -76,9 +73,6 @@ namespace sofaglfw SofaGLFWBaseGUI::SofaGLFWBaseGUI() { m_guiEngine = std::make_shared(); -#if SOFAGLFW_HAVE_FFMPEG == 1 - m_videoEncoder = std::make_unique(); -#endif } SofaGLFWBaseGUI::~SofaGLFWBaseGUI() @@ -485,6 +479,8 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations) bool running = true; std::size_t currentNbIterations = 0; std::stringstream tmpStr; + std::vector pixels; + while (s_numberOfActiveWindows > 0 && running) { SIMULATION_LOOP_SCOPE @@ -518,7 +514,8 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations) // Read framebuffer if(this->groot->getAnimate() && this->m_bVideoRecording) { - this->encodeFrame(); + const auto [width, height] = this->m_guiEngine->getFrameBufferPixels(pixels); + m_videoRecorderFFMPEG.addFrame(pixels.data(), width, height); } glfwSwapBuffers(glfwWindow); @@ -639,9 +636,9 @@ void SofaGLFWBaseGUI::terminate() if (m_guiEngine) m_guiEngine->terminate(); - if(m_videoEncoder) + if(m_bVideoRecording) { - m_videoEncoder->finish(); + m_videoRecorderFFMPEG.finishVideo(); } glfwTerminate(); @@ -1185,59 +1182,68 @@ bool SofaGLFWBaseGUI::centerWindow(GLFWwindow* window) } -void SofaGLFWBaseGUI::encodeFrame() +void SofaGLFWBaseGUI::toggleVideoRecording() { - if(!m_videoEncoder) + if(m_bVideoRecording) { - return; + m_bVideoRecording = false; + m_videoRecorderFFMPEG.finishVideo(); + msg_info("SofaGLFWBaseGUI") << "End recording"; } - - std::vector pixels; - const auto [width, height] = this->m_guiEngine->getFrameBufferPixels(pixels); - - if(!m_videoEncoder->isInitialized()) + else { - using sofa::helper::system::FileSystem; - std::string baseSceneFilename{}; - if (!this->getSceneFileName().empty()) - { - std::filesystem::path path(this->getSceneFileName()); - baseSceneFilename = path.stem().string(); - } - - const auto videoDirectory = FileSystem::append(sofa::helper::Utils::getSofaDataDirectory(), "recordings"); - FileSystem::ensureFolderExists(videoDirectory); - - const std::string currentTimeString = [](){ auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::ostringstream oss; oss << std::put_time(std::localtime(&t), "%Y%m%d%H%M%S"); return oss.str(); }(); + // Initialize recorder with default parameters + const int width = std::max(1, m_viewPortWidth); + const int height = std::max(1, m_viewPortHeight); + const unsigned int framerate = 60; + const unsigned int bitrate = 2000000; + const std::string codecExtension = "mp4"; + const std::string codecName = "yuv420p"; - const std::string videoExtension = ".mp4"; - const auto videoFilename = baseSceneFilename + "_" + currentTimeString + videoExtension; - const auto videoPath = FileSystem::append(videoDirectory,videoFilename); - - // assuming that the video path is unique and does not exist - // it would overwrite otherwise - constexpr int nbFramePerSecond = 60; - if(m_videoEncoder->init(videoPath.c_str(), width, height, nbFramePerSecond)) + if(initRecorder(width, height, framerate, bitrate, codecExtension, codecName)) { - msg_info("SofaGLFWBaseGUI") << "Writting in " << videoPath; + m_bVideoRecording = true; + msg_info("SofaGLFWBaseGUI") << "Start recording"; } else { - msg_error("SofaGLFWBaseGUI") << "Error while trying to write in " << videoPath; - return; + msg_error("SofaGLFWBaseGUI") << "Failed to initialize recorder"; } } - - - // Flip vertically (OpenGL has origin at bottom-left) - std::vector flipped(width * height * 3); - for (int y = 0; y < height; y++) { - memcpy(&flipped[y * width * 3], - &pixels[(height - 1 - y) * width * 3], - width * 3); +} + +bool SofaGLFWBaseGUI::initRecorder(int width, int height, unsigned int framerate, unsigned int bitrate, const std::string& codecExtension, const std::string& codecName) +{ + // Validate parameters + if (width <= 0 || height <= 0) + { + msg_error("SofaGLFWBaseGUI") << "Invalid video dimensions: " << width << "x" << height; + return false; } - - m_videoEncoder->encodeFrame(flipped.data(), width, height); + + bool res = true; + std::string ffmpeg_exec_path = ""; + const std::string ffmpegIniFilePath = sofa::helper::Utils::getSofaPathTo("etc/SofaGLFW.ini"); + std::map iniFileValues = sofa::helper::Utils::readBasicIniFile(ffmpegIniFilePath); + if (iniFileValues.find("FFMPEG_EXEC_PATH") != iniFileValues.end()) + { + // get absolute path of FFMPEG executable + msg_info("SofaGLFWBaseGUI") << " The file " << ffmpegIniFilePath << " points to " << ffmpeg_exec_path << " for the ffmpeg executable."; + ffmpeg_exec_path = sofa::helper::system::SetDirectory::GetRelativeFromProcess(iniFileValues["FFMPEG_EXEC_PATH"].c_str()); + } + else + { + msg_warning("SofaGLFWBaseGUI") << " The file " << helper::Utils::getSofaPathPrefix() <<"/etc/SofaGLFW.ini either doesn't exist or doesn't contain the string FFMPEG_EXEC_PATH." + " The initialization of the FFMPEG video recorder will likely fail. To fix this, provide a valid path to the ffmpeg executable inside this file using the syntax \"FFMPEG_EXEC_PATH=/usr/bin/ffmpeg\"."; + } + + const std::string videoFilename = m_videoRecorderFFMPEG.findFilename(framerate, bitrate / 1024, codecExtension); + + res = m_videoRecorderFFMPEG.init(ffmpeg_exec_path, videoFilename, width, height, framerate, bitrate, codecName); + + return res; } + + } // namespace sofaglfw diff --git a/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h b/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h index 83328d29b1..7e465fd819 100644 --- a/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h +++ b/SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h @@ -21,10 +21,9 @@ ******************************************************************************/ #pragma once -#include +#include #include #include -#include #include #include @@ -32,7 +31,7 @@ #include #include -#include +#include struct GLFWwindow; struct GLFWmonitor; @@ -115,30 +114,14 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer } void moveRayPickInteractor(int eventX, int eventY) override ; - void toggleVideoRecording() - { - if(m_videoEncoder) - { - m_bVideoRecording = !m_bVideoRecording; - if(m_bVideoRecording) - { - msg_info("SofaGLFWBaseGUI") << "Start recording"; - } - else - { - msg_info("SofaGLFWBaseGUI") << "End recording"; - } - } - } + void toggleVideoRecording(); + bool initRecorder(int width, int height, unsigned int framerate, unsigned int bitrate, const std::string& codecExtension, const std::string& codecName); bool isVideoRecording() const { return m_bVideoRecording; } - - void encodeFrame(); - static void triggerSceneAxis(sofa::simulation::NodeSPtr groot); private: @@ -187,7 +170,7 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer std::shared_ptr m_guiEngine; bool m_bVideoRecording {false}; - std::unique_ptr m_videoEncoder; + sofa::gl::VideoRecorderFFMPEG m_videoRecorderFFMPEG; }; } // namespace sofaglfw diff --git a/SofaGLFW/src/SofaGLFW/utils/VideoEncoder.h b/SofaGLFW/src/SofaGLFW/utils/VideoEncoder.h deleted file mode 100644 index 1ee87e0da8..0000000000 --- a/SofaGLFW/src/SofaGLFW/utils/VideoEncoder.h +++ /dev/null @@ -1,41 +0,0 @@ -/****************************************************************************** -* SOFA, Simulation Open-Framework Architecture * -* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * -* * -* This program is free software; you can redistribute it and/or modify it * -* under the terms of the GNU Lesser General Public License as published by * -* the Free Software Foundation; either version 2.1 of the License, or (at * -* your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, but WITHOUT * -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * -* for more details. * -* * -* You should have received a copy of the GNU Lesser General Public License * -* along with this program. If not, see . * -******************************************************************************* -* Authors: The SOFA Team and external contributors (see Authors.txt) * -* * -* Contact information: contact@sofa-framework.org * -******************************************************************************/ -#pragma once - -#include - -namespace sofaglfw -{ - -class VideoEncoder -{ -public: - virtual bool init(const char* filename, int width, int height, int fps) = 0; - virtual void encodeFrame(uint8_t* rgbData, int width, int height) = 0; - virtual void finish() = 0; - - [[nodiscard]] bool isInitialized() const { return m_bIsInitialized; } -protected: - bool m_bIsInitialized = false; -}; - -} // namespace sofaglfw diff --git a/SofaGLFW/src/SofaGLFW/utils/VideoEncoderFFMPEG.cpp b/SofaGLFW/src/SofaGLFW/utils/VideoEncoderFFMPEG.cpp deleted file mode 100644 index 46fe2d21f9..0000000000 --- a/SofaGLFW/src/SofaGLFW/utils/VideoEncoderFFMPEG.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/****************************************************************************** -* SOFA, Simulation Open-Framework Architecture * -* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * -* * -* This program is free software; you can redistribute it and/or modify it * -* under the terms of the GNU Lesser General Public License as published by * -* the Free Software Foundation; either version 2.1 of the License, or (at * -* your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, but WITHOUT * -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * -* for more details. * -* * -* You should have received a copy of the GNU Lesser General Public License * -* along with this program. If not, see . * -******************************************************************************* -* Authors: The SOFA Team and external contributors (see Authors.txt) * -* * -* Contact information: contact@sofa-framework.org * -******************************************************************************/ -#include - -extern "C" { -#include -#include -#include -#include -} - -namespace sofaglfw -{ - -bool VideoEncoderFFMPEG::init(const char* filename, int width, int height, int fps) -{ - // Print only error messages from ffmpeg - av_log_set_level(AV_LOG_ERROR); - - // Ensure dimensions are even (required for YUV420P) - m_encoderWidth = (width / 2) * 2; - m_encoderHeight = (height / 2) * 2; - - avformat_alloc_output_context2(&m_fmtCtx, nullptr, nullptr, filename); - if (!m_fmtCtx) return false; - - const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264); - if (!codec) return false; - - m_stream = avformat_new_stream(m_fmtCtx, nullptr); - if (!m_stream) return false; - - m_codecCtx = avcodec_alloc_context3(codec); - m_codecCtx->width = m_encoderWidth; - m_codecCtx->height = m_encoderHeight; - m_codecCtx->time_base = {1, fps}; - m_codecCtx->framerate = {fps, 1}; - m_codecCtx->pix_fmt = AV_PIX_FMT_YUV420P; - m_codecCtx->bit_rate = 2000000; - m_codecCtx->gop_size = 10; - m_codecCtx->max_b_frames = 1; - - if (m_fmtCtx->oformat->flags & AVFMT_GLOBALHEADER) - m_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; - - AVDictionary* opts = nullptr; - av_dict_set(&opts, "preset", "medium", 0); - av_dict_set(&opts, "crf", "23", 0); - - if (avcodec_open2(m_codecCtx, codec, &opts) < 0) { - av_dict_free(&opts); - return false; - } - av_dict_free(&opts); - - avcodec_parameters_from_context(m_stream->codecpar, m_codecCtx); - - m_frame = av_frame_alloc(); - m_frame->format = m_codecCtx->pix_fmt; - m_frame->width = m_encoderWidth; - m_frame->height = m_encoderHeight; - av_frame_get_buffer(m_frame, 0); - - m_pkt = av_packet_alloc(); - - if (avio_open(&m_fmtCtx->pb, filename, AVIO_FLAG_WRITE) < 0) return false; - if (avformat_write_header(m_fmtCtx, nullptr) < 0) return false; - - m_bIsInitialized = true; - - return true; -} - -void VideoEncoderFFMPEG::encodeFrame(uint8_t* rgbData, int fbWidth, int fbHeight) -{ - if(!m_bIsInitialized) - { - return; - } - - // Recreate sws context if framebuffer size changed - if (!m_swsCtx || fbWidth != m_encoderWidth || fbHeight != m_encoderHeight) { - if (m_swsCtx) sws_freeContext(m_swsCtx); - - m_swsCtx = sws_getContext(fbWidth, fbHeight, AV_PIX_FMT_RGB24, - m_encoderWidth, m_encoderHeight, AV_PIX_FMT_YUV420P, - SWS_BILINEAR, nullptr, nullptr, nullptr); - } - - uint8_t* inData[1] = {rgbData}; - int inLinesize[1] = {3 * fbWidth}; - - sws_scale(m_swsCtx, inData, inLinesize, 0, fbHeight, - m_frame->data, m_frame->linesize); - - m_frame->pts = m_frameCount++; - - avcodec_send_frame(m_codecCtx, m_frame); - while (avcodec_receive_packet(m_codecCtx, m_pkt) == 0) { - av_packet_rescale_ts(m_pkt, m_codecCtx->time_base, m_stream->time_base); - m_pkt->stream_index = m_stream->index; - av_interleaved_write_frame(m_fmtCtx, m_pkt); - av_packet_unref(m_pkt); - } -} - -void VideoEncoderFFMPEG::finish() -{ - if(!m_bIsInitialized) - { - return; - } - - avcodec_send_frame(m_codecCtx, nullptr); - while (avcodec_receive_packet(m_codecCtx, m_pkt) == 0) { - av_packet_rescale_ts(m_pkt, m_codecCtx->time_base, m_stream->time_base); - m_pkt->stream_index = m_stream->index; - av_interleaved_write_frame(m_fmtCtx, m_pkt); - av_packet_unref(m_pkt); - } - - av_write_trailer(m_fmtCtx); - - if (m_swsCtx) sws_freeContext(m_swsCtx); - if (m_frame) av_frame_free(&m_frame); - if (m_pkt) av_packet_free(&m_pkt); - if (m_codecCtx) avcodec_free_context(&m_codecCtx); - if (m_fmtCtx) { - avio_closep(&m_fmtCtx->pb); - avformat_free_context(m_fmtCtx); - } -} - -} // namespace sofaglfw diff --git a/SofaGLFW/src/SofaGLFW/utils/VideoEncoderFFMPEG.h b/SofaGLFW/src/SofaGLFW/utils/VideoEncoderFFMPEG.h deleted file mode 100644 index 1caaffe6b1..0000000000 --- a/SofaGLFW/src/SofaGLFW/utils/VideoEncoderFFMPEG.h +++ /dev/null @@ -1,60 +0,0 @@ -/****************************************************************************** -* SOFA, Simulation Open-Framework Architecture * -* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * -* * -* This program is free software; you can redistribute it and/or modify it * -* under the terms of the GNU Lesser General Public License as published by * -* the Free Software Foundation; either version 2.1 of the License, or (at * -* your option) any later version. * -* * -* This program is distributed in the hope that it will be useful, but WITHOUT * -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * -* for more details. * -* * -* You should have received a copy of the GNU Lesser General Public License * -* along with this program. If not, see . * -******************************************************************************* -* Authors: The SOFA Team and external contributors (see Authors.txt) * -* * -* Contact information: contact@sofa-framework.org * -******************************************************************************/ -#pragma once - -#include -#include - -struct AVFormatContext; -struct AVCodecContext; -struct AVStream; -struct AVFrame; -struct AVPacket; -struct SwsContext; - -namespace sofaglfw -{ - -class VideoEncoderFFMPEG : public VideoEncoder -{ -public: - VideoEncoderFFMPEG() = default; - ~VideoEncoderFFMPEG() = default; - - bool init(const char* filename, int width, int height, int fps) override; - void encodeFrame(uint8_t* rgbData, int width, int height) override; - void finish() override; - -private: - AVFormatContext* m_fmtCtx = nullptr; - AVCodecContext* m_codecCtx = nullptr; - AVStream* m_stream = nullptr; - AVFrame* m_frame = nullptr; - AVPacket* m_pkt = nullptr; - SwsContext* m_swsCtx = nullptr; - int m_frameCount = 0; - int m_encoderWidth = 0; - int m_encoderHeight = 0; - -}; - -} // namespace sofaglfw diff --git a/SofaImGui/src/SofaImGui/ImGuiGUIEngine.cpp b/SofaImGui/src/SofaImGui/ImGuiGUIEngine.cpp index 14016975b3..ea1936022b 100644 --- a/SofaImGui/src/SofaImGui/ImGuiGUIEngine.cpp +++ b/SofaImGui/src/SofaImGui/ImGuiGUIEngine.cpp @@ -919,11 +919,11 @@ type::Vec2i ImGuiGUIEngine::getFrameBufferPixels(std::vector& pixels) GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); - + if(m_pboSize[0] != viewport[2] || m_pboSize[1] != viewport[3]) { - // Size for your frame (e.g., 1920x1080 RGB) - int size = viewport[2] * viewport[3] * 3; + // Size for your frame (e.g., 1920x1080 RGBA) + int size = viewport[2] * viewport[3] * 4; for (int i = 0; i < s_NB_PBOS; i++) { glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbos[i]); @@ -939,14 +939,14 @@ type::Vec2i ImGuiGUIEngine::getFrameBufferPixels(std::vector& pixels) // Read to PBO (asynchronous) glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbos[readIndex]); - glReadPixels(0, 0, viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, 0); + glReadPixels(0, 0, viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, 0); // Map and process previous frame glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbos[processIndex]); void* data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); if (data) { - int size = viewport[2] * viewport[3] * 3; + int size = viewport[2] * viewport[3] * 4; pixels.resize(size); memcpy(pixels.data(), data, size); glUnmapBuffer(GL_PIXEL_PACK_BUFFER);