SequenceBatchRenderDialog.cpp 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. // Description : A dialog for batch-rendering sequences
  9. #include "EditorDefs.h"
  10. #include "SequenceBatchRenderDialog.h"
  11. #include "TrackViewMessageBox.h"
  12. #include <Atom/RPI.Public/ViewProviderBus.h>
  13. #include <AzCore/Component/ComponentApplication.h>
  14. #include <AzFramework/Windowing/WindowBus.h>
  15. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  16. // Qt
  17. #include <QAction>
  18. #include <QFileDialog>
  19. #include <QStringListModel>
  20. #include <QtConcurrent>
  21. // CryCommon
  22. #include <CryCommon/Maestro/Types/AnimNodeType.h>
  23. // Editor
  24. #include "MainWindow.h"
  25. #include "CustomResolutionDlg.h"
  26. #include "ViewPane.h"
  27. #include "GameEngine.h"
  28. #include "Include/ICommandManager.h"
  29. #include "CryEdit.h"
  30. #include "Viewport.h"
  31. AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
  32. #include <TrackView/ui_SequenceBatchRenderDialog.h>
  33. AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
  34. namespace
  35. {
  36. const int g_useActiveViewportResolution = -1; // reserved value to indicate the use of the active viewport resolution
  37. int resolutions[][2] = {
  38. {1280, 720},
  39. {1920, 1080},
  40. {1998, 1080},
  41. {2048, 858},
  42. {2560, 1440},
  43. {3840, 2160},
  44. { g_useActiveViewportResolution, g_useActiveViewportResolution } // active viewport res must be the last element of the resolution array
  45. };
  46. // cached current active viewport resolution
  47. int activeViewportWidth;
  48. int activeViewportHeight;
  49. struct SFPSPair
  50. {
  51. int fps;
  52. const char* fpsDesc;
  53. } fpsOptions[] = {
  54. {24, "Film(24)"}, {25, "PAL(25)"}, {30, "NTSC(30)"},
  55. {48, "Show(48)"}, {50, "PAL Field(50)"}, {60, "NTSC Field(60)"}
  56. };
  57. // currently supported file extensions
  58. const char* imageFormatExtensions[] = {"dds", "ppm"};
  59. const char defaultPresetFilename[] = "defaultBatchRender.preset";
  60. const char customResFormat[] = "Custom(%1 x %2)...";
  61. const char debugInfo[] = "debuginfo";
  62. const int kBatchRenderFileVersion = 2; // This version number should be incremented every time available options like the list of formats,
  63. // the list of buffers change.
  64. // get the actual render width to use (substitutes active viewport width if needed)
  65. int getResWidth(int renderItemWidth)
  66. {
  67. return (renderItemWidth == g_useActiveViewportResolution) ? activeViewportWidth : renderItemWidth;
  68. }
  69. // get the actual render height to use (substitutes active viewport height if needed)
  70. int getResHeight(int renderItemHeight)
  71. {
  72. return (renderItemHeight == g_useActiveViewportResolution) ? activeViewportHeight : renderItemHeight;
  73. }
  74. }
  75. namespace TrackView
  76. {
  77. AZ_CVAR(
  78. int,
  79. tv_SkipFramesCount,
  80. 30,
  81. nullptr,
  82. AZ::ConsoleFunctorFlags::Null,
  83. "Set the number of frames to skip during the warm up and switching to the Game Mode.");
  84. }; // namespace TrackView
  85. CSequenceBatchRenderDialog::CSequenceBatchRenderDialog(float fps, QWidget* pParent /* = nullptr */)
  86. : QDialog(pParent)
  87. , m_fpsForTimeToFrameConversion(fps)
  88. , m_customResH(0)
  89. , m_customResW(0)
  90. , m_customFPS(0)
  91. , m_bFFMPEGCommandAvailable(false)
  92. , m_ui(new Ui::SequenceBatchRenderDialog)
  93. , m_renderListModel(new QStringListModel(this))
  94. , CV_TrackViewRenderOutputCapturing(0)
  95. , m_editorIdleProcessingEnabled(true)
  96. , m_prefixValidator(new CPrefixValidator(this))
  97. {
  98. m_ui->setupUi(this);
  99. setFixedSize(size());
  100. m_ui->m_renderList->setModel(m_renderListModel);
  101. OnInitDialog();
  102. connect(&m_renderTimer, &QTimer::timeout, this, &CSequenceBatchRenderDialog::OnKickIdleTimout);
  103. m_renderTimer.setInterval(0);
  104. m_renderTimer.setSingleShot(true);
  105. REGISTER_CVAR3("TrackViewRenderOutputCapturing", CV_TrackViewRenderOutputCapturing, 0, VF_NULL, "Set to 1 when Track View is actively capturing render output.");
  106. }
  107. CSequenceBatchRenderDialog::~CSequenceBatchRenderDialog()
  108. = default;
  109. void CSequenceBatchRenderDialog::reject()
  110. {
  111. if (m_renderContext.IsInRendering())
  112. {
  113. OnCancelRender();
  114. }
  115. else
  116. {
  117. QDialog::reject();
  118. }
  119. }
  120. void CSequenceBatchRenderDialog::OnInitDialog()
  121. {
  122. QAction* browseAction = m_ui->m_destinationEdit->addAction(style()->standardPixmap(QStyle::SP_DirOpenIcon), QLineEdit::TrailingPosition);
  123. connect(browseAction, &QAction::triggered, this, [=]()
  124. {
  125. const QString dir = QFileDialog::getExistingDirectory(this);
  126. if (!dir.isEmpty())
  127. {
  128. m_ui->m_destinationEdit->setText(dir);
  129. }
  130. });
  131. void(QComboBox::* activated)(int) = &QComboBox::activated;
  132. void(QSpinBox::* editingFinished)() = &QSpinBox::editingFinished;
  133. connect(m_ui->m_shotCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &CSequenceBatchRenderDialog::OnDirectorChange);
  134. connect(m_ui->BATCH_RENDER_ADD_SEQ, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnAddRenderItem);
  135. connect(m_ui->BATCH_RENDER_REMOVE_SEQ, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnRemoveRenderItem);
  136. connect(m_ui->BATCH_RENDER_CLEAR_SEQ, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnClearRenderItems);
  137. connect(m_ui->m_updateBtn, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnUpdateRenderItem);
  138. connect(m_ui->BATCH_RENDER_LOAD_PRESET, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnLoadPreset);
  139. connect(m_ui->BATCH_RENDER_SAVE_PRESET, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnSavePreset);
  140. connect(m_ui->BATCH_RENDER_LOAD_BATCH, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnLoadBatch);
  141. connect(m_ui->BATCH_RENDER_SAVE_BATCH, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnSaveBatch);
  142. connect(m_ui->m_pGoBtn, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnGo);
  143. connect(m_ui->CANCEL, &QPushButton::clicked, this, &CSequenceBatchRenderDialog::OnDone);
  144. connect(m_ui->m_sequenceCombo, activated, this, &CSequenceBatchRenderDialog::OnSequenceSelected);
  145. connect(m_ui->m_fpsCombo->lineEdit(), &QLineEdit::textEdited, this, &CSequenceBatchRenderDialog::OnFPSEditChange);
  146. connect(m_ui->m_fpsCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &CSequenceBatchRenderDialog::OnFPSChange);
  147. connect(m_ui->m_renderList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &CSequenceBatchRenderDialog::OnRenderItemSelChange);
  148. connect(m_ui->m_resolutionCombo, activated, this, &CSequenceBatchRenderDialog::OnResolutionSelected);
  149. connect(m_ui->m_startFrame, editingFinished, this, &CSequenceBatchRenderDialog::OnStartFrameChange);
  150. connect(m_ui->m_endFrame, editingFinished, this, &CSequenceBatchRenderDialog::OnEndFrameChange);
  151. connect(m_ui->m_imageFormatCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &CSequenceBatchRenderDialog::OnImageFormatChange);
  152. connect(m_ui->m_cvarsEdit,&QTextEdit::textChanged, this, &CSequenceBatchRenderDialog::OnVarsChange);
  153. connect(m_ui->BATCH_RENDER_FILE_PREFIX, &QLineEdit::textChanged, this, &CSequenceBatchRenderDialog::OnPrefixChange);
  154. connect(m_ui->m_disableDebugInfoCheckBox, &QCheckBox::toggled, this, &CSequenceBatchRenderDialog::OnDisableDebugInfoChange);
  155. connect(m_ui->m_createVideoCheckBox, &QCheckBox::toggled, this, &CSequenceBatchRenderDialog::OnCreateVideoChange);
  156. const int bigEnoughNumber = 1000000;
  157. m_ui->m_startFrame->setRange(0, bigEnoughNumber);
  158. m_ui->m_endFrame->setRange(0, bigEnoughNumber);
  159. // Fill the sequence combo box.
  160. bool activeSequenceWasSet = false;
  161. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  162. if (movieSystem)
  163. {
  164. for (int k = 0; k < movieSystem->GetNumSequences(); ++k)
  165. {
  166. IAnimSequence* pSequence = movieSystem->GetSequence(k);
  167. m_ui->m_sequenceCombo->addItem(pSequence->GetName());
  168. if (pSequence->IsActivated())
  169. {
  170. m_ui->m_sequenceCombo->setCurrentIndex(k);
  171. activeSequenceWasSet = true;
  172. }
  173. }
  174. }
  175. if (!activeSequenceWasSet)
  176. {
  177. m_ui->m_sequenceCombo->setCurrentIndex(0);
  178. }
  179. m_ui->m_fpsCombo->setEditable(true);
  180. // Fill the shot combos and the default frame range.
  181. OnSequenceSelected();
  182. // Fill the resolution combo box.
  183. for (int i = 0; i < arraysize(resolutions); ++i)
  184. {
  185. if (resolutions[i][0] == g_useActiveViewportResolution && resolutions[i][1] == g_useActiveViewportResolution)
  186. {
  187. m_ui->m_resolutionCombo->addItem(tr("Active View Resolution"));
  188. StashActiveViewportResolution(); // render dialog is modal, so we can stash the viewport res on init
  189. }
  190. else
  191. {
  192. m_ui->m_resolutionCombo->addItem(tr("%1 x %2").arg(resolutions[i][0]).arg(resolutions[i][1]));
  193. }
  194. }
  195. m_ui->m_resolutionCombo->addItem(tr("Custom..."));
  196. m_ui->m_resolutionCombo->setCurrentIndex(0);
  197. // Fill the FPS combo box.
  198. for (int i = 0; i < AZStd::size(fpsOptions); ++i)
  199. {
  200. m_ui->m_fpsCombo->addItem(fpsOptions[i].fpsDesc);
  201. }
  202. m_ui->m_fpsCombo->setCurrentIndex(0);
  203. // Fill the image format combo box.
  204. for (int i = 0; i < AZStd::size(imageFormatExtensions); ++i)
  205. {
  206. m_ui->m_imageFormatCombo->addItem(imageFormatExtensions[i]);
  207. }
  208. m_ui->m_imageFormatCombo->setCurrentIndex(0);
  209. m_ui->BATCH_RENDER_FILE_PREFIX->setText("Frame");
  210. m_ui->BATCH_RENDER_FILE_PREFIX->setValidator(m_prefixValidator.data());
  211. m_ui->m_progressStatusMsg->setText("Not running");
  212. m_ui->BATCH_RENDER_REMOVE_SEQ->setEnabled(false);
  213. m_ui->m_pGoBtn->setEnabled(false);
  214. m_ui->m_pGoBtn->setIcon(QPixmap(":/Trackview/clapperboard_ready.png"));
  215. m_ui->m_progressBar->setRange(0, 100);
  216. m_ui->BATCH_RENDER_FRAME_IN_FPS->setText(tr("In %1 FPS").arg(static_cast<int>(m_fpsForTimeToFrameConversion)));
  217. m_bFFMPEGCommandAvailable = GetIEditor()->GetICommandManager()->IsRegistered("plugin", "ffmpeg_encode");
  218. m_ffmpegPluginStatusMsg = m_bFFMPEGCommandAvailable ?
  219. QString("") :
  220. tr("FFMPEG plug-in isn't found(creating a video isn't supported).");
  221. m_ui->BATCH_RENDER_PRESS_ESC_TO_CANCEL->setText(m_ffmpegPluginStatusMsg);
  222. // Disable the create video checkbox if the ffmpeg command is not available
  223. if (!m_bFFMPEGCommandAvailable)
  224. {
  225. m_ui->m_createVideoCheckBox->setChecked(false);
  226. m_ui->m_createVideoCheckBox->setEnabled(false);
  227. }
  228. // Load previously saved options, if any.
  229. QString defaultPresetPath = Path::GetUserSandboxFolder();
  230. defaultPresetPath += defaultPresetFilename;
  231. if (CFileUtil::FileExists(defaultPresetPath))
  232. {
  233. LoadOutputOptions(defaultPresetPath);
  234. }
  235. CheckForEnableUpdateButton();
  236. }
  237. void CSequenceBatchRenderDialog::OnRenderItemSelChange()
  238. {
  239. // Enable/disable the 'remove'/'update' button properly.
  240. bool bNoSelection = !m_ui->m_renderList->selectionModel()->hasSelection();
  241. m_ui->BATCH_RENDER_REMOVE_SEQ->setEnabled(bNoSelection ? false : true);
  242. CheckForEnableUpdateButton();
  243. if (bNoSelection)
  244. {
  245. return;
  246. }
  247. // Apply the settings of the selected one to the dialog.
  248. const SRenderItem& item = m_renderItems[m_ui->m_renderList->currentIndex().row()];
  249. // sequence
  250. for (int i = 0; i < m_ui->m_sequenceCombo->count(); ++i)
  251. {
  252. const QString sequenceName = m_ui->m_sequenceCombo->itemText(i);
  253. if (sequenceName == item.pSequence->GetName())
  254. {
  255. m_ui->m_sequenceCombo->setCurrentIndex(i);
  256. OnSequenceSelected();
  257. break;
  258. }
  259. }
  260. // director
  261. for (int i = 0; i < m_ui->m_shotCombo->count(); ++i)
  262. {
  263. const QString directorName = m_ui->m_shotCombo->itemText(i);
  264. if (directorName == item.pDirectorNode->GetName())
  265. {
  266. m_ui->m_shotCombo->setCurrentIndex(i);
  267. break;
  268. }
  269. }
  270. // frame range
  271. m_ui->m_startFrame->setValue(static_cast<int>(item.frameRange.start * m_fpsForTimeToFrameConversion));
  272. m_ui->m_endFrame->setValue(static_cast<int>(item.frameRange.end * m_fpsForTimeToFrameConversion));
  273. // folder
  274. m_ui->m_destinationEdit->setText(item.folder);
  275. // fps
  276. bool bFound = false;
  277. for (int i = 0; i < arraysize(fpsOptions); ++i)
  278. {
  279. if (item.fps == fpsOptions[i].fps)
  280. {
  281. m_ui->m_fpsCombo->setCurrentIndex(i);
  282. bFound = true;
  283. break;
  284. }
  285. }
  286. if (bFound == false)
  287. {
  288. m_customFPS = item.fps;
  289. m_ui->m_fpsCombo->setCurrentText(QString::number(item.fps));
  290. }
  291. // format
  292. m_ui->m_imageFormatCombo->setCurrentText(item.imageFormat);
  293. // prefix
  294. m_ui->BATCH_RENDER_FILE_PREFIX->setText(item.prefix);
  295. m_ui->m_disableDebugInfoCheckBox->setChecked(item.disableDebugInfo);
  296. // create_video
  297. if (m_bFFMPEGCommandAvailable)
  298. {
  299. m_ui->m_createVideoCheckBox->setChecked(item.bCreateVideo);
  300. }
  301. // resolution
  302. bFound = false;
  303. for (int i = 0; i < arraysize(resolutions); ++i)
  304. {
  305. if (item.resW == resolutions[i][0] && item.resH == resolutions[i][1])
  306. {
  307. m_ui->m_resolutionCombo->setCurrentIndex(i);
  308. bFound = true;
  309. break;
  310. }
  311. }
  312. if (bFound == false)
  313. {
  314. int indexOfCustomRes = arraysize(resolutions);
  315. const QString resText = QString::fromLatin1(customResFormat).arg(item.resW).arg(item.resH);
  316. m_customResW = item.resW;
  317. m_customResH = item.resH;
  318. m_ui->m_resolutionCombo->removeItem(indexOfCustomRes);
  319. m_ui->m_resolutionCombo->addItem(resText);
  320. m_ui->m_resolutionCombo->setCurrentIndex(indexOfCustomRes);
  321. }
  322. // cvars
  323. QString cvarsText;
  324. for (size_t i = 0; i < item.cvars.size(); ++i)
  325. {
  326. cvarsText += item.cvars[static_cast<int>(i)];
  327. cvarsText += "\r\n";
  328. }
  329. m_ui->m_cvarsEdit->setPlainText(cvarsText);
  330. }
  331. void CSequenceBatchRenderDialog::CheckForEnableUpdateButton()
  332. {
  333. bool enable = false;
  334. // Enable the Update button if any ui elements are changed
  335. // from the currently selected render item.
  336. if (m_ui->m_renderList->selectionModel()->hasSelection())
  337. {
  338. SRenderItem item;
  339. if (SetUpNewRenderItem(item))
  340. {
  341. int index = m_ui->m_renderList->currentIndex().row();
  342. assert(index >= 0 && index < m_renderItems.size());
  343. enable = !(m_renderItems[index] == item);
  344. }
  345. }
  346. m_ui->m_updateBtn->setEnabled(enable);
  347. }
  348. void CSequenceBatchRenderDialog::OnAddRenderItem()
  349. {
  350. // If there is no director node, it cannot be added.
  351. if (m_ui->m_shotCombo->count() == 0)
  352. {
  353. QMessageBox::critical(this, tr("Cannot add"), tr("No director available!"));
  354. return;
  355. }
  356. // Set up a new render item.
  357. SRenderItem item;
  358. if (SetUpNewRenderItem(item) == false)
  359. {
  360. return;
  361. }
  362. // Check a duplication before adding.
  363. for (size_t i = 0; i < m_renderItems.size(); ++i)
  364. {
  365. if (m_renderItems[i] == item)
  366. {
  367. QMessageBox::critical(this, tr("Cannot add"), tr("The same item already exists"));
  368. return;
  369. }
  370. }
  371. AddItem(item);
  372. }
  373. void CSequenceBatchRenderDialog::OnRemoveRenderItem()
  374. {
  375. int index = m_ui->m_renderList->currentIndex().row();
  376. assert(index != CB_ERR);
  377. m_ui->m_renderList->model()->removeRow(index);
  378. m_renderItems.erase(m_renderItems.begin() + index);
  379. if (m_renderItems.empty())
  380. {
  381. m_ui->BATCH_RENDER_REMOVE_SEQ->setEnabled(false);
  382. m_ui->m_pGoBtn->setEnabled(false);
  383. }
  384. else
  385. {
  386. m_ui->m_renderList->setCurrentIndex(m_ui->m_renderList->model()->index(0, 0));
  387. OnRenderItemSelChange();
  388. }
  389. CheckForEnableUpdateButton();
  390. }
  391. void CSequenceBatchRenderDialog::OnClearRenderItems()
  392. {
  393. m_ui->m_renderList->model()->removeRows(0, m_ui->m_renderList->model()->rowCount());
  394. m_renderItems.clear();
  395. m_ui->BATCH_RENDER_REMOVE_SEQ->setEnabled(false);
  396. m_ui->m_pGoBtn->setEnabled(false);
  397. CheckForEnableUpdateButton();
  398. }
  399. void CSequenceBatchRenderDialog::OnUpdateRenderItem()
  400. {
  401. int index = m_ui->m_renderList->currentIndex().row();
  402. assert(index != -1);
  403. // Set up a new render item.
  404. SRenderItem item;
  405. if (!SetUpNewRenderItem(item))
  406. {
  407. return;
  408. }
  409. // Check a duplication before updating.
  410. for (size_t i = 0; i < m_renderItems.size(); ++i)
  411. {
  412. if (m_renderItems[i] == item)
  413. {
  414. QMessageBox::critical(this, tr("Cannot update"), tr("The same item already exists!"));
  415. return;
  416. }
  417. }
  418. // Update the item.
  419. m_renderItems[index] = item;
  420. // Update the list box, too.
  421. m_ui->m_renderList->model()->setData(m_ui->m_renderList->model()->index(index, 0), GetCaptureItemString(item));
  422. m_ui->m_updateBtn->setEnabled(false);
  423. }
  424. void CSequenceBatchRenderDialog::OnLoadPreset()
  425. {
  426. QString loadPath;
  427. if (CFileUtil::SelectFile("Preset Files (*.preset)", Path::GetUserSandboxFolder(), loadPath))
  428. {
  429. if (LoadOutputOptions(loadPath) == false)
  430. {
  431. QMessageBox::critical(this, tr("Cannot load"), tr("The file version is different!"));
  432. }
  433. }
  434. }
  435. void CSequenceBatchRenderDialog::OnSavePreset()
  436. {
  437. QString savePath;
  438. if (CFileUtil::SelectSaveFile("Preset Files (*.preset)", "preset", Path::GetUserSandboxFolder(), savePath))
  439. {
  440. SaveOutputOptions(savePath);
  441. }
  442. }
  443. void CSequenceBatchRenderDialog::StashActiveViewportResolution()
  444. {
  445. // stash active resolution in global vars
  446. activeViewportWidth = resolutions[0][0];
  447. activeViewportHeight = resolutions[0][1];
  448. CViewport* activeViewport = GetIEditor()->GetActiveView();
  449. if (activeViewport)
  450. {
  451. activeViewport->GetDimensions(&activeViewportWidth, &activeViewportHeight);
  452. }
  453. }
  454. void CSequenceBatchRenderDialog::OnGo()
  455. {
  456. if (m_renderContext.IsInRendering())
  457. {
  458. OnCancelRender();
  459. }
  460. else
  461. {
  462. // Start a new batch.
  463. m_ui->m_pGoBtn->setText("Cancel");
  464. m_ui->m_pGoBtn->setIcon(QPixmap(":/Trackview/clapperboard_cancel.png"));
  465. // Inform the movie system that it soon will be in a batch-rendering mode.
  466. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  467. if (movieSystem)
  468. {
  469. movieSystem->EnableBatchRenderMode(true);
  470. }
  471. // Initialize the context.
  472. InitializeContext();
  473. // Trigger the first item.
  474. OnMovieEvent(IMovieListener::eMovieEvent_Stopped, nullptr);
  475. }
  476. }
  477. void CSequenceBatchRenderDialog::OnMovieEvent(IMovieListener::EMovieEvent event, IAnimSequence* pSequence)
  478. {
  479. if (event == IMovieListener::eMovieEvent_Stopped
  480. || event == IMovieListener::eMovieEvent_Aborted)
  481. {
  482. // Finalize the current one, if any.
  483. if (pSequence)
  484. {
  485. EnterCaptureState(CaptureState::End);
  486. m_renderContext.endingSequence = pSequence;
  487. m_renderContext.canceled = (event == IMovieListener::eMovieEvent_Aborted);
  488. }
  489. else
  490. {
  491. // This is odd, but this is the condition that starts the first item capturing
  492. // when the user presses the start button.
  493. if (m_renderItems.size() > 0)
  494. {
  495. // Setup and trigger the first time
  496. m_renderContext.spentTime = 0.0f;
  497. m_renderContext.currentItemIndex = 0;
  498. CaptureItemStart();
  499. }
  500. }
  501. }
  502. }
  503. void CSequenceBatchRenderDialog::OnDone()
  504. {
  505. if (m_renderContext.IsInRendering())
  506. {
  507. OnCancelRender();
  508. }
  509. else
  510. {
  511. // Save options when closed.
  512. QString defaultPresetPath = Path::GetUserSandboxFolder();
  513. defaultPresetPath += defaultPresetFilename;
  514. SaveOutputOptions(defaultPresetPath);
  515. reject();
  516. }
  517. }
  518. void CSequenceBatchRenderDialog::OnSequenceSelected()
  519. {
  520. // Get the selected sequence.
  521. const QString seqName = m_ui->m_sequenceCombo->currentText();
  522. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  523. IAnimSequence* pSequence = movieSystem ? movieSystem->FindLegacySequenceByName(seqName.toUtf8().data()) : nullptr;
  524. if (pSequence)
  525. {
  526. // Adjust the frame range.
  527. float sFrame = pSequence->GetTimeRange().start * m_fpsForTimeToFrameConversion;
  528. float eFrame = pSequence->GetTimeRange().end * m_fpsForTimeToFrameConversion;
  529. m_ui->m_startFrame->setRange(0, static_cast<int>(eFrame));
  530. m_ui->m_endFrame->setRange(0, static_cast<int>(eFrame));
  531. // Set the default start/end frames properly.
  532. m_ui->m_startFrame->setValue(static_cast<int>(sFrame));
  533. m_ui->m_endFrame->setValue(static_cast<int>(eFrame));
  534. m_ui->m_shotCombo->clear();
  535. // Fill the shot combo box with the names of director nodes.
  536. for (int i = 0; i < pSequence->GetNodeCount(); ++i)
  537. {
  538. if (pSequence->GetNode(i)->GetType() == AnimNodeType::Director)
  539. {
  540. m_ui->m_shotCombo->addItem(pSequence->GetNode(i)->GetName());
  541. }
  542. }
  543. }
  544. m_ui->m_shotCombo->setCurrentIndex(0);
  545. CheckForEnableUpdateButton();
  546. }
  547. void CSequenceBatchRenderDialog::OnFPSEditChange()
  548. {
  549. const QString fpsText = m_ui->m_fpsCombo->currentText();
  550. bool ok;
  551. const int fps = fpsText.toInt(&ok);
  552. bool bInvalidInput = !ok || fps <= 0;
  553. if (bInvalidInput)
  554. {
  555. m_ui->m_fpsCombo->setCurrentIndex(0);
  556. }
  557. else
  558. {
  559. m_customFPS = fps;
  560. }
  561. CheckForEnableUpdateButton();
  562. }
  563. void CSequenceBatchRenderDialog::OnFPSChange(int itemIndex)
  564. {
  565. m_customFPS = fpsOptions[itemIndex].fps;
  566. CheckForEnableUpdateButton();
  567. }
  568. void CSequenceBatchRenderDialog::OnDirectorChange([[maybe_unused]] int itemIndex)
  569. {
  570. CheckForEnableUpdateButton();
  571. }
  572. void CSequenceBatchRenderDialog::OnImageFormatChange()
  573. {
  574. CheckForEnableUpdateButton();
  575. }
  576. void CSequenceBatchRenderDialog::OnResolutionSelected()
  577. {
  578. int indexOfCustomRes = arraysize(resolutions);
  579. if (m_ui->m_resolutionCombo->currentIndex() == indexOfCustomRes)
  580. {
  581. int defaultW;
  582. int defaultH;
  583. const QString currentCustomResText = m_ui->m_resolutionCombo->currentText();
  584. GetResolutionFromCustomResText(currentCustomResText.toStdString().c_str(), defaultW, defaultH);
  585. CCustomResolutionDlg resDlg(defaultW, defaultH, this);
  586. if (resDlg.exec() == QDialog::Accepted)
  587. {
  588. const int maxRes = 8192;
  589. m_customResW = min(resDlg.GetWidth(), maxRes);
  590. m_customResH = min(resDlg.GetHeight(), maxRes);
  591. const QString resText = QString(customResFormat).arg(m_customResW).arg(m_customResH);
  592. m_ui->m_resolutionCombo->setItemText(indexOfCustomRes, resText);
  593. m_ui->m_resolutionCombo->setCurrentIndex(indexOfCustomRes);
  594. }
  595. else
  596. {
  597. m_ui->m_resolutionCombo->setCurrentIndex(0);
  598. }
  599. }
  600. CheckForEnableUpdateButton();
  601. }
  602. void CSequenceBatchRenderDialog::SaveOutputOptions(const QString& pathname) const
  603. {
  604. XmlNodeRef batchRenderOptionsNode = XmlHelpers::CreateXmlNode("batchrenderoptions");
  605. batchRenderOptionsNode->setAttr("version", kBatchRenderFileVersion);
  606. // Resolution
  607. XmlNodeRef resolutionNode = batchRenderOptionsNode->newChild("resolution");
  608. resolutionNode->setAttr("cursel", m_ui->m_resolutionCombo->currentIndex());
  609. if (m_ui->m_resolutionCombo->currentIndex() == arraysize(resolutions))
  610. {
  611. const QString resText = m_ui->m_resolutionCombo->currentText();
  612. resolutionNode->setContent(resText.toUtf8().data());
  613. }
  614. // FPS
  615. XmlNodeRef fpsNode = batchRenderOptionsNode->newChild("fps");
  616. fpsNode->setAttr("cursel", m_ui->m_fpsCombo->currentIndex());
  617. const QString fpsText = m_ui->m_fpsCombo->currentText();
  618. if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->findText(fpsText) == -1)
  619. {
  620. fpsNode->setContent(fpsText.toUtf8().data());
  621. }
  622. // Capture options (format, buffer, prefix, create_video)
  623. XmlNodeRef imageNode = batchRenderOptionsNode->newChild("image");
  624. imageNode->setAttr("format", m_ui->m_imageFormatCombo->currentIndex() % arraysize(imageFormatExtensions));
  625. const QString prefix = m_ui->BATCH_RENDER_FILE_PREFIX->text();
  626. imageNode->setAttr("prefix", prefix.toUtf8().data());
  627. bool disableDebugInfo = m_ui->m_disableDebugInfoCheckBox->isChecked();
  628. imageNode->setAttr("disabledebuginfo", disableDebugInfo);
  629. bool bCreateVideoOn = m_ui->m_createVideoCheckBox->isChecked();
  630. imageNode->setAttr("createvideo", bCreateVideoOn);
  631. // Custom configs
  632. XmlNodeRef cvarsNode = batchRenderOptionsNode->newChild("cvars");
  633. const QStringList lines = m_ui->m_cvarsEdit->toPlainText().split(QStringLiteral("\n"));
  634. for (const QString& line : lines)
  635. {
  636. cvarsNode->newChild("cvar")->setContent(line.toUtf8().data());
  637. }
  638. // Destination
  639. XmlNodeRef destinationNode = batchRenderOptionsNode->newChild("destination");
  640. const QString destinationText = m_ui->m_destinationEdit->text();
  641. destinationNode->setContent(destinationText.toUtf8().data());
  642. batchRenderOptionsNode->saveToFile(pathname.toUtf8().data());
  643. }
  644. bool CSequenceBatchRenderDialog::GetResolutionFromCustomResText(const char* customResText, int& retCustomWidth, int& retCustomHeight) const
  645. {
  646. // initialize to first resolution preset as default if the sscanf below doesn't scan values successfully
  647. retCustomWidth = resolutions[0][0];
  648. retCustomHeight = resolutions[0][1];
  649. bool scanSuccess = false;
  650. int scannedWidth = retCustomWidth; // initialize with default fall-back values - they'll be overwritten in the case of a succesful sscanf below.
  651. int scannedHeight = retCustomHeight;
  652. scanSuccess = (azsscanf(customResText, "Custom(%d x %d)...", &scannedWidth, &scannedHeight) == 2);
  653. if (scanSuccess)
  654. {
  655. retCustomWidth = scannedWidth;
  656. retCustomHeight = scannedHeight;
  657. }
  658. return scanSuccess;
  659. }
  660. bool CSequenceBatchRenderDialog::LoadOutputOptions(const QString& pathname)
  661. {
  662. XmlNodeRef batchRenderOptionsNode = XmlHelpers::LoadXmlFromFile(pathname.toStdString().c_str());
  663. if (batchRenderOptionsNode == nullptr)
  664. {
  665. return true;
  666. }
  667. int version = 0;
  668. batchRenderOptionsNode->getAttr("version", version);
  669. if (version != kBatchRenderFileVersion)
  670. {
  671. return false;
  672. }
  673. // Resolution
  674. XmlNodeRef resolutionNode = batchRenderOptionsNode->findChild("resolution");
  675. if (resolutionNode)
  676. {
  677. int curSel = CB_ERR;
  678. resolutionNode->getAttr("cursel", curSel);
  679. if (curSel == arraysize(resolutions))
  680. {
  681. const QString customResText = resolutionNode->getContent();
  682. m_ui->m_resolutionCombo->setItemText(curSel, customResText);
  683. GetResolutionFromCustomResText(customResText.toStdString().c_str(), m_customResW, m_customResH);
  684. }
  685. m_ui->m_resolutionCombo->setCurrentIndex(curSel);
  686. }
  687. // FPS
  688. XmlNodeRef fpsNode = batchRenderOptionsNode->findChild("fps");
  689. if (fpsNode)
  690. {
  691. int curSel = -1;
  692. fpsNode->getAttr("cursel", curSel);
  693. if (curSel == -1)
  694. {
  695. m_ui->m_fpsCombo->setCurrentIndex(-1);
  696. m_ui->m_fpsCombo->setCurrentText(fpsNode->getContent());
  697. m_customFPS = QString::fromLatin1(fpsNode->getContent()).toInt();
  698. }
  699. else
  700. {
  701. m_ui->m_fpsCombo->setCurrentIndex(curSel);
  702. }
  703. }
  704. // Capture options (format, buffer, prefix, create_video)
  705. XmlNodeRef imageNode = batchRenderOptionsNode->findChild("image");
  706. if (imageNode)
  707. {
  708. int curSel = CB_ERR;
  709. imageNode->getAttr("format", curSel);
  710. m_ui->m_imageFormatCombo->setCurrentIndex(curSel);
  711. curSel = CB_ERR;
  712. m_ui->BATCH_RENDER_FILE_PREFIX->setText(imageNode->getAttr("prefix"));
  713. bool disableDebugInfo = false;
  714. imageNode->getAttr("disabledebuginfo", disableDebugInfo);
  715. m_ui->m_disableDebugInfoCheckBox->setChecked(disableDebugInfo);
  716. if (m_bFFMPEGCommandAvailable)
  717. {
  718. bool bCreateVideoOn = false;
  719. imageNode->getAttr("createvideo", bCreateVideoOn);
  720. m_ui->m_createVideoCheckBox->setChecked(bCreateVideoOn);
  721. }
  722. }
  723. // Custom configs
  724. XmlNodeRef cvarsNode = batchRenderOptionsNode->findChild("cvars");
  725. if (cvarsNode)
  726. {
  727. QString cvarsText;
  728. for (int i = 0; i < cvarsNode->getChildCount(); ++i)
  729. {
  730. cvarsText += cvarsNode->getChild(i)->getContent();
  731. if (i < cvarsNode->getChildCount() - 1)
  732. {
  733. cvarsText += QStringLiteral("\r\n");
  734. }
  735. }
  736. m_ui->m_cvarsEdit->setPlainText(cvarsText);
  737. }
  738. // Destination
  739. XmlNodeRef destinationNode = batchRenderOptionsNode->findChild("destination");
  740. if (destinationNode)
  741. {
  742. m_ui->m_destinationEdit->setText(destinationNode->getContent());
  743. }
  744. return true;
  745. }
  746. void CSequenceBatchRenderDialog::OnStartFrameChange()
  747. {
  748. if (m_ui->m_startFrame->value() >= m_ui->m_endFrame->value())
  749. {
  750. m_ui->m_endFrame->setValue(m_ui->m_startFrame->value() + 1);
  751. }
  752. CheckForEnableUpdateButton();
  753. }
  754. void CSequenceBatchRenderDialog::OnEndFrameChange()
  755. {
  756. if (m_ui->m_startFrame->value() >= m_ui->m_endFrame->value())
  757. {
  758. m_ui->m_startFrame->setValue(m_ui->m_endFrame->value() - 1);
  759. }
  760. CheckForEnableUpdateButton();
  761. }
  762. void CSequenceBatchRenderDialog::InitializeContext()
  763. {
  764. m_renderContext.currentItemIndex = 0;
  765. m_renderContext.spentTime = 0;
  766. m_renderContext.expectedTotalTime = 0;
  767. for (size_t i = 0; i < m_renderItems.size(); ++i)
  768. {
  769. Range rng = m_renderItems[i].frameRange;
  770. m_renderContext.expectedTotalTime += rng.end - rng.start;
  771. }
  772. m_renderContext.captureOptions.once = false;
  773. m_ui->BATCH_RENDER_PRESS_ESC_TO_CANCEL->setText(tr("Press ESC to cancel"));
  774. }
  775. void CSequenceBatchRenderDialog::CaptureItemStart()
  776. {
  777. const SRenderItem& renderItem = m_renderItems[m_renderContext.currentItemIndex];
  778. const QString renderListName =
  779. m_ui->m_renderList->model()->index(m_renderContext.currentItemIndex, 0).data().toString();
  780. static const QString reservedCharacters("/\\:*?\"<>|");
  781. QString renderListNameSanitized;
  782. for (auto chr : renderListName)
  783. {
  784. const bool isReserved = reservedCharacters.contains(chr);
  785. renderListNameSanitized.append(isReserved ? '-' : chr);
  786. }
  787. AZStd::string folderName;
  788. AzFramework::StringFunc::Path::Join(renderItem.folder.toUtf8().data(), renderListNameSanitized.toUtf8().data(), folderName);
  789. // If this is a relative path, prepend the @products@ folder to match where the Renderer is going
  790. // to dump the frame buffer image captures.
  791. if (AzFramework::StringFunc::Path::IsRelative(folderName.c_str()))
  792. {
  793. AZStd::string absolutePath;
  794. const AZStd::string assetsRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@products@");
  795. AzFramework::StringFunc::Path::Join(assetsRoot.c_str(), folderName.c_str(), absolutePath);
  796. folderName = absolutePath;
  797. }
  798. QString finalFolder = folderName.c_str();
  799. int numProbeIndex = 2;
  800. QString probeName = finalFolder;
  801. while (QFileInfo::exists(probeName))
  802. {
  803. probeName = QObject::tr("%1_v%2").arg(finalFolder).arg(numProbeIndex++);
  804. }
  805. finalFolder = probeName;
  806. // Create a new folder before writing any files
  807. const bool mkdirResult = QDir().mkdir(finalFolder);
  808. if (!mkdirResult)
  809. {
  810. TrackViewMessageBox::Critical(
  811. AzToolsFramework::GetActiveWindow(),
  812. QString(),
  813. QObject::tr("Cannot create directory %1 for output frames").arg(finalFolder));
  814. OnUpdateFinalize();
  815. return;
  816. }
  817. // Disable most of the UI in group chunks.
  818. // (Leave the start/cancel button and feedback elements).
  819. m_ui->BATCH_RENDER_LIST_GROUP_BOX->setEnabled(false);
  820. m_ui->BATCH_RENDER_INPUT_GROUP_BOX->setEnabled(false);
  821. m_ui->BATCH_RENDER_OUTPUT_GROUP_BOX->setEnabled(false);
  822. m_renderContext.canceled = false;
  823. CV_TrackViewRenderOutputCapturing = 1;
  824. m_renderContext.captureOptions.timeStep = 1.0f / renderItem.fps;
  825. // Set up the custom config cvars for this item.
  826. AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get();
  827. AZ_Assert(console, "CSequenceBatchRenderDialog requires an IConsole interface but no instance has been created.");
  828. if (console)
  829. {
  830. for (const auto& cvar : renderItem.cvars)
  831. {
  832. console->PerformCommand(cvar.toUtf8().data());
  833. }
  834. }
  835. // Set specific capture options for this item.
  836. m_renderContext.captureOptions.prefix = renderItem.prefix.toUtf8().data();
  837. m_renderContext.captureOptions.folder = finalFolder.toUtf8().data();
  838. // Adjust the viewport resolution
  839. if (CLayoutViewPane* viewPane = MainWindow::instance()->GetActiveView())
  840. {
  841. const QRect& viewportRect = viewPane->GetViewport()->rect();
  842. activeViewportWidth = viewportRect.width();
  843. activeViewportHeight = viewportRect.height();
  844. const int renderWidth = getResWidth(renderItem.resW);
  845. const int renderHeight = getResHeight(renderItem.resH);
  846. viewPane->ResizeViewport(renderWidth, renderHeight);
  847. }
  848. // Turn off debug info if requested
  849. ICVar* cvarDebugInfo = gEnv->pConsole->GetCVar("r_DisplayInfo");
  850. if (cvarDebugInfo)
  851. {
  852. // cache the current value to restore during OnCaptureItemEnd()
  853. m_renderContext.cvarDisplayInfoBU = cvarDebugInfo->GetIVal();
  854. if (renderItem.disableDebugInfo && cvarDebugInfo->GetIVal())
  855. {
  856. const int DISPLAY_INFO_OFF = 0;
  857. cvarDebugInfo->Set(DISPLAY_INFO_OFF);
  858. }
  859. }
  860. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  861. if (movieSystem)
  862. {
  863. movieSystem->EnableFixedStepForCapture(m_renderContext.captureOptions.timeStep);
  864. }
  865. // The capturing doesn't actually start here. It just flags the warming-up and
  866. // once it's done, then the capturing really begins.
  867. // The warming-up is necessary to settle down some post-fxs after the resolution change.
  868. EnterCaptureState(CaptureState::WarmingUpAfterResChange);
  869. m_renderTimer.start();
  870. }
  871. void CSequenceBatchRenderDialog::OnUpdateWarmingUpAfterResChange()
  872. {
  873. UpdateSpinnerProgressMessage("Warming up");
  874. // Spend the given frames warming up after frame buffer resolution change
  875. if (m_renderContext.framesSpentInCurrentPhase++ >= TrackView::tv_SkipFramesCount)
  876. {
  877. // We will handle the idle tick manually now because calling Game Update directly.
  878. SetEnableEditorIdleProcessing(false);
  879. GetIEditor()->SetInGameMode(true);
  880. EnterCaptureState(CaptureState::EnteringGameMode);
  881. }
  882. }
  883. void CSequenceBatchRenderDialog::OnUpdateEnteringGameMode()
  884. {
  885. UpdateSpinnerProgressMessage("Entering game mode");
  886. GetIEditor()->GetGameEngine()->Update();
  887. // Pause the movie player on the first frame
  888. if (m_renderContext.framesSpentInCurrentPhase++ == 0)
  889. {
  890. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  891. if (movieSystem)
  892. {
  893. movieSystem->Pause();
  894. }
  895. }
  896. // Spend the given frames warming up after changing to game mode.
  897. else if (m_renderContext.framesSpentInCurrentPhase++ > TrackView::tv_SkipFramesCount)
  898. {
  899. EnterCaptureState(CaptureState::BeginPlayingSequence);
  900. }
  901. }
  902. void CSequenceBatchRenderDialog::OnUpdateBeginPlayingSequence()
  903. {
  904. UpdateSpinnerProgressMessage("Begin Playing Sequence");
  905. SRenderItem& renderItem = m_renderItems[m_renderContext.currentItemIndex];
  906. const AZStd::string seqName = renderItem.seqName.toUtf8().data();
  907. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  908. AZ_Assert(movieSystem, "Cannot get IMovieSystem");
  909. renderItem.pSequence = movieSystem->FindLegacySequenceByName(seqName.c_str());
  910. AZ_Assert(renderItem.pSequence, "Cannot find sequence by name %s", seqName.c_str());
  911. // Initialize the next one for the batch rendering.
  912. // Set the active shot.
  913. m_renderContext.pActiveDirectorBU = renderItem.pSequence->GetActiveDirector();
  914. renderItem.pSequence->SetActiveDirector(renderItem.pDirectorNode);
  915. // Back up flags and range of the sequence.
  916. m_renderContext.flagBU = renderItem.pSequence->GetFlags();
  917. m_renderContext.rangeBU = renderItem.pSequence->GetTimeRange();
  918. // Change flags and range of the sequence so that it automatically starts
  919. // once the game mode kicks in with the specified range.
  920. renderItem.pSequence->SetFlags(m_renderContext.flagBU | IAnimSequence::eSeqFlags_PlayOnReset);
  921. // Set the time range for this render, back it up 1 frame so the capture will start
  922. // exactly on the first frame.
  923. Range newRange = renderItem.frameRange;
  924. newRange.start -= m_renderContext.captureOptions.timeStep;
  925. renderItem.pSequence->SetTimeRange(newRange);
  926. m_renderContext.captureOptions.duration = newRange.end - newRange.start;
  927. if (movieSystem)
  928. {
  929. movieSystem->AddSequence(renderItem.pSequence);
  930. [[maybe_unused]] const bool listenerAdded = movieSystem->AddMovieListener(renderItem.pSequence, this);
  931. AZ_Assert(
  932. listenerAdded, "Failed to add movie listener for sequence %s", renderItem.pSequence->GetSequenceEntityId().ToString().c_str());
  933. movieSystem->Reset(true, false);
  934. }
  935. // Start the sequence playing
  936. if (movieSystem)
  937. {
  938. movieSystem->SetPlayingTime(renderItem.pSequence, newRange.start);
  939. }
  940. EnterCaptureState(CaptureState::Capturing);
  941. }
  942. void CSequenceBatchRenderDialog::OnUpdateCapturing()
  943. {
  944. IAnimSequence* pCurSeq = m_renderItems[m_renderContext.currentItemIndex].pSequence;
  945. // Make sure we are still in game mode if we are capturing, so we can never
  946. // get soft locked if we somehow leave game mode without this module knowing about it.
  947. if (!GetIEditor()->IsInGameMode())
  948. {
  949. m_renderContext.endingSequence = pCurSeq;
  950. m_renderContext.canceled = true;
  951. EnterCaptureState(CaptureState::End);
  952. return;
  953. }
  954. // Progress bar
  955. Range rng = pCurSeq->GetTimeRange();
  956. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  957. float elapsedTime = movieSystem ? movieSystem->GetPlayingTime(pCurSeq) - rng.start : 0.f;
  958. const int percentage
  959. = static_cast<int>(100.0f * (m_renderContext.spentTime + elapsedTime) / m_renderContext.expectedTotalTime);
  960. m_ui->m_progressBar->setValue(percentage);
  961. // Progress message
  962. const QString itemText = m_ui->m_renderList->model()->index(m_renderContext.currentItemIndex, 0).data().toString();
  963. const QString msg = tr("Rendering '%1'...(%2%)").arg(itemText).arg(static_cast<int>(100.0f * elapsedTime / (rng.end - rng.start)));
  964. UpdateSpinnerProgressMessage(msg.toLatin1().data());
  965. m_renderContext.framesSpentInCurrentPhase++;
  966. }
  967. void CSequenceBatchRenderDialog::OnUpdateEnd(IAnimSequence* sequence)
  968. {
  969. // Restore flags, range and the active director of the sequence.
  970. sequence->SetFlags(m_renderContext.flagBU);
  971. sequence->SetTimeRange(m_renderContext.rangeBU);
  972. sequence->SetActiveDirector(m_renderContext.pActiveDirectorBU);
  973. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  974. if (movieSystem)
  975. {
  976. movieSystem->DisableFixedStepForCapture();
  977. // Important: End batch render mode BEFORE leaving Game Mode.
  978. // Otherwise track view will set the active camera based on the directors in the current sequence while leaving game mode
  979. movieSystem->EnableBatchRenderMode(false);
  980. movieSystem->RemoveMovieListener(sequence, this);
  981. }
  982. GetIEditor()->SetInGameMode(false);
  983. GetIEditor()->GetGameEngine()->Update(); // Update is needed because SetInGameMode() queues game mode, Update() executes it.
  984. if (CLayoutViewPane* viewPane = MainWindow::instance()->GetActiveView())
  985. {
  986. viewPane->ResizeViewport(activeViewportWidth, activeViewportHeight);
  987. }
  988. // Restore display debug info
  989. ICVar* cvarDebugInfo = gEnv->pConsole->GetCVar("r_DisplayInfo");
  990. if (cvarDebugInfo)
  991. {
  992. cvarDebugInfo->Set(m_renderContext.cvarDisplayInfoBU);
  993. }
  994. const SRenderItem& renderItem = m_renderItems[m_renderContext.currentItemIndex];
  995. if (m_bFFMPEGCommandAvailable && renderItem.bCreateVideo)
  996. {
  997. // Create a video using the ffmpeg plug-in from captured images.
  998. m_renderContext.processingFFMPEG = true;
  999. const AZStd::string outputFolder = m_renderContext.captureOptions.folder;
  1000. const AZStd::string imageFormat = m_ui->m_imageFormatCombo->currentText().toStdString().c_str();
  1001. AZStd::string outputFile;
  1002. AzFramework::StringFunc::Path::Join(outputFolder.c_str(), renderItem.prefix.toUtf8().data(), outputFile);
  1003. // Create the input file string, leave the %06d unexpanded for the ffmpeg tool.
  1004. const AZStd::string inputFile = outputFile + "_%06d." + imageFormat;
  1005. outputFile += ".webm";
  1006. static const char* filePattern = "__input_file__";
  1007. QString command = AZStd::string::format(
  1008. "plugin.ffmpeg_encode '%s' '%s' '%s' %i %i 'crop=%i:%i:0:0'",
  1009. filePattern,
  1010. outputFile.c_str(),
  1011. "libvpx-vp9",
  1012. 10240,
  1013. renderItem.fps,
  1014. getResWidth(renderItem.resW),
  1015. getResHeight(renderItem.resH)).c_str();
  1016. command = command.replace(filePattern, inputFile.c_str());
  1017. auto future = QtConcurrent::run([command]
  1018. {
  1019. // Run the command
  1020. GetIEditor()->ExecuteCommand(command);
  1021. });
  1022. // Use a watcher to set a flag when the ffmpeg processing is complete.
  1023. connect(&m_renderContext.processingFFMPEGWatcher, &QFutureWatcher<void>::finished, this, [this]()
  1024. {
  1025. m_renderContext.processingFFMPEG = false;
  1026. });
  1027. m_renderContext.processingFFMPEGWatcher.setFuture(future);
  1028. EnterCaptureState(CaptureState::FFMPEGProcessing);
  1029. }
  1030. else
  1031. {
  1032. EnterCaptureState(CaptureState::Finalize);
  1033. }
  1034. }
  1035. void CSequenceBatchRenderDialog::OnUpdateFFMPEGProcessing()
  1036. {
  1037. UpdateSpinnerProgressMessage("FFMPEG processing");
  1038. if (!m_renderContext.processingFFMPEG)
  1039. {
  1040. EnterCaptureState(CaptureState::Finalize);
  1041. }
  1042. }
  1043. void CSequenceBatchRenderDialog::OnUpdateFinalize()
  1044. {
  1045. SetEnableEditorIdleProcessing(true);
  1046. m_renderTimer.stop();
  1047. // Turn disabled UI elements back on
  1048. m_ui->BATCH_RENDER_LIST_GROUP_BOX->setEnabled(true);
  1049. m_ui->BATCH_RENDER_INPUT_GROUP_BOX->setEnabled(true);
  1050. m_ui->BATCH_RENDER_OUTPUT_GROUP_BOX->setEnabled(true);
  1051. m_renderContext.frameNumber = 0;
  1052. m_renderContext.capturingFrame = false;
  1053. m_atomOutputFrameCapture.DestroyPipeline(*TrackView::SceneFromGameEntityContext());
  1054. // Check to see if there is more items to process
  1055. bool done = m_renderContext.currentItemIndex == m_renderItems.size() - 1;
  1056. if (done)
  1057. {
  1058. // Update end the batch message
  1059. if (m_renderContext.canceled)
  1060. {
  1061. m_ui->m_progressBar->setValue(0);
  1062. m_ui->m_progressStatusMsg->setText(tr("Rendering canceled"));
  1063. }
  1064. else
  1065. {
  1066. m_ui->m_progressBar->setValue(100);
  1067. m_ui->m_progressStatusMsg->setText(tr("Rendering finished"));
  1068. }
  1069. m_ui->m_pGoBtn->setText(tr("Start"));
  1070. m_ui->m_pGoBtn->setIcon(QPixmap(":/Trackview/clapperboard_ready.png"));
  1071. m_renderContext.currentItemIndex = -1;
  1072. m_ui->BATCH_RENDER_PRESS_ESC_TO_CANCEL->setText(m_ffmpegPluginStatusMsg);
  1073. CV_TrackViewRenderOutputCapturing = 0;
  1074. EnterCaptureState(CaptureState::Idle);
  1075. }
  1076. else
  1077. {
  1078. // Update the context.
  1079. m_renderContext.spentTime += m_renderContext.captureOptions.duration;
  1080. ++m_renderContext.currentItemIndex;
  1081. // Trigger the next item.
  1082. CaptureItemStart();
  1083. }
  1084. }
  1085. void CSequenceBatchRenderDialog::OnKickIdleTimout()
  1086. {
  1087. OnKickIdle();
  1088. if (m_renderContext.IsInRendering())
  1089. {
  1090. m_renderTimer.start();
  1091. }
  1092. else
  1093. {
  1094. // All done with our custom OnKickIdle, restore editor idle.
  1095. SetEnableEditorIdleProcessing(true);
  1096. }
  1097. //When we disable the editor idle processing. system tick is no longer invoked.
  1098. //so we call it here to ensure rendering + other systems are updated.
  1099. if (!m_editorIdleProcessingEnabled)
  1100. {
  1101. AZ::ComponentApplication* componentApplication = nullptr;
  1102. AZ::ComponentApplicationBus::BroadcastResult(componentApplication, &AZ::ComponentApplicationRequests::GetApplication);
  1103. if (componentApplication)
  1104. {
  1105. componentApplication->TickSystem();
  1106. }
  1107. }
  1108. }
  1109. void CSequenceBatchRenderDialog::OnKickIdle()
  1110. {
  1111. if (m_renderContext.captureState == CaptureState::WarmingUpAfterResChange)
  1112. {
  1113. OnUpdateWarmingUpAfterResChange();
  1114. }
  1115. else if (m_renderContext.captureState == CaptureState::EnteringGameMode)
  1116. {
  1117. OnUpdateEnteringGameMode();
  1118. }
  1119. else if (m_renderContext.captureState == CaptureState::BeginPlayingSequence)
  1120. {
  1121. OnUpdateBeginPlayingSequence();
  1122. }
  1123. else if (m_renderContext.captureState == CaptureState::Capturing)
  1124. {
  1125. OnUpdateCapturing();
  1126. }
  1127. else if (m_renderContext.captureState == CaptureState::End)
  1128. {
  1129. OnUpdateEnd(m_renderContext.endingSequence);
  1130. m_renderContext.endingSequence = nullptr;
  1131. }
  1132. else if (m_renderContext.captureState == CaptureState::FFMPEGProcessing)
  1133. {
  1134. OnUpdateFFMPEGProcessing();
  1135. }
  1136. else if (m_renderContext.captureState == CaptureState::Finalize)
  1137. {
  1138. OnUpdateFinalize();
  1139. }
  1140. else
  1141. {
  1142. if (!m_renderContext.IsInRendering())
  1143. {
  1144. CheckForEnableUpdateButton();
  1145. }
  1146. }
  1147. if (GetIEditor()->IsInGameMode())
  1148. {
  1149. // note: the internal state may change by calling GetGameEngine()->Update() so
  1150. // we must not cache this value
  1151. const auto capturing = [this]
  1152. {
  1153. return m_renderContext.captureState == CaptureState::Capturing
  1154. // this lags behind by one frame since we are capturing the back buffer,
  1155. // so don't bother enabling the capture on the first frame.
  1156. && m_renderContext.framesSpentInCurrentPhase != 0;
  1157. };
  1158. // if we're currently trying to capture and aren't waiting for a frame
  1159. // capture to complete, it's possible to start the next capture
  1160. const auto canBeginFrameCapture = [capturing, this]
  1161. {
  1162. return capturing() && !m_renderContext.capturingFrame;
  1163. };
  1164. if (canBeginFrameCapture())
  1165. {
  1166. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  1167. if (movieSystem)
  1168. {
  1169. // update the time so the frame number can be calculated in StartCapture()
  1170. IAnimSequence* sequence = m_renderItems[m_renderContext.currentItemIndex].pSequence;
  1171. m_renderContext.captureOptions.time = movieSystem->GetPlayingTime(sequence);
  1172. movieSystem->StartCapture(m_renderContext.captureOptions, ++m_renderContext.frameNumber);
  1173. movieSystem->ControlCapture();
  1174. }
  1175. }
  1176. // if we're not capturing or we're not currently waiting for the current frame to finish
  1177. // being captured, it's safe to move to the next step of the main update
  1178. if (!capturing() || !m_renderContext.capturingFrame)
  1179. {
  1180. const auto& renderItem = m_renderItems[m_renderContext.currentItemIndex];
  1181. // update the view given the current camera transform and projection
  1182. if (!m_atomOutputFrameCapture.IsCreated())
  1183. {
  1184. // create a new atom pipeline to capture the frames of the current sequence
  1185. m_atomOutputFrameCapture.CreatePipeline(
  1186. *TrackView::SceneFromGameEntityContext(), "TrackViewSequencePipeline", renderItem.resW, renderItem.resH);
  1187. }
  1188. UpdateAtomOutputFrameCaptureView(renderItem.resW, renderItem.resH);
  1189. GetIEditor()->GetGameEngine()->Update(); // step update (original frame capture)
  1190. }
  1191. if (canBeginFrameCapture())
  1192. {
  1193. const AZStd::string fileName = AZStd::string::format("%s_%06d", m_renderContext.captureOptions.prefix.c_str(), m_renderContext.frameNumber);
  1194. AZStd::string filePath;
  1195. AzFramework::StringFunc::Path::Join(
  1196. m_renderContext.captureOptions.folder.c_str(), fileName.c_str(), filePath, /*caseInsensitive=*/true,
  1197. /*normalize=*/false);
  1198. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  1199. // track view callback after each frame is captured
  1200. const auto captureFinishedCallback = [this, movieSystem]()
  1201. {
  1202. m_renderContext.capturingFrame = false;
  1203. if (movieSystem)
  1204. {
  1205. movieSystem->EndCapture();
  1206. movieSystem->ControlCapture();
  1207. }
  1208. };
  1209. const auto imageFormatExtension = m_ui->m_imageFormatCombo->currentText();
  1210. // readback result callback (how the image should be captured)
  1211. // currently only .dds and .ppm
  1212. const auto readbackCallback = [filePath,
  1213. imageFormatExtension](const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) {
  1214. const auto imageFormatExtensionUtf8 = imageFormatExtension.toUtf8();
  1215. const auto imageFormatExtensionCstr = imageFormatExtensionUtf8.constData();
  1216. const AZStd::string fileName = AZStd::string::format("%s.%s", filePath.c_str(), imageFormatExtensionCstr);
  1217. if (AZ::StringFunc::Equal(imageFormatExtensionCstr, "dds"))
  1218. {
  1219. if (const AZ::Render::FrameCaptureOutputResult result = AZ::Render::DdsFrameCaptureOutput(fileName, readbackResult);
  1220. result.m_errorMessage.has_value())
  1221. {
  1222. AZ_Printf("TrackView", "Dds frame capture failed: %s", result.m_errorMessage.value().c_str());
  1223. }
  1224. }
  1225. else if (AZ::StringFunc::Equal(imageFormatExtensionCstr, "ppm"))
  1226. {
  1227. if (const AZ::Render::FrameCaptureOutputResult result = AZ::Render::PpmFrameCaptureOutput(fileName, readbackResult);
  1228. result.m_errorMessage.has_value())
  1229. {
  1230. AZ_Printf("TrackView", "Ppm frame capture failed: %s", result.m_errorMessage.value().c_str());
  1231. }
  1232. }
  1233. else
  1234. {
  1235. AZ_Printf("TrackView", "Image format .%s not supported", imageFormatExtensionCstr);
  1236. }
  1237. };
  1238. m_renderContext.capturingFrame = m_atomOutputFrameCapture.BeginCapture(readbackCallback, captureFinishedCallback);
  1239. }
  1240. }
  1241. else
  1242. {
  1243. // Post events, this will cause an Update tick.
  1244. qApp->sendPostedEvents();
  1245. }
  1246. }
  1247. void CSequenceBatchRenderDialog::OnCancelRender()
  1248. {
  1249. if (m_renderContext.captureState == CaptureState::Capturing)
  1250. {
  1251. // In the capturing state, abort the sequence, OnMovieEvent with an abort will fire and cause
  1252. // the transition to CaptureState::End.
  1253. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  1254. if (movieSystem)
  1255. {
  1256. movieSystem->AbortSequence(m_renderItems[m_renderContext.currentItemIndex].pSequence);
  1257. }
  1258. }
  1259. else if (m_renderContext.captureState == CaptureState::EnteringGameMode)
  1260. {
  1261. // In the EnteringGameMode state, the movie sequences hasn't started yet, so we can't count on an
  1262. // OnMovieEvent event to end the capture early. So transition into the End state manually.
  1263. m_renderContext.endingSequence = m_renderItems[m_renderContext.currentItemIndex].pSequence;
  1264. m_renderContext.canceled = true;
  1265. EnterCaptureState(CaptureState::End);
  1266. }
  1267. }
  1268. void CSequenceBatchRenderDialog::OnVarsChange()
  1269. {
  1270. CheckForEnableUpdateButton();
  1271. }
  1272. void CSequenceBatchRenderDialog::OnFormatChange()
  1273. {
  1274. CheckForEnableUpdateButton();
  1275. }
  1276. void CSequenceBatchRenderDialog::OnPrefixChange()
  1277. {
  1278. CheckForEnableUpdateButton();
  1279. }
  1280. void CSequenceBatchRenderDialog::OnDisableDebugInfoChange()
  1281. {
  1282. CheckForEnableUpdateButton();
  1283. }
  1284. void CSequenceBatchRenderDialog::OnCreateVideoChange()
  1285. {
  1286. CheckForEnableUpdateButton();
  1287. }
  1288. void CSequenceBatchRenderDialog::OnLoadBatch()
  1289. {
  1290. QString loadPath;
  1291. if (CFileUtil::SelectFile("Render Batch Files (*.batch)",
  1292. Path::GetUserSandboxFolder(), loadPath))
  1293. {
  1294. XmlNodeRef batchRenderListNode = XmlHelpers::LoadXmlFromFile(loadPath.toStdString().c_str());
  1295. if (batchRenderListNode == nullptr)
  1296. {
  1297. return;
  1298. }
  1299. int version = 0;
  1300. batchRenderListNode->getAttr("version", version);
  1301. if (version != kBatchRenderFileVersion)
  1302. {
  1303. QMessageBox::critical(this, tr("Cannot load"), tr("The file version is different!"));
  1304. return;
  1305. }
  1306. OnClearRenderItems();
  1307. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  1308. for (int i = 0; i < batchRenderListNode->getChildCount(); ++i)
  1309. {
  1310. // Get an item.
  1311. SRenderItem item;
  1312. XmlNodeRef itemNode = batchRenderListNode->getChild(i);
  1313. // sequence
  1314. const QString seqName = itemNode->getAttr("sequence");
  1315. item.pSequence = movieSystem ? movieSystem->FindLegacySequenceByName(seqName.toUtf8().data()) : nullptr;
  1316. if (item.pSequence == nullptr)
  1317. {
  1318. QMessageBox::warning(this, tr("Sequence not found"), tr("A sequence of '%1' not found! This'll be skipped.").arg(seqName));
  1319. continue;
  1320. }
  1321. // director node
  1322. const QString directorName = itemNode->getAttr("director");
  1323. for (int k = 0; k < item.pSequence->GetNodeCount(); ++k)
  1324. {
  1325. IAnimNode* pNode = item.pSequence->GetNode(k);
  1326. if (pNode->GetType() == AnimNodeType::Director && directorName == pNode->GetName())
  1327. {
  1328. item.pDirectorNode = pNode;
  1329. break;
  1330. }
  1331. }
  1332. if (item.pDirectorNode == nullptr)
  1333. {
  1334. QMessageBox::warning(this, tr("Director node not found"), tr("A director node of '%1' not found in the sequence of '%2'! This'll be skipped.").arg(directorName).arg(seqName));
  1335. continue;
  1336. }
  1337. // frame range
  1338. itemNode->getAttr("startframe", item.frameRange.start);
  1339. itemNode->getAttr("endframe", item.frameRange.end);
  1340. // resolution
  1341. itemNode->getAttr("width", item.resW);
  1342. itemNode->getAttr("height", item.resH);
  1343. // fps
  1344. itemNode->getAttr("fps", item.fps);
  1345. // prefix
  1346. item.prefix = itemNode->getAttr("prefix");
  1347. // create_video
  1348. itemNode->getAttr("createvideo", item.bCreateVideo);
  1349. // folder
  1350. item.folder = itemNode->getAttr("folder");
  1351. itemNode->getAttr(debugInfo, item.disableDebugInfo);
  1352. item.imageFormat = itemNode->getAttr("format");
  1353. // cvars
  1354. for (int k = 0; k < itemNode->getChildCount(); ++k)
  1355. {
  1356. const QString cvar = itemNode->getChild(k)->getContent();
  1357. item.cvars.push_back(cvar);
  1358. }
  1359. AddItem(item);
  1360. }
  1361. }
  1362. }
  1363. void CSequenceBatchRenderDialog::OnSaveBatch()
  1364. {
  1365. QString savePath;
  1366. if (CFileUtil::SelectSaveFile("Render Batch Files (*.batch)", "batch",
  1367. Path::GetUserSandboxFolder(), savePath))
  1368. {
  1369. XmlNodeRef batchRenderListNode = XmlHelpers::CreateXmlNode("batchrenderlist");
  1370. batchRenderListNode->setAttr("version", kBatchRenderFileVersion);
  1371. for (size_t i = 0; i < m_renderItems.size(); ++i)
  1372. {
  1373. const SRenderItem& item = m_renderItems[i];
  1374. XmlNodeRef itemNode = batchRenderListNode->newChild("item");
  1375. // sequence
  1376. itemNode->setAttr("sequence", item.pSequence->GetName());
  1377. // director node
  1378. itemNode->setAttr("director", item.pDirectorNode->GetName());
  1379. // frame range
  1380. itemNode->setAttr("startframe", item.frameRange.start);
  1381. itemNode->setAttr("endframe", item.frameRange.end);
  1382. // resolution
  1383. itemNode->setAttr("width", item.resW);
  1384. itemNode->setAttr("height", item.resH);
  1385. // fps
  1386. itemNode->setAttr("fps", item.fps);
  1387. // prefix
  1388. itemNode->setAttr("prefix", item.prefix.toUtf8().data());
  1389. // create_video
  1390. itemNode->setAttr("createvideo", item.bCreateVideo);
  1391. // folder
  1392. itemNode->setAttr("folder", item.folder.toUtf8().data());
  1393. itemNode->setAttr(debugInfo, item.disableDebugInfo);
  1394. itemNode->setAttr("format", item.imageFormat.toUtf8().data());
  1395. // cvars
  1396. for (size_t k = 0; k < item.cvars.size(); ++k)
  1397. {
  1398. itemNode->newChild("cvar")->setContent(item.cvars[static_cast<int>(k)].toUtf8().data());
  1399. }
  1400. }
  1401. XmlHelpers::SaveXmlNode(GetIEditor()->GetFileUtil(), batchRenderListNode, savePath.toStdString().c_str());
  1402. }
  1403. }
  1404. bool CSequenceBatchRenderDialog::SetUpNewRenderItem(SRenderItem& item)
  1405. {
  1406. item.seqName = m_ui->m_sequenceCombo->currentText();
  1407. const QString shotName = m_ui->m_shotCombo->currentText();
  1408. // folder
  1409. item.folder = m_ui->m_destinationEdit->text();
  1410. if (item.folder.isEmpty())
  1411. {
  1412. QMessageBox::critical(this, tr("Cannot add"), tr("The output folder should be specified!"));
  1413. return false;
  1414. }
  1415. // sequence
  1416. IMovieSystem* movieSystem = AZ::Interface<IMovieSystem>::Get();
  1417. item.pSequence = movieSystem ? movieSystem->FindLegacySequenceByName(item.seqName.toUtf8().data()) : nullptr;
  1418. AZ_Assert(item.pSequence, "Cannot find sequence by name %s", item.seqName.toUtf8().data());
  1419. // director
  1420. for (int i = 0; i < item.pSequence->GetNodeCount(); ++i)
  1421. {
  1422. IAnimNode* pNode = item.pSequence->GetNode(i);
  1423. if (pNode->GetType() == AnimNodeType::Director && shotName == pNode->GetName())
  1424. {
  1425. item.pDirectorNode = pNode;
  1426. break;
  1427. }
  1428. }
  1429. if (item.pDirectorNode == nullptr)
  1430. {
  1431. return false;
  1432. }
  1433. // frame range
  1434. item.frameRange = Range(m_ui->m_startFrame->value() / m_fpsForTimeToFrameConversion,
  1435. m_ui->m_endFrame->value() / m_fpsForTimeToFrameConversion);
  1436. // fps
  1437. if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fpsOptions[m_ui->m_fpsCombo->currentIndex()].fpsDesc)
  1438. {
  1439. item.fps = m_customFPS;
  1440. }
  1441. else
  1442. {
  1443. item.fps = fpsOptions[m_ui->m_fpsCombo->currentIndex()].fps;
  1444. }
  1445. // format
  1446. item.imageFormat = m_ui->m_imageFormatCombo->currentText();
  1447. // prefix
  1448. item.prefix = m_ui->BATCH_RENDER_FILE_PREFIX->text();
  1449. // disable debug info
  1450. item.disableDebugInfo = m_ui->m_disableDebugInfoCheckBox->isChecked();
  1451. // create_video
  1452. item.bCreateVideo = m_ui->m_createVideoCheckBox->isChecked();
  1453. // resolution
  1454. int curResSel = m_ui->m_resolutionCombo->currentIndex();
  1455. if (curResSel < arraysize(resolutions))
  1456. {
  1457. item.resW = resolutions[curResSel][0];
  1458. item.resH = resolutions[curResSel][1];
  1459. }
  1460. else
  1461. {
  1462. item.resW = m_customResW;
  1463. item.resH = m_customResH;
  1464. }
  1465. // cvars
  1466. const QStringList lines = m_ui->m_cvarsEdit->toPlainText().split('\n');
  1467. for (const QString& line : lines)
  1468. {
  1469. if (!line.isEmpty())
  1470. {
  1471. item.cvars.push_back(line);
  1472. }
  1473. }
  1474. return true;
  1475. }
  1476. void CSequenceBatchRenderDialog::AddItem(const SRenderItem& item)
  1477. {
  1478. // Add the item.
  1479. m_renderItems.push_back(item);
  1480. // Add it to the list box, too.
  1481. m_renderListModel->setStringList(m_renderListModel->stringList() << GetCaptureItemString(item));
  1482. m_ui->m_pGoBtn->setEnabled(true);
  1483. }
  1484. QString CSequenceBatchRenderDialog::GetCaptureItemString(const SRenderItem& item) const
  1485. {
  1486. return QString::fromLatin1("%1_%2_%3-%4(%5x%6,%7)%8").arg(item.pSequence->GetName())
  1487. .arg(item.pDirectorNode->GetName())
  1488. .arg(int(item.frameRange.start * m_fpsForTimeToFrameConversion))
  1489. .arg(int(item.frameRange.end * m_fpsForTimeToFrameConversion))
  1490. .arg(getResWidth(item.resW)).arg(getResHeight(item.resH)).arg(item.fps)
  1491. .arg(item.bCreateVideo ? "[v]" : "");
  1492. }
  1493. void CSequenceBatchRenderDialog::UpdateSpinnerProgressMessage(const char* description)
  1494. {
  1495. static int count = 0;
  1496. const char* rotatingCursor[] = { "|", "/", "-", "\\" };
  1497. const QString msg = tr("%1 %2").arg(description).arg(rotatingCursor[(count++) % arraysize(rotatingCursor)]);
  1498. m_ui->m_progressStatusMsg->setText(msg);
  1499. GetIEditor()->Notify(eNotify_OnIdleUpdate);
  1500. }
  1501. void CSequenceBatchRenderDialog::EnterCaptureState(CaptureState captureState)
  1502. {
  1503. m_renderContext.captureState = captureState;
  1504. m_renderContext.framesSpentInCurrentPhase = 0;
  1505. }
  1506. void CSequenceBatchRenderDialog::SetEnableEditorIdleProcessing(bool enabled)
  1507. {
  1508. if (enabled && !m_editorIdleProcessingEnabled)
  1509. {
  1510. EditorIdleProcessingBus::Broadcast(&EditorIdleProcessing::EnableIdleProcessing);
  1511. m_editorIdleProcessingEnabled = true;
  1512. }
  1513. if (!enabled && m_editorIdleProcessingEnabled)
  1514. {
  1515. EditorIdleProcessingBus::Broadcast(&EditorIdleProcessing::DisableIdleProcessing);
  1516. m_editorIdleProcessingEnabled = false;
  1517. }
  1518. }
  1519. void CSequenceBatchRenderDialog::UpdateAtomOutputFrameCaptureView(const int width, const int height)
  1520. {
  1521. const AZ::EntityId activeCameraEntityId = TrackView::ActiveCameraEntityId();
  1522. AZ_Assert(activeCameraEntityId.IsValid(), "Active camera Entity Id is invalid");
  1523. AZ::RPI::ViewPtr view = nullptr;
  1524. AZ::RPI::ViewProviderBus::EventResult(view, activeCameraEntityId, &AZ::RPI::ViewProvider::GetView);
  1525. m_atomOutputFrameCapture.UpdateView(
  1526. TrackView::TransformFromEntityId(activeCameraEntityId),
  1527. TrackView::ProjectionFromCameraEntityId(activeCameraEntityId, aznumeric_cast<float>(width), aznumeric_cast<float>(height)),
  1528. view);
  1529. }