// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "Screenshot.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Math/Math.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Async/GPUTask.h" #include "Engine/Graphics/GPUResourceProperty.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUSwapChain.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Engine/Globals.h" #if COMPILE_WITH_TEXTURE_TOOL #include "Engine/Tools/TextureTool/TextureTool.h" #endif /// /// Capture screenshot helper /// /// class CaptureScreenshot : public ThreadPoolTask { friend Screenshot; private: TextureData _data; GPUTextureReference _texture; ScriptingObjectReference _renderTask; String _path; DateTime _startTime; public: /// /// Initializes a new instance of the class. /// /// The target. /// The path. CaptureScreenshot(GPUTexture* target, const StringView& path) : _texture(target) , _path(path) , _startTime(DateTime::NowUTC()) { ASSERT(_texture != nullptr); } /// /// Initializes a new instance of the class. /// /// The target. /// The path. CaptureScreenshot(RenderTask* target, const StringView& path) : _renderTask(target) , _path(path) , _startTime(DateTime::NowUTC()) { ASSERT(_renderTask != nullptr); } public: /// /// Gets the texture data container. /// /// Texture data FORCE_INLINE TextureData& GetData() { return _data; } protected: // [ThreadPoolTask] bool Run() override; void OnFail() override; }; bool CaptureScreenshot::Run() { const auto texture = _texture.Get(); if (texture == nullptr && _renderTask == nullptr) { LOG(Warning, "Missing target render task."); return true; } // TODO: how about a case two or more screenshots at the same second? update counter and check files // Ensure that path is valid and folder exists String screenshotsDir; if (_path.IsEmpty()) { #if USE_EDITOR screenshotsDir = Globals::ProjectFolder / TEXT("Screenshots"); #else screenshotsDir = Globals::ProductLocalFolder / TEXT("Screenshots"); #endif _path = screenshotsDir / String::Format(TEXT("Screenshot_{0}.png"), DateTime::Now().ToFileNameString()); } else { screenshotsDir = StringUtils::GetDirectoryName(_path); } if (!FileSystem::DirectoryExists(screenshotsDir)) FileSystem::CreateDirectory(screenshotsDir); #if COMPILE_WITH_TEXTURE_TOOL // Export to file if (TextureTool::ExportTexture(_path, _data)) { LOG(Warning, "Cannot export screenshot to file."); return true; } #else LOG(Warning, "Cannot export screenshot to file. No textures exporting support in build."); return true; #endif LOG(Info, "Saved screenshot '{1}' (time: {0} ms)", Math::RoundToInt((float)(DateTime::NowUTC() - _startTime).GetTotalMilliseconds()), _path); _renderTask = nullptr; return false; } void CaptureScreenshot::OnFail() { LOG(Warning, "Cannot take screenshot."); // Base ThreadPoolTask::OnFail(); } void Screenshot::Capture(GPUTexture* target, const StringView& path) { // Validate if (target == nullptr) { LOG(Warning, "Cannot take screenshot. Render target texture is not allocated."); return; } if (target->Depth() != 1) { LOG(Warning, "Cannot take screenshot. 3D textures are not supported."); return; } if (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready) { LOG(Warning, "Cannot take screenshot. Graphics device is not ready."); return; } // Faster path for staging textures that contents are ready to access on a CPU if (target->IsStaging()) { CaptureScreenshot screenshot(target, path); TextureData& data = screenshot.GetData(); data.Width = target->Width(); data.Height = target->Height(); data.Format = target->Format(); data.Depth = target->Depth(); data.Items.Resize(target->ArraySize()); for (int32 arrayIndex = 0; arrayIndex < target->ArraySize(); arrayIndex++) { auto& slice = data.Items[arrayIndex]; slice.Mips.Resize(target->MipLevels()); for (int32 mipIndex = 0; mipIndex < target->MipLevels(); mipIndex++) { auto& mip = slice.Mips[mipIndex]; if (target->GetData(arrayIndex, mipIndex, mip)) { LOG(Warning, "Cannot take screenshot. Failed to get texture data."); return; } } } screenshot.Run(); return; } // Create tasks auto saveTask = New(target, path); auto downloadTask = target->DownloadDataAsync(saveTask->GetData()); if (downloadTask == nullptr) { LOG(Warning, "Cannot capture screenshot. Cannot create download async task."); Delete(saveTask); return; } // Start downloadTask->ContinueWith(saveTask); downloadTask->Start(); } void Screenshot::Capture(SceneRenderTask* target, const StringView& path) { // Select default task if none provided if (target == nullptr) { Capture(path); return; } // Validate if ((target->Output == nullptr && target->SwapChain == nullptr) || (target->Output && !target->Output->IsAllocated())) { LOG(Warning, "Cannot take screenshot. Render task output is not allocated."); return; } if (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready) { LOG(Warning, "Cannot take screenshot. Graphics device is not ready."); return; } // Create tasks auto saveTask = New(target, path); Task* downloadTask; if (target->Output) downloadTask = target->Output->DownloadDataAsync(saveTask->GetData()); else downloadTask = target->SwapChain->DownloadDataAsync(saveTask->GetData()); if (downloadTask == nullptr) { LOG(Warning, "Cannot capture screenshot. Cannot create download async task."); Delete(saveTask); return; } // Start downloadTask->ContinueWith(saveTask); downloadTask->Start(); } void Screenshot::Capture(const StringView& path) { const auto mainTask = MainRenderTask::Instance; if (!mainTask) { LOG(Warning, "Cannot take screenshot. Missing main rendering task."); return; } Capture(mainTask, path); }