65 std::chrono::milliseconds timeout,
66 std::chrono::milliseconds poll_interval) ->
bool
68 const auto deadline = std::chrono::steady_clock::now() + timeout;
69 while(std::chrono::steady_clock::now() < deadline)
72 if(std::filesystem::exists(path, ec) && !ec)
74 const auto size = std::filesystem::file_size(path, ec);
78 std::this_thread::sleep_for(std::chrono::milliseconds(16));
82 std::this_thread::sleep_for(poll_interval);
130 const std::string&
tag,
131 std::chrono::milliseconds wait_timeout) ->
tool_result
136 const auto png_path = std::filesystem::path(stem.generic_string() +
".png");
137 auto state = std::make_shared<fbo_capture_state>();
140 [&ctx, &resolve_fbo, state]() ->
bool
142 auto fbo = resolve_fbo(ctx);
143 if(!fbo || !fbo->is_valid())
145 state->error =
"Viewport framebuffer is not available yet (panel may not have rendered)";
149 const auto& src_tex = fbo->get_texture(0);
150 if(!src_tex || !bgfx::isValid(src_tex->native_handle()))
152 state->error =
"Viewport color texture is not available";
156 state->width = src_tex->info.width;
157 state->height = src_tex->info.height;
158 if(state->width == 0 || state->height == 0)
160 state->error =
"Viewport framebuffer has zero size";
164 constexpr auto format = gfx::texture_format::RGBA8;
165 const uint64_t flags = BGFX_TEXTURE_BLIT_DST | BGFX_TEXTURE_READ_BACK | BGFX_SAMPLER_U_CLAMP |
166 BGFX_SAMPLER_V_CLAMP;
168 if(!bgfx::isValid(state->blit_tex))
170 state->error =
"Failed to create readback texture";
176 state->pixels.resize(info.storageSize);
180 gfx::blit(pass.
id, state->blit_tex, 0, 0, src_tex->native_handle());
184 std::chrono::milliseconds(10000));
188 return {.text =
"Timed out requesting screenshot on main thread", .is_error =
true};
192 return {.text = state->error.empty() ?
"Failed to request screenshot" : state->error, .is_error =
true};
195 const auto deadline = std::chrono::steady_clock::now() + wait_timeout;
197 while(std::chrono::steady_clock::now() < deadline)
204 std::chrono::milliseconds(2000));
205 if(frame_ready && *frame_ready)
210 std::this_thread::sleep_for(std::chrono::milliseconds(32));
218 if(bgfx::isValid(state->blit_tex))
221 state->blit_tex = BGFX_INVALID_HANDLE;
225 return {.text = fmt::format(
"Timed out waiting for GPU readback (frame {})", state->ready_frame),
229 std::string write_error;
231 [state, &png_path, &write_error]() ->
bool
233 const bool ok = write_rgba_png(png_path, state->width, state->height, state->pixels, write_error);
234 if(bgfx::isValid(state->blit_tex))
237 state->blit_tex = BGFX_INVALID_HANDLE;
241 std::chrono::milliseconds(10000));
245 return {.text =
"Timed out writing screenshot PNG on main thread", .is_error =
true};
249 return {.text = write_error.empty() ?
"Failed to write screenshot PNG" : write_error, .is_error =
true};
252 std::string read_error;
255 std::filesystem::remove(png_path, ec);
259 return {.text = read_error.empty() ?
"Failed to read screenshot PNG" : read_error, .is_error =
true};
263 result.
text = fmt::format(R
"({{"source":{},"path":{},"bytes":{},"width":{},"height":{},"mime":"image/png"}})",