载入中...
搜索中...
未找到
GraphicsCapabilities.cpp
浏览该文件的文档.
1// IRenderCapture capability provider.
2//
3// DevTools / MCP must not depend on the graphics module, so the graphics
4// module registers this implementation of the common render-capture
5// interface. The scene traversal for the entity-id mask and visible-entity
6// snapshot lives here too (graphics -> scene is a legal downward edge).
7
8#include "common/Capability.h"
10
11#include "common/ECS.h"
12#include "common/b64.h"
13#include "filesystem/FileData.h"
14#include "graphics/ClipSpace.h"
15#include "graphics/Graphics.h"
16#include "graphics/Mesh.h"
18#include "image/ImageData.h"
19#include "scene/SceneHost.h"
21
22#include <glm/gtc/matrix_transform.hpp>
23
24#include <algorithm>
25#include <cmath>
26#include <cstdio>
27#include <filesystem>
28#include <fstream>
29#include <string>
30#include <vector>
31
32namespace eve::graphics {
33namespace {
34
35constexpr float kPi = 3.14159265358979323846f;
36
37float degToRad(float d) { return d * kPi / 180.f; }
38
39std::string jsonEscape(const std::string &s) {
40 std::string out;
41 out.reserve(s.size() + 8);
42 for (char c : s) {
43 switch (c) {
44 case '"': out += "\\\""; break;
45 case '\\': out += "\\\\"; break;
46 case '\n': out += "\\n"; break;
47 case '\r': out += "\\r"; break;
48 case '\t': out += "\\t"; break;
49 default: out += c;
50 }
51 }
52 return out;
53}
54
55std::string num(float v) {
56 char buf[64];
57 std::snprintf(buf, sizeof(buf), "%g", static_cast<double>(v));
58 return buf;
59}
60
61std::string vec3Json(const glm::vec3 &v) {
62 return "[" + num(v.x) + "," + num(v.y) + "," + num(v.z) + "]";
63}
64
65class RenderCaptureImpl final : public eve::IRenderCapture {
66public:
67 Graphics *gfx() { return eve::ModuleManager::getInstance<Graphics>("Graphics"); }
68
69 Camera3D *camera() {
70 if (ecs::current()->getManager<Camera3D>() != nullptr) {
71 auto view = ecs::View<Camera3D, Camera3D::Data>();
72 for (auto it = view.begin(); it != view.end(); ++it) {
73 auto [data] = *it;
74 if (data->active && data->entity) return data->entity;
75 }
76 }
78 }
79
80 eve::RenderStatusInfo status() const override {
82 if (auto *g = const_cast<RenderCaptureImpl *>(this)->gfx()) {
83 info.width = g->getWidth();
84 info.height = g->getHeight();
85 info.pixelWidth = g->getPixelWidth();
86 info.pixelHeight = g->getPixelHeight();
87 info.had3DThisFrame = g->had3DThisFrame();
88 info.readbackEnabled = g->isScreenReadbackEnabled();
89 info.backend = g->getBackendName();
90 }
91 return info;
92 }
93
94 void setReadbackEnabled(bool enabled) override {
95 if (auto *g = gfx()) g->setScreenReadbackEnabled(enabled);
96 }
97
98 bool savePng(const std::string &path, int *outWidth, int *outHeight,
99 std::string *err) override {
100 auto *g = gfx();
101 if (!g) {
102 if (err) *err = "Graphics module not available";
103 return false;
104 }
105 g->setScreenReadbackEnabled(true);
106 image::ImageData *frame = nullptr;
107 try {
108 frame = g->newImageData();
109 } catch (...) {
110 if (err) *err = "no presented frame";
111 return false;
112 }
113 if (!frame) {
114 if (err) *err = "no presented frame (enable readback and render a frame first)";
115 return false;
116 }
117 filesystem::FileData *png = nullptr;
118 try {
119 png = frame->encode(image::ImageData::FormatHandler::ENCODED_PNG, "frame.png", false);
120 } catch (...) {
121 delete frame;
122 if (err) *err = "png encode failed";
123 return false;
124 }
125 if (!png) {
126 delete frame;
127 if (err) *err = "png encode returned null";
128 return false;
129 }
130 std::error_code ec;
131 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
132 std::ofstream out(path, std::ios::binary);
133 if (!out.good()) {
134 delete png;
135 delete frame;
136 if (err) *err = "cannot open output file: " + path;
137 return false;
138 }
139 out.write(static_cast<const char *>(png->getData()),
140 static_cast<std::streamsize>(png->getSize()));
141 const bool ok = out.good();
142 delete png;
143 delete frame;
144 if (outWidth) *outWidth = g->getPixelWidth();
145 if (outHeight) *outHeight = g->getPixelHeight();
146 if (!ok && err) *err = "failed writing: " + path;
147 return ok;
148 }
149
150 std::string capturePngDataUrl() override {
151 auto *g = gfx();
152 if (!g) return {};
153 g->setScreenReadbackEnabled(true);
154 image::ImageData *frame = nullptr;
155 try {
156 frame = g->newImageData();
157 } catch (...) {
158 return {};
159 }
160 if (!frame) return {};
161 filesystem::FileData *png = nullptr;
162 try {
163 png = frame->encode(image::ImageData::FormatHandler::ENCODED_PNG, "frame.png", false);
164 } catch (...) {
165 delete frame;
166 return {};
167 }
168 if (!png) {
169 delete frame;
170 return {};
171 }
172 size_t len = 0;
173 char *b64 = eve::b64_encode(static_cast<const char *>(png->getData()), png->getSize(), 0, len);
174 std::string url = "data:image/png;base64," + std::string(b64 ? b64 : "", len);
175 delete[] b64;
176 delete png;
177 delete frame;
178 return url;
179 }
180
181 bool ensureCamera() override { return camera() != nullptr; }
182
183 bool setCameraPose(float ex, float ey, float ez, float tx, float ty, float tz,
184 float fovYDeg) override {
185 auto *cam = camera();
186 if (!cam) return false;
187 cam->setActive(true);
188 cam->setEye(ex, ey, ez);
189 cam->setTarget(tx, ty, tz);
190 if (fovYDeg > 0.f) cam->setFov(fovYDeg);
191 return true;
192 }
193
194 bool setCameraPoseYawPitch(float ex, float ey, float ez, float yawDeg, float pitchDeg,
195 float fovYDeg) override {
196 auto *cam = camera();
197 if (!cam) return false;
198 cam->setActive(true);
199 const float yaw = degToRad(yawDeg);
200 const float pitch = degToRad(pitchDeg);
201 const glm::vec3 dir(std::cos(pitch) * std::sin(yaw), std::sin(pitch),
202 std::cos(pitch) * std::cos(yaw));
203 cam->setEye(ex, ey, ez);
204 cam->setTarget(ex + dir.x * 100.f, ey + dir.y * 100.f, ez + dir.z * 100.f);
205 if (fovYDeg > 0.f) cam->setFov(fovYDeg);
206 return true;
207 }
208
209 std::string cameraPoseJson() override {
210 auto *cam = camera();
211 if (!cam) return "{\"error\":\"no camera\"}";
212 auto d = cam->data();
213 std::string out = "{\"eye\":" + vec3Json(glm::vec3(d->eyeX, d->eyeY, d->eyeZ));
214 out += ",\"target\":" + vec3Json(glm::vec3(d->targetX, d->targetY, d->targetZ));
215 out += ",\"up\":" + vec3Json(glm::vec3(d->upX, d->upY, d->upZ));
216 out += ",\"fov\":" + num(d->fovYDeg);
217 out += ",\"near\":" + num(d->nearZ);
218 out += ",\"far\":" + num(d->farZ);
219 if (auto *g = gfx()) {
220 out += ",\"viewportWidth\":" + std::to_string(g->getPixelWidth());
221 out += ",\"viewportHeight\":" + std::to_string(g->getPixelHeight());
222 }
223 out += "}";
224 return out;
225 }
226
227 std::string visibleEntitiesJson(float fovYDeg, bool *ok) override {
228 return visibleAt(nullptr, nullptr, fovYDeg, ok);
229 }
230
231 std::string visibleEntitiesJsonAt(float ex, float ey, float ez, float tx, float ty, float tz,
232 float fovYDeg, bool *ok) override {
233 const glm::vec3 eye(ex, ey, ez);
234 const glm::vec3 target(tx, ty, tz);
235 return visibleAt(&eye, &target, fovYDeg, ok);
236 }
237
238 std::string entityIdMaskJson(int *outWidth, int *outHeight, bool *ok) override {
239 auto *g = gfx();
240 if (ok) *ok = false;
241 if (!g) return "{\"error\":\"Graphics module not available\"}";
242 auto *cam = camera();
243 if (!cam) return "{\"error\":\"no camera\"}";
244 auto d = cam->data();
245 const glm::vec3 eye(d->eyeX, d->eyeY, d->eyeZ);
246 const glm::vec3 target(d->targetX, d->targetY, d->targetZ);
247 const glm::vec3 up(d->upX, d->upY, d->upZ);
248 const int vw = g->getPixelWidth() > 0 ? g->getPixelWidth() : g->getWidth();
249 const int vh = g->getPixelHeight() > 0 ? g->getPixelHeight() : g->getHeight();
250 if (outWidth) *outWidth = vw;
251 if (outHeight) *outHeight = vh;
252 if (vw <= 0 || vh <= 0) return "{\"error\":\"empty viewport\"}";
253 const glm::mat4 viewM = glm::lookAtRH(eye, target, up);
254 const glm::mat4 projM = perspectiveVulkanRH_ZO(degToRad(d->fovYDeg), float(vw) / float(vh),
255 d->nearZ, d->farZ);
256 const glm::mat4 viewProj = projM * viewM;
257
258 std::vector<Graphics::EntityIdDraw> draws;
259 int id = 1;
260 std::string entities;
262 if (ecs::current()->getManager<scene::SceneHost>() != nullptr) {
263 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
264 scene::SceneHost::Tree>();
265 for (auto it = hostView.begin(); it != hostView.end(); ++it) {
266 auto [meta, tree] = *it;
267 if (!meta || !meta->entity || !tree) continue;
268 auto *host = meta->entity;
269 host->walkDepthFirst([&](scene::SceneHost *, int, scene::SceneNode &node) {
270 if (!node.visible || node.space != "3d") return;
271 const auto *l = host->findLink(&node, scene::findLinkKind("renderable3d"));
272 if (!l || !l->target) return;
273 auto *r = static_cast<Renderable3D *>(l->target);
274 auto mr = r->meshRenderer();
275 Mesh *mesh = mr->mesh;
276 if (!mesh || !mesh->gpuHandle) return;
277 Graphics::EntityIdDraw draw;
278 draw.mesh = mesh;
279 draw.model = node.world;
280 const uint32_t packed = uint32_t(id);
281 draw.idColor = glm::vec4((packed & 255u) / 255.f,
282 ((packed >> 8) & 255u) / 255.f,
283 ((packed >> 16) & 255u) / 255.f, 1.f);
284 draws.push_back(draw);
285 if (!entities.empty()) entities += ",";
286 entities += "{\"id\":" + std::to_string(id) +
287 ",\"asset\":\"" + jsonEscape(node.name.empty() ? node.id : node.name) +
288 "\",\"node\":\"" + jsonEscape(node.id.empty() ? node.name : node.id) + "\"}";
289 ++id;
290 });
291 }
292 }
293 image::ImageData *img = g->renderEntityIdMask(draws, viewProj, vw, vh);
294 if (!img) return "{\"error\":\"id mask unsupported\"}";
295 delete img;
296 if (ok) *ok = true;
297 return "{\"camera\":" + vec3Json(eye) + ",\"viewportWidth\":" + std::to_string(vw) +
298 ",\"viewportHeight\":" + std::to_string(vh) + ",\"entities\":[" + entities +
299 "],\"count\":" + std::to_string(id - 1) + "}";
300 }
301
302 bool entityIdMaskPng(const std::string &path, std::string *err) override {
303 auto *g = gfx();
304 if (!g) {
305 if (err) *err = "Graphics module not available";
306 return false;
307 }
308 auto *cam = camera();
309 if (!cam) {
310 if (err) *err = "no camera";
311 return false;
312 }
313 auto d = cam->data();
314 const glm::vec3 eye(d->eyeX, d->eyeY, d->eyeZ);
315 const glm::vec3 target(d->targetX, d->targetY, d->targetZ);
316 const glm::vec3 up(d->upX, d->upY, d->upZ);
317 const int vw = g->getPixelWidth() > 0 ? g->getPixelWidth() : g->getWidth();
318 const int vh = g->getPixelHeight() > 0 ? g->getPixelHeight() : g->getHeight();
319 if (vw <= 0 || vh <= 0) {
320 if (err) *err = "empty viewport";
321 return false;
322 }
323 const glm::mat4 viewM = glm::lookAtRH(eye, target, up);
324 const glm::mat4 projM = perspectiveVulkanRH_ZO(degToRad(d->fovYDeg), float(vw) / float(vh),
325 d->nearZ, d->farZ);
326 const glm::mat4 viewProj = projM * viewM;
327 std::vector<Graphics::EntityIdDraw> draws;
328 int id = 1;
330 if (ecs::current()->getManager<scene::SceneHost>() != nullptr) {
331 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
332 scene::SceneHost::Tree>();
333 for (auto it = hostView.begin(); it != hostView.end(); ++it) {
334 auto [meta, tree] = *it;
335 if (!meta || !meta->entity || !tree) continue;
336 auto *host = meta->entity;
337 host->walkDepthFirst([&](scene::SceneHost *, int, scene::SceneNode &node) {
338 if (!node.visible || node.space != "3d") return;
339 const auto *l = host->findLink(&node, scene::findLinkKind("renderable3d"));
340 if (!l || !l->target) return;
341 auto *r = static_cast<Renderable3D *>(l->target);
342 auto mr = r->meshRenderer();
343 Mesh *mesh = mr->mesh;
344 if (!mesh || !mesh->gpuHandle) return;
345 Graphics::EntityIdDraw draw;
346 draw.mesh = mesh;
347 draw.model = node.world;
348 const uint32_t packed = uint32_t(id);
349 draw.idColor = glm::vec4((packed & 255u) / 255.f,
350 ((packed >> 8) & 255u) / 255.f,
351 ((packed >> 16) & 255u) / 255.f, 1.f);
352 draws.push_back(draw);
353 ++id;
354 });
355 }
356 }
357 image::ImageData *img = g->renderEntityIdMask(draws, viewProj, vw, vh);
358 if (!img) {
359 if (err) *err = "id mask unsupported";
360 return false;
361 }
362 filesystem::FileData *png = nullptr;
363 try {
364 png = img->encode(image::ImageData::FormatHandler::ENCODED_PNG, "id.png", false);
365 } catch (...) {
366 delete img;
367 if (err) *err = "png encode failed";
368 return false;
369 }
370 bool ok = false;
371 if (png) {
372 std::error_code ec;
373 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
374 std::ofstream out(path, std::ios::binary);
375 if (out.good()) {
376 out.write(static_cast<const char *>(png->getData()),
377 static_cast<std::streamsize>(png->getSize()));
378 ok = out.good();
379 }
380 }
381 delete png;
382 delete img;
383 if (!ok && err) *err = "failed writing id mask";
384 return ok;
385 }
386
387 bool gbufferPng(const std::string &name, const std::string &path, std::string *err) override {
388 auto *g = gfx();
389 if (!g) {
390 if (err) *err = "Graphics module not available";
391 return false;
392 }
393 image::ImageData *img = g->readGBufferToImageData(name);
394 if (!img) {
395 if (err) *err = "G-buffer attachment unavailable: " + name;
396 return false;
397 }
398 filesystem::FileData *png = nullptr;
399 try {
400 png = img->encode(image::ImageData::FormatHandler::ENCODED_PNG, "buf.png", false);
401 } catch (...) {
402 delete img;
403 if (err) *err = "png encode failed";
404 return false;
405 }
406 const bool ok = png != nullptr;
407 if (ok) {
408 std::error_code ec;
409 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
410 std::ofstream out(path, std::ios::binary);
411 if (out.good()) {
412 out.write(static_cast<const char *>(png->getData()),
413 static_cast<std::streamsize>(png->getSize()));
414 }
415 }
416 delete png;
417 delete img;
418 if (!ok && err) *err = "png encode returned null";
419 return ok;
420 }
421
422private:
423 std::string visibleAt(const glm::vec3 *eye, const glm::vec3 *target, float fovYDeg,
424 bool *ok) {
425 auto *g = gfx();
426 if (ok) *ok = false;
427 auto *cam = camera();
428
429 glm::vec3 camEye(0.f, 1.8f, 3.f);
430 glm::vec3 camTarget(0.f, 1.2f, 0.f);
431 float camFov = 60.f;
432 float camNear = 0.1f, camFar = 100.f;
433 if (cam) {
434 auto d = cam->data();
435 camEye = glm::vec3(d->eyeX, d->eyeY, d->eyeZ);
436 camTarget = glm::vec3(d->targetX, d->targetY, d->targetZ);
437 camFov = d->fovYDeg;
438 camNear = d->nearZ;
439 camFar = d->farZ;
440 }
441 if (eye && target) {
442 camEye = *eye;
443 camTarget = *target;
444 }
445 if (fovYDeg > 0.f) camFov = fovYDeg;
446
447 const int vw = g ? g->getPixelWidth() : 1280;
448 const int vh = g ? g->getPixelHeight() : 720;
449 const glm::vec3 up(0.f, 1.f, 0.f);
450 const glm::mat4 view = glm::lookAtRH(camEye, camTarget, up);
451 const glm::mat4 proj = perspectiveVulkanRH_ZO(degToRad(camFov), float(vw) / float(vh),
452 camNear, camFar);
453 const glm::mat4 viewProj = proj * view;
454
455 static const glm::vec3 kLocalCorners[8] = {
456 {-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f},
457 {-0.5f, -0.5f, 0.5f}, {0.5f, 0.5f, -0.5f}, {0.5f, -0.5f, 0.5f},
458 {-0.5f, 0.5f, 0.5f}, {0.5f, 0.5f, 0.5f}};
459
460 std::string entities;
461 int count = 0;
463 if (ecs::current()->getManager<scene::SceneHost>() != nullptr) {
464 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
465 scene::SceneHost::Tree>();
466 for (auto it = hostView.begin(); it != hostView.end(); ++it) {
467 auto [meta, tree] = *it;
468 if (!meta || !meta->entity || !tree) continue;
469 auto *host = meta->entity;
470 host->walkDepthFirst([&](scene::SceneHost *, int, scene::SceneNode &node) {
471 if (!node.visible || node.space != "3d") return;
472 float minX = 1e30f, minY = 1e30f, minZ = 1e30f;
473 float maxX = -1e30f, maxY = -1e30f, maxZ = -1e30f;
474 float scMinX = 1e30f, scMinY = 1e30f;
475 float scMaxX = -1e30f, scMaxY = -1e30f;
476 bool anyInFront = false;
477 for (const auto &c : kLocalCorners) {
478 const glm::vec4 w = node.world * glm::vec4(c, 1.f);
479 minX = std::min(minX, w.x);
480 minY = std::min(minY, w.y);
481 minZ = std::min(minZ, w.z);
482 maxX = std::max(maxX, w.x);
483 maxY = std::max(maxY, w.y);
484 maxZ = std::max(maxZ, w.z);
485 const glm::vec4 clip = viewProj * w;
486 if (clip.w <= 1e-6f) continue;
487 anyInFront = true;
488 const float ndcX = clip.x / clip.w;
489 const float ndcY = clip.y / clip.w;
490 const float sx = (ndcX * 0.5f + 0.5f) * float(vw);
491 const float sy = (ndcY * 0.5f + 0.5f) * float(vh);
492 scMinX = std::min(scMinX, sx);
493 scMinY = std::min(scMinY, sy);
494 scMaxX = std::max(scMaxX, sx);
495 scMaxY = std::max(scMaxY, sy);
496 }
497 if (!anyInFront) return;
498 if (scMaxX < 0.f || scMinX > float(vw) || scMaxY < 0.f ||
499 scMinY > float(vh))
500 return;
501 if (!entities.empty()) entities += ",";
502 entities += "{\"id\":\"" + jsonEscape(node.id.empty() ? node.name : node.id) +
503 "\",\"asset\":\"" +
504 jsonEscape(node.name.empty() ? node.id : node.name) +
505 "\",\"world_aabb\":{\"min\":" + vec3Json(glm::vec3(minX, minY, minZ)) +
506 ",\"max\":" + vec3Json(glm::vec3(maxX, maxY, maxZ)) +
507 "},\"screen_bbox\":{\"x\":" + num(scMinX) + ",\"y\":" + num(scMinY) +
508 ",\"w\":" + num(scMaxX - scMinX) + ",\"h\":" + num(scMaxY - scMinY) +
509 "}}";
510 ++count;
511 });
512 }
513 }
514 if (ok) *ok = true;
515 return "{\"camera\":" + vec3Json(camEye) + ",\"cameraTarget\":" + vec3Json(camTarget) +
516 ",\"fov\":" + num(camFov) + ",\"viewportWidth\":" + std::to_string(vw) +
517 ",\"viewportHeight\":" + std::to_string(vh) + ",\"entities\":[" + entities +
518 "],\"count\":" + std::to_string(count) + "}";
519 }
520};
521
522} // namespace
523
525 static RenderCaptureImpl impl;
527}
528
529} // namespace eve::graphics
void * impl
std::string id
int w
JobStatus status
uint32_t c
Renderable3D::MeshRenderer * mr
glm::vec3 eye
Mesh * mesh
glm::mat4 viewProj
glm::mat4 view
glm::mat4 proj
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
bool enabled
int d
int v
V3 dir
Definition TreeMesh.cpp:121
uint32_t s
Definition Weather.cpp:28
Frame capture + camera + visible-entity inspection (graphics).
static Camera3D * createCamera()
I * query()
Definition Capability.h:77
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition AnimTrail.h:6
glm::mat4 perspectiveVulkanRH_ZO(float fovyRad, float aspect, float zNear, float zFar)
Right-handed, zero-to-one depth perspective for Vulkan swapchains.
Definition ClipSpace.h:20
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
Definition NodeDesc.cpp:214
int findLinkKind(const char *kind)
Definition SceneLink.cpp:39
char * b64_encode(const char *src, size_t srclen, size_t linelen, size_t &dstlen)
Base64-encode data.
Definition b64.cpp:28
Render surface / frame state snapshot (value type).
std::string id
Definition NodeDesc.h:16
std::string name
Definition NodeDesc.h:19
std::string space
"2d" or "3d" (string enum per module convention).
Definition NodeDesc.h:21