载入中...
搜索中...
未找到
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
9
10#include "common/Capability.h"
13
14#include "common/ECS.h"
15#include "common/b64.h"
16#include "filesystem/FileData.h"
17#include "graphics/ClipSpace.h"
18#include "graphics/Graphics.h"
19#include "graphics/Mesh.h"
24#include "image/ImageData.h"
25#include "scene/SceneHost.h"
27
28#include <glm/gtc/matrix_transform.hpp>
29
30#include <algorithm>
31#include <cmath>
32#include <cstdio>
33#include <filesystem>
34#include <fstream>
35#include <string>
36#include <memory>
37#include <unordered_map>
38#include <unordered_set>
39#include <vector>
40
41namespace eve::graphics {
42namespace {
43
44constexpr float kPi = 3.14159265358979323846f;
45
46float degToRad(float d) { return d * kPi / 180.f; }
47
48std::string jsonEscape(const std::string &s) {
49 std::string out;
50 out.reserve(s.size() + 8);
51 for (char c : s) {
52 switch (c) {
53 case '"': out += "\\\""; break;
54 case '\\': out += "\\\\"; break;
55 case '\n': out += "\\n"; break;
56 case '\r': out += "\\r"; break;
57 case '\t': out += "\\t"; break;
58 default: out += c;
59 }
60 }
61 return out;
62}
63
64std::string num(float v) {
65 char buf[64];
66 std::snprintf(buf, sizeof(buf), "%g", static_cast<double>(v));
67 return buf;
68}
69
70std::string vec3Json(const glm::vec3 &v) {
71 return "[" + num(v.x) + "," + num(v.y) + "," + num(v.z) + "]";
72}
73
74class RenderCaptureImpl final : public eve::IRenderCapture {
75public:
76 Graphics *gfx() { return eve::ModuleManager::getInstance<Graphics>("Graphics"); }
77
78 Camera3D *camera() {
79 if (ecs::current()->getManager<Camera3D>() != nullptr) {
80 auto view = ecs::View<Camera3D, Camera3D::Data>();
81 for (auto it = view.begin(); it != view.end(); ++it) {
82 auto [data] = *it;
83 if (data->active && data->entity) return data->entity;
84 }
85 }
87 }
88
89 eve::RenderStatusInfo status() const override {
91 if (auto *g = const_cast<RenderCaptureImpl *>(this)->gfx()) {
92 info.width = g->getWidth();
93 info.height = g->getHeight();
94 info.pixelWidth = g->getPixelWidth();
95 info.pixelHeight = g->getPixelHeight();
96 info.had3DThisFrame = g->had3DThisFrame();
97 info.readbackEnabled = g->isScreenReadbackEnabled();
98 info.backend = g->getBackendName();
99 }
100 return info;
101 }
102
103 void setReadbackEnabled(bool enabled) override {
104 if (auto *g = gfx()) g->setScreenReadbackEnabled(enabled);
105 }
106
107 bool savePng(const std::string &path, int *outWidth, int *outHeight,
108 std::string *err) override {
109 auto *g = gfx();
110 if (!g) {
111 if (err) *err = "Graphics module not available";
112 return false;
113 }
114 g->setScreenReadbackEnabled(true);
115 image::ImageData *frame = nullptr;
116 try {
117 frame = g->newImageData();
118 } catch (...) {
119 if (err)
120 *err = "no presented frame yet: readback was just enabled and the "
121 "copy lands on the next present — call again after one frame "
122 "(screenshots need a running, presented game loop)";
123 return false;
124 }
125 if (!frame) {
126 if (err) *err = "no presented frame (enable readback and render a frame first)";
127 return false;
128 }
129 filesystem::FileData *png = nullptr;
130 try {
131 png = frame->encode(image::ImageData::FormatHandler::ENCODED_PNG, "frame.png", false);
132 } catch (...) {
133 delete frame;
134 if (err) *err = "png encode failed";
135 return false;
136 }
137 if (!png) {
138 delete frame;
139 if (err) *err = "png encode returned null";
140 return false;
141 }
142 std::error_code ec;
143 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
144 std::ofstream out(path, std::ios::binary);
145 if (!out.good()) {
146 delete png;
147 delete frame;
148 if (err) *err = "cannot open output file: " + path;
149 return false;
150 }
151 out.write(static_cast<const char *>(png->getData()),
152 static_cast<std::streamsize>(png->getSize()));
153 const bool ok = out.good();
154 delete png;
155 delete frame;
156 if (outWidth) *outWidth = g->getPixelWidth();
157 if (outHeight) *outHeight = g->getPixelHeight();
158 if (!ok && err) *err = "failed writing: " + path;
159 return ok;
160 }
161
162 std::string capturePngDataUrl() override {
163 auto *g = gfx();
164 if (!g) return {};
165 g->setScreenReadbackEnabled(true);
166 image::ImageData *frame = nullptr;
167 try {
168 frame = g->newImageData();
169 } catch (...) {
170 return {};
171 }
172 if (!frame) return {};
173 filesystem::FileData *png = nullptr;
174 try {
175 png = frame->encode(image::ImageData::FormatHandler::ENCODED_PNG, "frame.png", false);
176 } catch (...) {
177 delete frame;
178 return {};
179 }
180 if (!png) {
181 delete frame;
182 return {};
183 }
184 size_t len = 0;
185 char *b64 = eve::b64_encode(static_cast<const char *>(png->getData()), png->getSize(), 0, len);
186 std::string url = "data:image/png;base64," + std::string(b64 ? b64 : "", len);
187 delete[] b64;
188 delete png;
189 delete frame;
190 return url;
191 }
192
193 bool ensureCamera() override { return camera() != nullptr; }
194
195 bool setCameraPose(float ex, float ey, float ez, float tx, float ty, float tz,
196 float fovYDeg) override {
197 auto *cam = camera();
198 if (!cam) return false;
199 cam->setActive(true);
200 cam->setEye(ex, ey, ez);
201 cam->setTarget(tx, ty, tz);
202 if (fovYDeg > 0.f) cam->setFov(fovYDeg);
203 return true;
204 }
205
206 bool setCameraPoseYawPitch(float ex, float ey, float ez, float yawDeg, float pitchDeg,
207 float fovYDeg) override {
208 auto *cam = camera();
209 if (!cam) return false;
210 cam->setActive(true);
211 const float yaw = degToRad(yawDeg);
212 const float pitch = degToRad(pitchDeg);
213 const glm::vec3 dir(std::cos(pitch) * std::sin(yaw), std::sin(pitch),
214 std::cos(pitch) * std::cos(yaw));
215 cam->setEye(ex, ey, ez);
216 cam->setTarget(ex + dir.x * 100.f, ey + dir.y * 100.f, ez + dir.z * 100.f);
217 if (fovYDeg > 0.f) cam->setFov(fovYDeg);
218 return true;
219 }
220
221 std::string cameraPoseJson() override {
222 auto *cam = camera();
223 if (!cam) return "{\"error\":\"no camera\"}";
224 auto d = cam->data();
225 std::string out = "{\"eye\":" + vec3Json(glm::vec3(d->eyeX, d->eyeY, d->eyeZ));
226 out += ",\"target\":" + vec3Json(glm::vec3(d->targetX, d->targetY, d->targetZ));
227 out += ",\"up\":" + vec3Json(glm::vec3(d->upX, d->upY, d->upZ));
228 out += ",\"fov\":" + num(d->fovYDeg);
229 out += ",\"near\":" + num(d->nearZ);
230 out += ",\"far\":" + num(d->farZ);
231 if (auto *g = gfx()) {
232 out += ",\"viewportWidth\":" + std::to_string(g->getPixelWidth());
233 out += ",\"viewportHeight\":" + std::to_string(g->getPixelHeight());
234 }
235 out += "}";
236 return out;
237 }
238
239 eve::Result<eve::Renderable3DInfo> inspectRenderable3D(
240 std::uint32_t entityId, std::uint32_t generation) override {
241 ecs::EntityHandle handle{ecs::current(), std::type_index(typeid(Renderable3D)),
242 entityId, generation};
243 auto* renderable = dynamic_cast<Renderable3D*>(ecs::try_get(handle));
244 if (!renderable)
247 "Renderable3D handle is missing or stale", "entity"));
248 auto transform = renderable->transform();
249 auto renderer = renderable->meshRenderer();
251 info.entityId = renderable->id;
252 info.generation = renderable->generation;
253 info.x = transform->x;
254 info.y = transform->y;
255 info.z = transform->z;
256 info.tintR = renderer->r;
257 info.tintG = renderer->g;
258 info.tintB = renderer->b;
259 info.tintA = renderer->a;
260 info.metallic = renderer->metallic;
261 info.roughness = renderer->roughness;
262 info.parallaxScale = renderer->parallaxScale;
263 info.visible = renderer->visible;
264 info.receiveLight = renderer->receiveLight;
265 info.castShadow = renderer->castShadow;
266 info.receiveShadow = renderer->receiveShadow;
267 info.hasPackedMaterial = renderer->material != nullptr;
268 info.hasTexture = renderer->texture != nullptr || renderer->normalTexture != nullptr ||
269 renderer->heightTexture != nullptr;
270 info.hasShader = renderer->shader != nullptr;
272 }
273
274 std::string visibleEntitiesJson(float fovYDeg, bool *ok) override {
275 return visibleAt(nullptr, nullptr, fovYDeg, ok);
276 }
277
278 std::string visibleEntitiesJsonAt(float ex, float ey, float ez, float tx, float ty, float tz,
279 float fovYDeg, bool *ok) override {
280 const glm::vec3 eye(ex, ey, ez);
281 const glm::vec3 target(tx, ty, tz);
282 return visibleAt(&eye, &target, fovYDeg, ok);
283 }
284
285 std::string entityIdMaskJson(int *outWidth, int *outHeight, bool *ok) override {
286 auto *g = gfx();
287 if (ok) *ok = false;
288 if (!g) return "{\"error\":\"Graphics module not available\"}";
289 auto *cam = camera();
290 if (!cam) return "{\"error\":\"no camera\"}";
291 auto d = cam->data();
292 const glm::vec3 eye(d->eyeX, d->eyeY, d->eyeZ);
293 const glm::vec3 target(d->targetX, d->targetY, d->targetZ);
294 const glm::vec3 up(d->upX, d->upY, d->upZ);
295 const int vw = g->getPixelWidth() > 0 ? g->getPixelWidth() : g->getWidth();
296 const int vh = g->getPixelHeight() > 0 ? g->getPixelHeight() : g->getHeight();
297 if (outWidth) *outWidth = vw;
298 if (outHeight) *outHeight = vh;
299 if (vw <= 0 || vh <= 0) return "{\"error\":\"empty viewport\"}";
300 const glm::mat4 viewM = glm::lookAtRH(eye, target, up);
301 const glm::mat4 projM = cameraProjectionVulkanRH_ZO(
302 d->orthographic, degToRad(d->fovYDeg), d->orthoHeight,
303 float(vw) / float(vh), d->nearZ, d->farZ);
304 const glm::mat4 viewProj = projM * viewM;
305
306 std::vector<Graphics::EntityIdDraw> draws;
307 int id = 1;
308 std::string entities;
310 if (ecs::current()->getManager<scene::SceneHost>() != nullptr) {
311 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
312 scene::SceneHost::Tree>();
313 for (auto it = hostView.begin(); it != hostView.end(); ++it) {
314 auto [meta, tree] = *it;
315 if (!meta || !meta->entity || !tree) continue;
316 auto *host = meta->entity;
317 host->walkDepthFirst([&](scene::SceneHost *, int, scene::SceneNode &node) {
318 if (!node.visible || node.space != "3d") return;
319 const auto *l = host->findLink(&node, scene::findLinkKind("renderable3d"));
320 if (!l || !l->target) return;
321 auto *r = static_cast<Renderable3D *>(l->target);
322 auto mr = r->meshRenderer();
323 Mesh *mesh = mr->mesh;
324 if (!mesh || !mesh->gpuHandle) return;
325 Graphics::EntityIdDraw draw;
326 draw.mesh = mesh;
327 draw.model = node.world;
328 const uint32_t packed = uint32_t(id);
329 draw.idColor = glm::vec4((packed & 255u) / 255.f,
330 ((packed >> 8) & 255u) / 255.f,
331 ((packed >> 16) & 255u) / 255.f, 1.f);
332 draws.push_back(draw);
333 if (!entities.empty()) entities += ",";
334 entities += "{\"id\":" + std::to_string(id) +
335 ",\"asset\":\"" + jsonEscape(node.name.empty() ? node.id : node.name) +
336 "\",\"node\":\"" + jsonEscape(node.id.empty() ? node.name : node.id) + "\"}";
337 ++id;
338 });
339 }
340 }
341 image::ImageData *img = g->renderEntityIdMask(draws, viewProj, vw, vh);
342 if (!img) return "{\"error\":\"id mask unsupported\"}";
343 delete img;
344 if (ok) *ok = true;
345 return "{\"camera\":" + vec3Json(eye) + ",\"viewportWidth\":" + std::to_string(vw) +
346 ",\"viewportHeight\":" + std::to_string(vh) + ",\"entities\":[" + entities +
347 "],\"count\":" + std::to_string(id - 1) + "}";
348 }
349
350 bool entityIdMaskPng(const std::string &path, std::string *err) override {
351 auto *g = gfx();
352 if (!g) {
353 if (err) *err = "Graphics module not available";
354 return false;
355 }
356 auto *cam = camera();
357 if (!cam) {
358 if (err) *err = "no camera";
359 return false;
360 }
361 auto d = cam->data();
362 const glm::vec3 eye(d->eyeX, d->eyeY, d->eyeZ);
363 const glm::vec3 target(d->targetX, d->targetY, d->targetZ);
364 const glm::vec3 up(d->upX, d->upY, d->upZ);
365 const int vw = g->getPixelWidth() > 0 ? g->getPixelWidth() : g->getWidth();
366 const int vh = g->getPixelHeight() > 0 ? g->getPixelHeight() : g->getHeight();
367 if (vw <= 0 || vh <= 0) {
368 if (err) *err = "empty viewport";
369 return false;
370 }
371 const glm::mat4 viewM = glm::lookAtRH(eye, target, up);
372 const glm::mat4 projM = cameraProjectionVulkanRH_ZO(
373 d->orthographic, degToRad(d->fovYDeg), d->orthoHeight,
374 float(vw) / float(vh), d->nearZ, d->farZ);
375 const glm::mat4 viewProj = projM * viewM;
376 std::vector<Graphics::EntityIdDraw> draws;
377 int id = 1;
379 if (ecs::current()->getManager<scene::SceneHost>() != nullptr) {
380 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
381 scene::SceneHost::Tree>();
382 for (auto it = hostView.begin(); it != hostView.end(); ++it) {
383 auto [meta, tree] = *it;
384 if (!meta || !meta->entity || !tree) continue;
385 auto *host = meta->entity;
386 host->walkDepthFirst([&](scene::SceneHost *, int, scene::SceneNode &node) {
387 if (!node.visible || node.space != "3d") return;
388 const auto *l = host->findLink(&node, scene::findLinkKind("renderable3d"));
389 if (!l || !l->target) return;
390 auto *r = static_cast<Renderable3D *>(l->target);
391 auto mr = r->meshRenderer();
392 Mesh *mesh = mr->mesh;
393 if (!mesh || !mesh->gpuHandle) return;
394 Graphics::EntityIdDraw draw;
395 draw.mesh = mesh;
396 draw.model = node.world;
397 const uint32_t packed = uint32_t(id);
398 draw.idColor = glm::vec4((packed & 255u) / 255.f,
399 ((packed >> 8) & 255u) / 255.f,
400 ((packed >> 16) & 255u) / 255.f, 1.f);
401 draws.push_back(draw);
402 ++id;
403 });
404 }
405 }
406 image::ImageData *img = g->renderEntityIdMask(draws, viewProj, vw, vh);
407 if (!img) {
408 if (err) *err = "id mask unsupported";
409 return false;
410 }
411 filesystem::FileData *png = nullptr;
412 try {
413 png = img->encode(image::ImageData::FormatHandler::ENCODED_PNG, "id.png", false);
414 } catch (...) {
415 delete img;
416 if (err) *err = "png encode failed";
417 return false;
418 }
419 bool ok = false;
420 if (png) {
421 std::error_code ec;
422 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
423 std::ofstream out(path, std::ios::binary);
424 if (out.good()) {
425 out.write(static_cast<const char *>(png->getData()),
426 static_cast<std::streamsize>(png->getSize()));
427 ok = out.good();
428 }
429 }
430 delete png;
431 delete img;
432 if (!ok && err) *err = "failed writing id mask";
433 return ok;
434 }
435
436 bool gbufferPng(const std::string &name, const std::string &path, std::string *err) override {
437 auto *g = gfx();
438 if (!g) {
439 if (err) *err = "Graphics module not available";
440 return false;
441 }
442 image::ImageData *img = g->readGBufferToImageData(name);
443 if (!img) {
444 if (err) *err = "G-buffer attachment unavailable: " + name;
445 return false;
446 }
447 filesystem::FileData *png = nullptr;
448 try {
449 png = img->encode(image::ImageData::FormatHandler::ENCODED_PNG, "buf.png", false);
450 } catch (...) {
451 delete img;
452 if (err) *err = "png encode failed";
453 return false;
454 }
455 const bool ok = png != nullptr;
456 if (ok) {
457 std::error_code ec;
458 std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
459 std::ofstream out(path, std::ios::binary);
460 if (out.good()) {
461 out.write(static_cast<const char *>(png->getData()),
462 static_cast<std::streamsize>(png->getSize()));
463 }
464 }
465 delete png;
466 delete img;
467 if (!ok && err) *err = "png encode returned null";
468 return ok;
469 }
470
471private:
472 std::string visibleAt(const glm::vec3 *eye, const glm::vec3 *target, float fovYDeg,
473 bool *ok) {
474 auto *g = gfx();
475 if (ok) *ok = false;
476 auto *cam = camera();
477
478 glm::vec3 camEye(0.f, 1.8f, 3.f);
479 glm::vec3 camTarget(0.f, 1.2f, 0.f);
480 float camFov = 60.f;
481 float camNear = 0.1f, camFar = 100.f;
482 if (cam) {
483 auto d = cam->data();
484 camEye = glm::vec3(d->eyeX, d->eyeY, d->eyeZ);
485 camTarget = glm::vec3(d->targetX, d->targetY, d->targetZ);
486 camFov = d->fovYDeg;
487 camNear = d->nearZ;
488 camFar = d->farZ;
489 }
490 if (eye && target) {
491 camEye = *eye;
492 camTarget = *target;
493 }
494 if (fovYDeg > 0.f) camFov = fovYDeg;
495
496 const int vw = g ? g->getPixelWidth() : 1280;
497 const int vh = g ? g->getPixelHeight() : 720;
498 const glm::vec3 up(0.f, 1.f, 0.f);
499 const glm::mat4 view = glm::lookAtRH(camEye, camTarget, up);
500 const glm::mat4 proj = perspectiveVulkanRH_ZO(degToRad(camFov), float(vw) / float(vh),
501 camNear, camFar);
502 const glm::mat4 viewProj = proj * view;
503
504 static const glm::vec3 kLocalCorners[8] = {
505 {-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {-0.5f, 0.5f, -0.5f},
506 {-0.5f, -0.5f, 0.5f}, {0.5f, 0.5f, -0.5f}, {0.5f, -0.5f, 0.5f},
507 {-0.5f, 0.5f, 0.5f}, {0.5f, 0.5f, 0.5f}};
508
509 std::string entities;
510 int count = 0;
512 if (ecs::current()->getManager<scene::SceneHost>() != nullptr) {
513 auto hostView = ecs::View<scene::SceneHost, scene::SceneHost::Meta,
514 scene::SceneHost::Tree>();
515 for (auto it = hostView.begin(); it != hostView.end(); ++it) {
516 auto [meta, tree] = *it;
517 if (!meta || !meta->entity || !tree) continue;
518 auto *host = meta->entity;
519 host->walkDepthFirst([&](scene::SceneHost *, int, scene::SceneNode &node) {
520 if (!node.visible || node.space != "3d") return;
521 float minX = 1e30f, minY = 1e30f, minZ = 1e30f;
522 float maxX = -1e30f, maxY = -1e30f, maxZ = -1e30f;
523 float scMinX = 1e30f, scMinY = 1e30f;
524 float scMaxX = -1e30f, scMaxY = -1e30f;
525 bool anyInFront = false;
526 for (const auto &c : kLocalCorners) {
527 const glm::vec4 w = node.world * glm::vec4(c, 1.f);
528 minX = std::min(minX, w.x);
529 minY = std::min(minY, w.y);
530 minZ = std::min(minZ, w.z);
531 maxX = std::max(maxX, w.x);
532 maxY = std::max(maxY, w.y);
533 maxZ = std::max(maxZ, w.z);
534 const glm::vec4 clip = viewProj * w;
535 if (clip.w <= 1e-6f) continue;
536 anyInFront = true;
537 const float ndcX = clip.x / clip.w;
538 const float ndcY = clip.y / clip.w;
539 const float sx = (ndcX * 0.5f + 0.5f) * float(vw);
540 const float sy = (ndcY * 0.5f + 0.5f) * float(vh);
541 scMinX = std::min(scMinX, sx);
542 scMinY = std::min(scMinY, sy);
543 scMaxX = std::max(scMaxX, sx);
544 scMaxY = std::max(scMaxY, sy);
545 }
546 if (!anyInFront) return;
547 if (scMaxX < 0.f || scMinX > float(vw) || scMaxY < 0.f ||
548 scMinY > float(vh))
549 return;
550 if (!entities.empty()) entities += ",";
551 entities += "{\"id\":\"" + jsonEscape(node.id.empty() ? node.name : node.id) +
552 "\",\"asset\":\"" +
553 jsonEscape(node.name.empty() ? node.id : node.name) +
554 "\",\"world_aabb\":{\"min\":" + vec3Json(glm::vec3(minX, minY, minZ)) +
555 ",\"max\":" + vec3Json(glm::vec3(maxX, maxY, maxZ)) +
556 "},\"screen_bbox\":{\"x\":" + num(scMinX) + ",\"y\":" + num(scMinY) +
557 ",\"w\":" + num(scMaxX - scMinX) + ",\"h\":" + num(scMaxY - scMinY) +
558 "}}";
559 ++count;
560 });
561 }
562 }
563 if (ok) *ok = true;
564 return "{\"camera\":" + vec3Json(camEye) + ",\"cameraTarget\":" + vec3Json(camTarget) +
565 ",\"fov\":" + num(camFov) + ",\"viewportWidth\":" + std::to_string(vw) +
566 ",\"viewportHeight\":" + std::to_string(vh) + ",\"entities\":[" + entities +
567 "],\"count\":" + std::to_string(count) + "}";
568 }
569};
570
571class ProcgenProbeSinkImpl final : public eve::IProcgenProbeSink {
572public:
573 struct Batch {
574 std::vector<std::unique_ptr<ReflectionProbeCapture>> reflections;
575 std::vector<eve::ProcgenProbeDesc> lights;
576 };
577
578 Result<int> replaceProbeBatch(const std::string& batchId,
579 const std::vector<eve::ProcgenProbeDesc>& probes) override {
580 Graphics* graphics = eve::ModuleManager::getInstance<Graphics>("Graphics");
581 if (batchId.empty() || !graphics)
583 "procedural probe publication requires a batch id and graphics provider"));
584 Batch candidate;
585 std::unordered_set<std::uint64_t> identities;
586 for (const auto& probe : probes) {
587 if (!probe.sourcePointId || !identities.insert(probe.sourcePointId).second ||
588 probe.resource.empty() || (probe.type != 0 && probe.type != 1) ||
589 !std::isfinite(probe.x) || !std::isfinite(probe.y) || !std::isfinite(probe.z) ||
590 !std::isfinite(probe.extentX) || !std::isfinite(probe.extentY) || !std::isfinite(probe.extentZ) ||
591 probe.extentX <= 0 || probe.extentY <= 0 || probe.extentZ <= 0 ||
592 probe.resolution < 16 || probe.resolution > 2048 ||
593 !std::isfinite(probe.clipDistance) || probe.clipDistance <= 0 ||
594 !std::isfinite(probe.irradianceR) || !std::isfinite(probe.irradianceG) ||
595 !std::isfinite(probe.irradianceB) || probe.irradianceR < 0 || probe.irradianceG < 0 ||
596 probe.irradianceB < 0)
598 "procedural probe publication requires valid unique probe descriptors"));
599 if (probe.hasSphericalHarmonics &&
600 !std::all_of(probe.sphericalHarmonics.begin(), probe.sphericalHarmonics.end(),
601 [](float coefficient) { return std::isfinite(coefficient); }))
603 "procedural light probe spherical harmonics must be finite"));
604 if (probe.type == 1) {
605 candidate.lights.push_back(probe);
606 continue;
607 }
608 auto capture = std::make_unique<ReflectionProbeCapture>(graphics);
609 capture->configure(probe.x, probe.y, probe.z, probe.resolution, 0.1F, probe.clipDistance);
610 capture->configureInfluence(probe.extentX, probe.extentY, probe.extentZ, 1, 1, 0);
611 capture->requestCapture();
612 candidate.reflections.push_back(std::move(capture));
613 }
614 ensureRegistry();
615 for (auto& probe : candidate.reflections) registry_->add(probe.get());
616 auto found = batches_.find(batchId);
617 if (found != batches_.end())
618 for (auto& probe : found->second.reflections) registry_->remove(probe.get());
619 DiffuseLightProbeRegistry::instance().replaceBatch(batchId, candidate.lights);
620 batches_.insert_or_assign(batchId, std::move(candidate));
621 return Result<int>::success(static_cast<int>(probes.size()));
622 }
623
624 Result<int> removeProbeBatch(const std::string& batchId) override {
625 auto found = batches_.find(batchId);
626 if (found == batches_.end())
628 "procedural probe batch was not published", batchId));
629 const int count = static_cast<int>(found->second.reflections.size() + found->second.lights.size());
630 if (registry_)
631 for (auto& probe : found->second.reflections) registry_->remove(probe.get());
633 batches_.erase(found);
635 }
636
637 Result<int> tickProbeBatches(int faceBudget, int filterBudget, int filterSamples) override {
638 if (faceBudget < 0 || filterBudget < 0 || filterSamples < 8 || filterSamples > 512)
640 "procedural probe tick requires nonnegative budgets and 8..512 filter samples"));
641 ensureRegistry();
642 return Result<int>::success(registry_->tick(faceBudget, filterBudget, filterSamples));
643 }
644 int probeCount(const std::string& batchId) const override {
645 auto found = batches_.find(batchId);
646 return found == batches_.end() ? 0 : static_cast<int>(found->second.reflections.size() + found->second.lights.size());
647 }
648 int reflectionProbeCount(const std::string& batchId) const override {
649 auto found = batches_.find(batchId);
650 return found == batches_.end() ? 0 : static_cast<int>(found->second.reflections.size());
651 }
652 int lightProbeCount(const std::string& batchId) const override {
653 auto found = batches_.find(batchId);
654 return found == batches_.end() ? 0 : static_cast<int>(found->second.lights.size());
655 }
656private:
657 void ensureRegistry() {
658 if (!registry_) registry_ = std::make_unique<ReflectionProbeRegistry>();
659 }
660 std::unique_ptr<ReflectionProbeRegistry> registry_;
661 std::unordered_map<std::string, Batch> batches_;
662};
663
664} // namespace
665
667 static RenderCaptureImpl impl;
668 static ProcgenProbeSinkImpl probeSink;
669 eve::cap::provide<eve::IRenderCapture>(&impl);
670 eve::cap::provide<eve::IProcgenProbeSink>(&probeSink);
671}
672
673} // namespace eve::graphics
LogicalId target
float w
Definition AnimClip.cpp:738
const std::string & s
void * impl
tensor::Graph g
Definition GpuGraph.cpp:7
std::vector< eve::ProcgenProbeDesc > lights
std::vector< std::unique_ptr< ReflectionProbeCapture > > reflections
float camera[2]
wgpu::PopErrorScopeStatus status
glm::vec4 clip
double r
float v
HexVec3 up
std::int32_t second
std::int32_t c
std::string name
MeshVfxBatchedDraw draw
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::vector< ecs::EntityHandle > entities
std::uint32_t generation
PrimitiveHandle handle
float d
Renderable3D::MeshRenderer * mr
glm::vec3 eye
Mesh * mesh
glm::mat4 viewProj
glm::mat4 view
glm::mat4 proj
const RoadNode * node
bool found
std::uint32_t count
V3 dir
Definition TreeMesh.cpp:150
UIHostHandle host
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Optional graphics consumer for complete generated probe batches. Implementations own all runtime reso...
Frame capture + camera + visible-entity inspection (graphics).
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Camera3D * createCamera()
void removeBatch(const std::string &batchId)
Remove every diffuse probe owned by one generated batch.
void replaceBatch(const std::string &batchId, const std::vector< eve::ProcgenProbeDesc > &probes)
Atomically replace the diffuse probes owned by one generated batch.
static DiffuseLightProbeRegistry & instance()
Return the process-wide graphics-owned registry.
static void updateAll()
Updates all.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
glm::mat4 cameraProjectionVulkanRH_ZO(bool orthographic, float fovyRad, float orthoHeight, float aspect, float zNear, float zFar)
Camera projection shared by rendering, picking, and diagnostic passes.
Definition ClipSpace.h:38
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:22
void registerGraphicsCapabilities()
Registers graphics capabilities.
int findLinkKind(const char *kind)
Finds link 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
bool enabled
Render surface / frame state snapshot (value type).
Generation-qualified snapshot of one live field-backed Renderable3D.
std::uint32_t entityId
glm::uvec4 info