port: grp.SaveScreenShot via next-frame swapchain readback + JPEG
CPythonGraphic::SaveScreenShot queues a readback of the next presented frame (ScreenShotHost -> VulkanWindow::request_frame); WriteScreenShot runs the rest of 40250's function verbatim (X8R8G8B8 rows, SaveJPEG, EXIF tag). jpeg_save is encoded with stb_image_write. SHGetSpecialFolderPath returns $HOME/Documents (else cwd); CreateDirectory/_access/jpeg_save accept '\\'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c21e012965
commit
bda85eea50
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "platform/PackBackend.h"
|
||||
#include "platform/ScriptLib/PythonBoot.h"
|
||||
#include "platform/EterPythonLib/ScreenShotHost.h"
|
||||
#include "platform/UserInterface/ServerClock.h"
|
||||
#include "platform/EterBase/TraceErrorObserver.h"
|
||||
#include "platform/EterBase/FrameRateMode.h"
|
||||
@@ -188,6 +189,11 @@ int run_live_client(
|
||||
SDL_Log("live stage: PythonBoot started");
|
||||
#endif
|
||||
|
||||
ScreenShotHost::SetRequest([&renderer](const std::string& path) {
|
||||
return renderer.request_frame([path](const std::uint8_t* bgra, std::uint32_t width, std::uint32_t height) {
|
||||
ScreenShotHost::WriteScreenShot(path.c_str(), bgra, width, height, width * 4);
|
||||
});
|
||||
});
|
||||
SetPlatformServerTime(123456789);
|
||||
PythonBoot::SetUISafeInset(renderer.ui_safe_inset());
|
||||
PythonBoot::SetUISize(static_cast<int>(renderer.logical_width()), static_cast<int>(renderer.logical_height()));
|
||||
@@ -505,6 +511,7 @@ int run_live_client(
|
||||
const auto wall_ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
print_summary(renderer, completed, wall_ms, update_ms, std::move(frame_ms));
|
||||
|
||||
ScreenShotHost::SetRequest(nullptr);
|
||||
PythonBoot::Stop();
|
||||
#ifdef __ANDROID__
|
||||
SDL_Log("live stage: PythonBoot stopped");
|
||||
|
||||
@@ -139,6 +139,12 @@ void VulkanWindow::request_screenshot(std::string path, std::uint64_t frame) {
|
||||
screenshot_frame_ = frame;
|
||||
}
|
||||
|
||||
bool VulkanWindow::request_frame(FrameSink sink) {
|
||||
if (!swapchain_readable_) return false;
|
||||
frame_sinks_.push_back(std::move(sink));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t VulkanWindow::logical_width() const {
|
||||
int w = 0, h = 0;
|
||||
if (window_) SDL_GetWindowSize(window_, &w, &h);
|
||||
|
||||
@@ -120,6 +120,7 @@ void VulkanWindow::create_swapchain() {
|
||||
info.imageFormat = format.format; info.imageColorSpace = format.colorSpace; info.imageExtent = extent_;
|
||||
info.imageArrayLayers = 1; info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
|
||||
(capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
|
||||
swapchain_readable_ = (info.imageUsage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0;
|
||||
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
// Android surfaces can report a quarter-turn transform even when SDL's window is
|
||||
// already landscape. Rendering in SDL window coordinates and requesting that transform
|
||||
|
||||
+13
-3
@@ -493,7 +493,9 @@ void VulkanWindow::record_passes(std::uint32_t image_index, const FrameDraws& fr
|
||||
|
||||
VulkanWindow::Readback VulkanWindow::record_readbacks(std::uint32_t image_index) {
|
||||
Readback readback;
|
||||
readback.screenshot = !screenshot_path_.empty() && frame_number_ == screenshot_frame_;
|
||||
readback.bmp = !screenshot_path_.empty() && frame_number_ == screenshot_frame_;
|
||||
readback.sinks.swap(frame_sinks_);
|
||||
readback.screenshot = readback.bmp || !readback.sinks.empty();
|
||||
if (readback.screenshot) {
|
||||
const VkDeviceSize bytes = VkDeviceSize(extent_.width) * extent_.height * 4;
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
@@ -530,7 +532,7 @@ VulkanWindow::Readback VulkanWindow::record_readbacks(std::uint32_t image_index)
|
||||
}
|
||||
// Debug: MT_DUMP_RT=prefix writes each render target of the screenshot frame to <prefix>_<n>.pgm.
|
||||
const char* dump_rt = std::getenv("MT_DUMP_RT");
|
||||
if (readback.screenshot && dump_rt && *dump_rt) {
|
||||
if (readback.bmp && dump_rt && *dump_rt) {
|
||||
readback.dump_rt = dump_rt;
|
||||
const std::uint32_t texel = offscreen_format_ == VK_FORMAT_R5G6B5_UNORM_PACK16 ? 2 : 4;
|
||||
for (auto& entry : render_targets_) {
|
||||
@@ -578,7 +580,15 @@ void VulkanWindow::finish_readbacks(Readback& readback) {
|
||||
check(vkWaitForFences(device_, 1, &f_->fence, VK_TRUE, UINT64_MAX), "vkWaitForFences readback");
|
||||
void* mapped = nullptr;
|
||||
check(vkMapMemory(device_, readback.memory, 0, VK_WHOLE_SIZE, 0, &mapped), "vkMapMemory readback");
|
||||
write_bmp(screenshot_path_, static_cast<const std::uint8_t*>(mapped));
|
||||
if (readback.bmp) write_bmp(screenshot_path_, static_cast<const std::uint8_t*>(mapped));
|
||||
if (!readback.sinks.empty()) {
|
||||
const std::uint32_t w = extent_.width, h = extent_.height;
|
||||
std::vector<std::uint8_t> bgra(static_cast<const std::uint8_t*>(mapped),
|
||||
static_cast<const std::uint8_t*>(mapped) + std::size_t(w) * h * 4);
|
||||
if (swapchain_format_ == VK_FORMAT_R8G8B8A8_UNORM || swapchain_format_ == VK_FORMAT_R8G8B8A8_SRGB)
|
||||
for (std::size_t i = 0; i < bgra.size(); i += 4) std::swap(bgra[i], bgra[i + 2]);
|
||||
for (auto& sink : readback.sinks) sink(bgra.data(), w, h);
|
||||
}
|
||||
vkUnmapMemory(device_, readback.memory);
|
||||
const std::uint32_t texel = offscreen_format_ == VK_FORMAT_R5G6B5_UNORM_PACK16 ? 2 : 4;
|
||||
for (std::size_t n = 0; n < readback.rt_dumps.size(); ++n) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -133,6 +134,10 @@ public:
|
||||
double input_idle_seconds() const;
|
||||
|
||||
void request_screenshot(std::string path, std::uint64_t frame);
|
||||
// Hands the next presented frame to `sink` as top-down BGRA rows (grp.SaveScreenShot). False when the
|
||||
// swapchain images cannot be copied from.
|
||||
using FrameSink = std::function<void(const std::uint8_t* bgra, std::uint32_t width, std::uint32_t height)>;
|
||||
bool request_frame(FrameSink sink);
|
||||
std::uint64_t frame_number() const { return frame_number_; }
|
||||
|
||||
int msaa_samples() const { return int(samples_); }
|
||||
@@ -215,7 +220,9 @@ private:
|
||||
// Host copies of the swapchain image (--screenshot-out) and, with MT_DUMP_RT, of every render target.
|
||||
struct Readback {
|
||||
struct RtDump { VkBuffer buffer; VkDeviceMemory memory; std::uint32_t w, h; };
|
||||
bool screenshot = false;
|
||||
bool screenshot = false; // the swapchain image is copied (a BMP or a frame sink wants it)
|
||||
bool bmp = false; // --screenshot-out
|
||||
std::vector<FrameSink> sinks;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
const char* dump_rt = nullptr;
|
||||
@@ -398,6 +405,8 @@ private:
|
||||
// --screenshot-out: read back the swapchain image of one frame into a BMP file.
|
||||
std::string screenshot_path_;
|
||||
std::uint64_t screenshot_frame_ = 0;
|
||||
std::vector<FrameSink> frame_sinks_; // request_frame, served by the next frame's readback
|
||||
bool swapchain_readable_ = false; // the swapchain images have TRANSFER_SRC usage
|
||||
VkImage depth_image_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory depth_memory_ = VK_NULL_HANDLE;
|
||||
VkImageView depth_view_ = VK_NULL_HANDLE;
|
||||
|
||||
Reference in New Issue
Block a user