diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index e7362930d..9a7040223 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -130,6 +130,9 @@ IrrDriver::IrrDriver() m_boundingboxesviz = false; m_last_light_bucket_distance = 0; memset(object_count, 0, sizeof(object_count)); + + m_avi_writer = NULL; + m_request_recording = false; } // IrrDriver // ---------------------------------------------------------------------------- @@ -137,6 +140,8 @@ IrrDriver::IrrDriver() */ IrrDriver::~IrrDriver() { + stopRecording(); + // Note that we can not simply delete m_post_processing here: // m_post_processing uses a material that has a reference to // m_post_processing (for a callback). So when the material is @@ -2118,6 +2123,56 @@ void IrrDriver::doScreenShot() image->drop(); } // doScreenShot + +void IrrDriver::startRecording() +{ + if (m_avi_writer != NULL) + return; + + time_t rawtime; + time(&rawtime); + tm* timeInfo = localtime(&rawtime); + char time_buffer[256]; + sprintf(time_buffer, "%i.%02i.%02i_%02i.%02i.%02i", + timeInfo->tm_year + 1900, timeInfo->tm_mon+1, + timeInfo->tm_mday, timeInfo->tm_hour, + timeInfo->tm_min, timeInfo->tm_sec); + + std::string track_name = World::getWorld() != NULL ? + race_manager->getTrackName() : "menu"; + + std::string path = file_manager->getScreenshotDir() + track_name + "-" + + time_buffer + ".avi"; + + m_request_recording = true; + int msec_per_frame = 1000 / m_video_driver->getFPS(); + m_avi_writer = new AVIWriter(); + m_avi_writer->createFile(path, AVI_FORMAT_JPG, m_actual_screen_size.Width, + m_actual_screen_size.Height, msec_per_frame, 24, 70); + +} + +void IrrDriver::stopRecording() +{ + if (m_avi_writer == NULL) + return; + + m_avi_writer->closeFile(); + delete m_avi_writer; + m_avi_writer = NULL; + m_request_recording = false; + + RaceGUIBase* base = World::getWorld() ? World::getWorld()->getRaceGUI() + : NULL; + if (base) + { + std::string path = file_manager->getScreenshotDir(); + base->addMessage( + core::stringw(("Video saved in\n" + path).c_str()), + NULL, 2.0f, video::SColor(255,255,255,255), true, false); + } +} + // ---------------------------------------------------------------------------- /** Update, called once per frame. * \param dt Time since last update @@ -2207,6 +2262,37 @@ void IrrDriver::update(float dt) // menu. //if(World::getWorld() && World::getWorld()->isRacePhase()) // printRenderStats(); + + if (m_request_recording == true && m_avi_writer != NULL) + { + video::IImage* image = m_video_driver->createScreenShot(); + + if (image != NULL) + { + int bits = image->getBytesPerPixel(); + int w = image->getDimension().Width; + int h = image->getDimension().Height; + + unsigned char* img = (unsigned char*)image->lock(); + + int buf_length = bits * w * h; + unsigned char* img_jpg = new unsigned char[buf_length]; + + int len = m_avi_writer->bmpToJpg(img, img_jpg, buf_length, w, h, + bits, 70); + m_avi_writer->addImage(img_jpg, len); + + delete[] img_jpg; + + image->unlock(); + image->drop(); + + // update msec per frame using the fps during recording to get + // something closer to the real value + int msec_per_frame = 1000 / m_video_driver->getFPS(); + m_avi_writer->updateMsecPerFrame(msec_per_frame); + } + } } // update // ---------------------------------------------------------------------------- diff --git a/src/graphics/irr_driver.hpp b/src/graphics/irr_driver.hpp index 606d7e2fe..38f02fd2e 100644 --- a/src/graphics/irr_driver.hpp +++ b/src/graphics/irr_driver.hpp @@ -42,6 +42,7 @@ #include "graphics/wind.hpp" #include "io/file_manager.hpp" #include "utils/aligned_array.hpp" +#include "utils/avi_writer.hpp" #include "utils/no_copy.hpp" #include "utils/ptr_vector.hpp" #include "utils/vec3.hpp" @@ -306,6 +307,9 @@ private: float m_ssao_radius; float m_ssao_k; float m_ssao_sigma; + + bool m_request_recording; + AVIWriter* m_avi_writer; #ifdef DEBUG /** Used to visualise skeletons. */ @@ -429,6 +433,8 @@ public: void printRenderStats(); bool supportsSplatting(); void requestScreenshot(); + void startRecording(); + void stopRecording(); void setTextureErrorMessage(const std::string &error, const std::string &detail=""); void unsetTextureErrorMessage(); diff --git a/src/utils/avi_writer.cpp b/src/utils/avi_writer.cpp new file mode 100644 index 000000000..d7373a14c --- /dev/null +++ b/src/utils/avi_writer.cpp @@ -0,0 +1,374 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2015 Dawid Gan +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "avi_writer.hpp" + + +bool AVIWriter::addJUNKChunk(std::string str, unsigned int min_size) +{ + int size = str.size() < min_size ? min_size : str.size() + 1; + size = (size + 1) & 0xfffffffe; + + CHUNK chunk; + chunk.fcc = FOURCC('J', 'U', 'N', 'K'); + chunk.cb = size; + + char buffer[size]; + memset(buffer, '\0', size); + strcpy(buffer, str.c_str()); + + int num = fwrite(&chunk, 1, sizeof(chunk), m_file); + if (num != sizeof(chunk)) + goto error; + + num = fwrite(buffer, 1, size * sizeof(char), m_file); + if (num != size) + goto error; + + m_last_junk_chunk = ftell(m_file); + if (m_last_junk_chunk < 0) + goto error; + + return true; + +error: + closeFile(true); + return false; +} + +bool AVIWriter::addImage(unsigned char* buffer, int buf_size) +{ + if (m_file == NULL) + return false; + + int num = ftell(m_file); + if (num < 0) + goto error; + + if (m_total_frames >= MAX_FRAMES) + goto error; + + CHUNK chunk; + chunk.fcc = m_chunk_fcc; + chunk.cb = buf_size; + + m_index_table[m_total_frames].Offset = num; + m_index_table[m_total_frames].Length = chunk.cb; + m_index_table[m_total_frames].fcc = chunk.fcc; + + num = fwrite(&chunk, 1, sizeof(chunk), m_file); + if (num != sizeof(chunk)) + goto error; + + num = fwrite(buffer, 1, buf_size, m_file); + if (num != buf_size) + goto error; + + int fill_size; fill_size = (sizeof(chunk) + buf_size) & 0x00000001; + if (fill_size > 0) + { + uint32_t filler = 0; + num = fwrite(&filler, 1, fill_size, m_file); + if (num != fill_size) + goto error; + } + + m_stream_bytes += sizeof(chunk) + buf_size + fill_size; + m_total_frames++; + + num = ftell(m_file); + if (num < 0) + goto error; + + if (((num - m_last_junk_chunk) > 20000) && (!addJUNKChunk("", 1))) + goto error; + + // check if we reached the file size limit + if (num >= MAX_FILE_SIZE) + goto error; + + return true; + +error: + closeFile(true); + return false; +} + +bool AVIWriter::closeFile(bool delete_file) +{ + if (m_file == NULL) + return false; + + if (delete_file) + goto error; + + // add the index + int idx_start; idx_start = ftell(m_file); + if (idx_start < 0) + goto error; + + CHUNK chunk; + chunk.fcc = FOURCC('i', 'd', 'x', '1'); + chunk.cb = sizeof(AVIINDEXENTRY) * m_total_frames; + + int num; num = fwrite(&chunk, 1, sizeof(chunk), m_file); + if (num != sizeof(chunk)) + goto error; + + for (unsigned int i = 0; i < m_total_frames; i++) + { + AVIINDEXENTRY Index; + Index.ckid = m_index_table[i].fcc; + Index.dwFlags = AVIIF_KEYFRAME; + Index.dwChunkOffset = m_index_table[i].Offset; + Index.dwChunkLength = m_index_table[i].Length; + + num = fwrite(&Index, 1, sizeof(Index), m_file); + if (num != sizeof(Index)) + goto error; + } + + // update the header + if (m_total_frames > 0 && m_msec_per_frame > 0) + { + num = fseek(m_file, 0, SEEK_END); + if (num < 0) + goto error; + + int size; size = ftell(m_file); + if (size < 0) + goto error; + + num = fseek(m_file, 0, SEEK_SET); + if (num < 0) + goto error; + + m_avi_hdr.riff.cb = size - sizeof(m_avi_hdr.riff); + m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame; + m_avi_hdr.avih.dwMaxBytesPerSec = (uint32_t)(((m_stream_bytes / m_total_frames) * + m_format_hdr.strh.dwRate) / m_msec_per_frame + 0.5f); + m_avi_hdr.avih.dwTotalFrames = m_total_frames; + + num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file); + if (num != sizeof(m_avi_hdr)) + goto error; + + m_format_hdr.strh.dwScale = m_msec_per_frame; + m_format_hdr.strh.dwLength = m_total_frames; + + num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file); + if (num != sizeof(m_format_hdr)) + goto error; + } + + // update the movi section + m_movi_chunk.cb = idx_start - m_movi_start; + + num = fseek(m_file, m_movi_start - sizeof(m_movi_chunk), SEEK_SET); + if (num < 0) + goto error; + + num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file); + if (num != sizeof(m_movi_chunk)) + goto error; + + fclose(m_file); + m_file = NULL; + + return true; + +error: + fclose(m_file); + remove(m_filename.c_str()); + m_file = NULL; + return false; +} + +bool AVIWriter::createFile(std::string filename, AVIFormat avi_format, + int width, int height, unsigned int msec_per_frame, + int bits, int quality) +{ + if (m_file != NULL) + return false; + + if (width < 1 || height < 1) + return false; + + m_filename = filename; + m_stream_bytes = 0; + m_total_frames = 0; + m_msec_per_frame = msec_per_frame; + m_movi_start = 0; + m_last_junk_chunk = 0; + m_total_frames = 0; + + BitmapInfoHeader bitmap_hdr; + bitmap_hdr.biSize = sizeof(BitmapInfoHeader); + bitmap_hdr.biWidth = width; + bitmap_hdr.biHeight = height; + bitmap_hdr.biPlanes = 1; + bitmap_hdr.biBitCount = bits; + bitmap_hdr.biCompression = 0; + bitmap_hdr.biSizeImage = (width * height * 3 * bitmap_hdr.biPlanes); + bitmap_hdr.biXPelsPerMeter = 0; + bitmap_hdr.biYPelsPerMeter = 0; + bitmap_hdr.biClrUsed = 0; + bitmap_hdr.biClrImportant = 0; + + memset(&m_avi_hdr, '\0', sizeof(m_avi_hdr)); + m_avi_hdr.riff.fcc = FOURCC('R', 'I', 'F', 'F'); + m_avi_hdr.riff.cb = 0; // update when finished (size of the file - 8) + m_avi_hdr.avi = FOURCC('A', 'V', 'I', ' '); + m_avi_hdr.list1.fcc = FOURCC('L', 'I', 'S', 'T'); + m_avi_hdr.list1.cb = 0; + m_avi_hdr.hdrl = FOURCC('h', 'd', 'r', 'l'); + m_avi_hdr.avihhdr.fcc = FOURCC('a', 'v', 'i', 'h'); + m_avi_hdr.avihhdr.cb = sizeof(m_avi_hdr.avih); + m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame; + m_avi_hdr.avih.dwMaxBytesPerSec = 0; // update when finished + m_avi_hdr.avih.dwPaddingGranularity = 0; + m_avi_hdr.avih.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX; + m_avi_hdr.avih.dwTotalFrames = 0; // update when finished + m_avi_hdr.avih.dwInitialFrames = 0; + m_avi_hdr.avih.dwStreams = 1; // 1 = video, 2 = video and audio + m_avi_hdr.avih.dwSuggestedBufferSize = 0; // can be just 0 + m_avi_hdr.avih.dwWidth = width; + m_avi_hdr.avih.dwHeight = height; + + m_format_hdr.list.fcc = FOURCC('L', 'I', 'S', 'T'); + m_format_hdr.list.cb = (sizeof(m_format_hdr) - 8) + sizeof(BitmapInfoHeader); + m_format_hdr.strl = FOURCC('s', 't', 'r', 'l'); + m_format_hdr.strhhdr.fcc = FOURCC('s', 't', 'r', 'h'); + m_format_hdr.strhhdr.cb = sizeof(m_format_hdr.strh); + m_format_hdr.strh.fccType = FOURCC('v', 'i', 'd', 's'); + m_format_hdr.strh.fccHandler = CC_DIB; + m_format_hdr.strh.dwFlags = 0; + m_format_hdr.strh.wPriority = 0; + m_format_hdr.strh.wLanguage = 0; + m_format_hdr.strh.dwInitialFrames = 0; + m_format_hdr.strh.dwScale = m_msec_per_frame; + m_format_hdr.strh.dwRate = 1000; + m_format_hdr.strh.dwStart = 0; + m_format_hdr.strh.dwLength = 0; // update when finished + m_format_hdr.strh.dwSuggestedBufferSize = 0; // can be just 0 + m_format_hdr.strh.dwQuality = quality * 100; + m_format_hdr.strh.dwSampleSize = 0; + m_format_hdr.strh.Left = 0; + m_format_hdr.strh.Top = 0; + m_format_hdr.strh.Right = m_avi_hdr.avih.dwWidth; + m_format_hdr.strh.Bottom = m_avi_hdr.avih.dwHeight; + m_format_hdr.strfhdr.fcc = FOURCC('s', 't', 'r', 'f'); + m_format_hdr.strfhdr.cb = sizeof(BitmapInfoHeader); + + //Format specific changes + if (avi_format == AVI_FORMAT_JPG) + { + m_format_hdr.strh.fccHandler = CC_MJPG; + bitmap_hdr.biCompression = FOURCC('M', 'J', 'P', 'G'); + m_chunk_fcc = FOURCC('0', '0', 'd', 'c'); + } + else if (avi_format == AVI_FORMAT_BMP) + { + bitmap_hdr.biHeight = -height; + bitmap_hdr.biCompression = 0; + m_chunk_fcc = FOURCC('0', '0', 'd', 'b'); + } + + const uint32_t fcc_movi = FOURCC('m', 'o', 'v', 'i'); + + m_file = fopen(filename.c_str(), "wb"); + if (m_file == NULL) + return false; + + int num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file); + if (num != sizeof(m_avi_hdr)) + goto error; + + num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file); + if (num != sizeof(m_format_hdr)) + goto error; + + num = fwrite(&bitmap_hdr, 1, sizeof(BitmapInfoHeader), m_file); + if (num != sizeof(BitmapInfoHeader)) + goto error; + + m_end_of_header = ftell(m_file); + if (m_end_of_header < 0) + goto error; + + if (!addJUNKChunk("", 2840)) + goto error; + + m_avi_hdr.list1.cb = m_end_of_header - sizeof(m_avi_hdr.riff) - + sizeof(m_avi_hdr.avi) - sizeof(m_avi_hdr.list1); + m_movi_chunk.fcc = FOURCC('L', 'I', 'S', 'T'); + m_movi_chunk.cb = 0; // update when finished + + num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file); + if (num != sizeof(m_movi_chunk)) + goto error; + + m_movi_start = ftell(m_file); + if (m_movi_start < 0) + goto error; + + num = fwrite(&fcc_movi, 1, sizeof(fcc_movi), m_file); + if (num != sizeof(fcc_movi)) + goto error; + + return true; + +error: + closeFile(true); + return false; +} + +int AVIWriter::bmpToJpg(unsigned char* image_data, unsigned char* image_output, + unsigned long buf_length, unsigned int width, + unsigned int height, int num_channels, int quality) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + jpeg_create_compress(&cinfo); + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = num_channels; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, true); + + jpeg_mem_dest(&cinfo, &image_output, &buf_length); + + jpeg_start_compress(&cinfo, true); + + JSAMPROW jrow[1]; + while (cinfo.next_scanline < cinfo.image_height) + { + jrow[0] = &image_data[cinfo.next_scanline * width * num_channels]; + jpeg_write_scanlines(&cinfo, jrow, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return buf_length; +} diff --git a/src/utils/avi_writer.hpp b/src/utils/avi_writer.hpp new file mode 100644 index 000000000..bb1e727e4 --- /dev/null +++ b/src/utils/avi_writer.hpp @@ -0,0 +1,178 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2015 Dawid Gan +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 3 +// 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include +#include + +#include + +#define FOURCC(a,b,c,d) ((uint32_t) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a))) + +const uint32_t CC_MJPG = FOURCC('m', 'j', 'p', 'g'); +const uint32_t CC_DIB = FOURCC('\0', '\0', '\0', '\0'); +const uint32_t CC_VIDS = FOURCC('v', 'i', 'd', 's'); + +const uint32_t AVIF_HASINDEX = 0x00000010; +const uint32_t AVIF_MUSTUSEINDEX = 0x00000020; +const uint32_t AVIF_ISINTERLEAVED = 0x00000100; +const uint32_t AVIF_TRUSTCKTYPE = 0x00000800; +const uint32_t AVIF_WASCAPTUREFILE = 0x00010000; +const uint32_t AVIF_COPYRIGHTED = 0x00020000; + +const uint32_t AVISF_DISABLED = 0x00000001; +const uint32_t AVISF_VIDEO_PALCHANGES = 0x00010000; + +const uint32_t AVIIF_LIST = 0x00000001; +const uint32_t AVIIF_KEYFRAME = 0x00000010; +const uint32_t AVIIF_FIRSTPART = 0x00000020; +const uint32_t AVIIF_LASTPART = 0x00000040; +const uint32_t AVIIF_MIDPART = 0x00000060; +const uint32_t AVIIF_NOTIME = 0x00000100; +const uint32_t AVIIF_COMPUSE = 0x0FFF0000; + +enum AVIFormat {AVI_FORMAT_BMP, AVI_FORMAT_JPG}; +const int MAX_FRAMES = 1000000; +const int MAX_FILE_SIZE = 2000000000; + +struct MainAVIHeader +{ + uint32_t dwMicroSecPerFrame; + uint32_t dwMaxBytesPerSec; + uint32_t dwPaddingGranularity; + uint32_t dwFlags; + uint32_t dwTotalFrames; + uint32_t dwInitialFrames; + uint32_t dwStreams; + uint32_t dwSuggestedBufferSize; + uint32_t dwWidth; + uint32_t dwHeight; + uint32_t dwReserved[4]; +}; + +struct AVIStreamHeader +{ + uint32_t fccType; + uint32_t fccHandler; + uint32_t dwFlags; + uint16_t wPriority; + uint16_t wLanguage; + uint32_t dwInitialFrames; + uint32_t dwScale; + uint32_t dwRate; + uint32_t dwStart; + uint32_t dwLength; + uint32_t dwSuggestedBufferSize; + uint32_t dwQuality; + uint32_t dwSampleSize; + uint16_t Left; + uint16_t Top; + uint16_t Right; + uint16_t Bottom; +}; + +struct BitmapInfoHeader +{ + uint32_t biSize; + uint32_t biWidth; + uint32_t biHeight; + uint16_t biPlanes; + uint16_t biBitCount; + uint32_t biCompression; + uint32_t biSizeImage; + uint32_t biXPelsPerMeter; + uint32_t biYPelsPerMeter; + uint32_t biClrUsed; + uint32_t biClrImportant; +}; + +struct AVIINDEXENTRY +{ + uint32_t ckid; + uint32_t dwFlags; + uint32_t dwChunkOffset; + uint32_t dwChunkLength; +}; + +struct CHUNK +{ + uint32_t fcc; + uint32_t cb; +}; + +struct AVIHeader +{ + CHUNK riff; + uint32_t avi; + CHUNK list1; + uint32_t hdrl; + CHUNK avihhdr; + MainAVIHeader avih; +}; + +struct FormatHeader +{ + CHUNK list; + uint32_t strl; + CHUNK strhhdr; + AVIStreamHeader strh; + CHUNK strfhdr; +}; + +struct IndexTable +{ + uint32_t Offset; + uint32_t Length; + uint32_t fcc; +}; + + +class AVIWriter +{ +private: + FILE* m_file; + std::string m_filename; + int m_last_junk_chunk; + int m_end_of_header; + int m_movi_start; + unsigned int m_msec_per_frame; + unsigned int m_stream_bytes; + unsigned int m_total_frames; + AVIHeader m_avi_hdr; + CHUNK m_movi_chunk; + FormatHeader m_format_hdr; + IndexTable m_index_table[MAX_FRAMES]; + uint32_t m_chunk_fcc; + + bool addJUNKChunk(std::string str, unsigned int min_size); + +public: + AVIWriter() {m_file = NULL;} + + bool addImage(unsigned char* buffer, int size); + bool closeFile(bool delete_file = false); + bool createFile(std::string filename, AVIFormat avi_format, int width, + int height, unsigned int msec_per_frame, int bits, + int quality); + + void updateMsecPerFrame(unsigned int value) + {m_msec_per_frame = (m_msec_per_frame + value) / 2;} + + int bmpToJpg(unsigned char* image_data, unsigned char* image_output, + unsigned long buf_length, unsigned int width, + unsigned int height, int num_channels, int quality); +}; diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 17544f18d..4498b2cec 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -107,7 +107,9 @@ enum DebugMenuCommand DEBUG_VISUAL_VALUES, DEBUG_PRINT_START_POS, DEBUG_ADJUST_LIGHTS, - DEBUG_SCRIPT_CONSOLE + DEBUG_SCRIPT_CONSOLE, + DEBUG_START_RECORDING, + DEBUG_STOP_RECORDING }; // DebugMenuCommand // ----------------------------------------------------------------------------- @@ -566,6 +568,14 @@ bool handleContextMenuAction(s32 cmdID) { ScriptingConsole* console = new ScriptingConsole(); } + else if (cmdID == DEBUG_START_RECORDING) + { + irr_driver->startRecording(); + } + else if (cmdID == DEBUG_STOP_RECORDING) + { + irr_driver->stopRecording(); + } return false; } @@ -642,6 +652,11 @@ bool onEvent(const SEvent &event) sub->addItem(L"Normal view (Ctrl + F2)", DEBUG_GUI_CAM_NORMAL); sub->addItem(L"Toggle smooth camera", DEBUG_GUI_CAM_SMOOTH); sub->addItem(L"Attach fps camera to kart", DEBUG_GUI_CAM_ATTACH); + + int idx = mnu->addItem(L"Recording >",-1,true, true); + sub = mnu->getSubMenu(idx); + sub->addItem(L"Start recording", DEBUG_START_RECORDING); + sub->addItem(L"Stop recording", DEBUG_STOP_RECORDING); mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES);