@@ -248,75 +248,84 @@ CaptureResult captureFrame(Session& session, const CaptureRequest& req) {
248248 throw CoreError (CoreError::Code::InternalError,
249249 " Failed to connect to target process" );
250250
251- // RAII guard for ctrl->Shutdown()
251+ // RAII guard for ctrl->Shutdown() — single cleanup path, no manual Shutdown()
252252 struct CtrlGuard {
253253 ITargetControl* c;
254254 ~CtrlGuard () { if (c) c->Shutdown (); }
255- } guard{ctrl};
256-
257- uint32_t pid = ctrl->GetPID ();
255+ };
258256
259- // 7. Wait for delayFrames, then trigger capture.
257+ std::string foundCapture;
258+ uint32_t pid = 0 ;
260259 {
261- uint32_t waitMs = req.delayFrames * 16 ; // ~16ms per frame at 60fps
262- if (waitMs < 2000 ) waitMs = 2000 ; // minimum 2s for API init
263- auto waitUntil = std::chrono::steady_clock::now () +
264- std::chrono::milliseconds (waitMs);
265- while (std::chrono::steady_clock::now () < waitUntil) {
266- if (!ctrl->Connected ())
267- throw CoreError (CoreError::Code::InternalError,
268- " Target process exited during wait" );
269- TargetControlMessage msg = ctrl->ReceiveMessage (nullptr );
270- if (msg.type == TargetControlMessageType::Disconnected)
271- throw CoreError (CoreError::Code::InternalError,
272- " Target process disconnected during wait" );
273- std::this_thread::sleep_for (std::chrono::milliseconds (50 ));
260+ CtrlGuard guard{ctrl};
261+
262+ pid = ctrl->GetPID ();
263+
264+ // 7. Wait for delayFrames, then trigger capture.
265+ {
266+ uint32_t waitMs = req.delayFrames * 16 ; // ~16ms per frame at 60fps
267+ if (waitMs < 2000 ) waitMs = 2000 ; // minimum 2s for API init
268+ auto waitUntil = std::chrono::steady_clock::now () +
269+ std::chrono::milliseconds (waitMs);
270+ while (std::chrono::steady_clock::now () < waitUntil) {
271+ if (!ctrl->Connected ())
272+ throw CoreError (CoreError::Code::InternalError,
273+ " Target process exited during wait" );
274+ TargetControlMessage msg = ctrl->ReceiveMessage (nullptr );
275+ if (msg.type == TargetControlMessageType::Disconnected)
276+ throw CoreError (CoreError::Code::InternalError,
277+ " Target process disconnected during wait" );
278+ std::this_thread::sleep_for (std::chrono::milliseconds (50 ));
279+ }
274280 }
275- }
276281
277- ctrl->TriggerCapture (1 );
282+ ctrl->TriggerCapture (1 );
278283
279- // 8. Poll for NewCapture message
280- bool captured = false ;
281- auto deadline = std::chrono::steady_clock::now () + std::chrono::seconds (60 );
284+ // 8. Poll for NewCapture message
285+ bool captured = false ;
286+ std::string captureMsgPath; // path from NewCapture message (preferred)
287+ auto deadline = std::chrono::steady_clock::now () + std::chrono::seconds (60 );
282288
283- while (std::chrono::steady_clock::now () < deadline) {
284- if (!ctrl->Connected ())
285- break ;
289+ while (std::chrono::steady_clock::now () < deadline) {
290+ if (!ctrl->Connected ())
291+ break ;
286292
287- TargetControlMessage msg = ctrl->ReceiveMessage (nullptr );
293+ TargetControlMessage msg = ctrl->ReceiveMessage (nullptr );
288294
289- if (msg.type == TargetControlMessageType::NewCapture) {
290- captured = true ;
291- break ;
292- }
293- if (msg.type == TargetControlMessageType::Disconnected)
294- break ;
295+ if (msg.type == TargetControlMessageType::NewCapture) {
296+ captured = true ;
297+ captureMsgPath = std::string (msg.newCapture .path .c_str ());
298+ break ;
299+ }
300+ if (msg.type == TargetControlMessageType::Disconnected)
301+ break ;
295302
296- std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
297- }
303+ std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
304+ }
298305
299- // 9. Find the capture file on disk.
300- // RenderDoc saves captures to its default temp directory (%TEMP%/RenderDoc/)
301- // or the template path we provided.
302- auto exeName = fs::path (req.exePath ).stem ().string ();
303- std::vector<fs::path> searchDirs = {
304- fs::path (captureTemplate).parent_path (),
305- fs::temp_directory_path () / " RenderDoc" ,
306- };
307- std::string foundCapture = findNewestCapture (exeName, searchDirs);
306+ // 9. Find the capture file on disk.
307+ // Prefer the path from the NewCapture message (avoids filesystem race).
308+ // Fall back to scanning directories if the message path is unavailable.
309+ if (!captureMsgPath.empty () && fs::exists (captureMsgPath)) {
310+ foundCapture = captureMsgPath;
311+ } else {
312+ auto exeName = fs::path (req.exePath ).stem ().string ();
313+ std::vector<fs::path> searchDirs = {
314+ fs::path (captureTemplate).parent_path (),
315+ fs::temp_directory_path () / " RenderDoc" ,
316+ };
317+ foundCapture = findNewestCapture (exeName, searchDirs);
318+ }
308319
309- if (foundCapture.empty ())
310- throw CoreError (CoreError::Code::InternalError,
311- captured ? " Capture completed but file not found on disk"
312- : " Capture timed out and no capture file found" );
320+ if (foundCapture.empty ())
321+ throw CoreError (CoreError::Code::InternalError,
322+ captured ? " Capture completed but file not found on disk"
323+ : " Capture timed out and no capture file found" );
313324
314- if (foundCapture != outputPath)
315- fs::copy_file (foundCapture, outputPath, fs::copy_options::overwrite_existing);
325+ if (foundCapture != outputPath)
326+ fs::copy_file (foundCapture, outputPath, fs::copy_options::overwrite_existing);
316327
317- // 10. Cleanup
318- guard.c = nullptr ;
319- ctrl->Shutdown ();
328+ } // 10. guard destructor calls ctrl->Shutdown()
320329
321330 // 11. Auto-open the capture
322331 session.open (outputPath);
0 commit comments