载入中...
搜索中...
未找到
ArtifactProvider.cpp
浏览该文件的文档.
2
3#include "common/Capability.h"
4#include "graphics/Graphics.h"
5
6#include <algorithm>
7#include <charconv>
8#include <cmath>
9#include <limits>
10#include <memory>
11#include <type_traits>
12#include <unordered_set>
13#include <utility>
14
15namespace eve::graphics {
16namespace {
17
18const eve::Value* member(const eve::Value::Object& object, const char* name) noexcept {
19 const auto found = object.find(name);
20 return found == object.end() ? nullptr : &found->second;
21}
22
23bool readString(const eve::Value::Object& object, const char* name, std::string& output, bool allowEmpty = false) {
24 const auto* value = member(object, name);
25 const auto* text = value ? value->getIf<std::string>() : nullptr;
26 if (!text || (!allowEmpty && text->empty())) return false;
27 output = *text;
28 return true;
29}
30
31bool readU64(const eve::Value::Object& object, const char* name, std::uint64_t& output) {
32 std::string text;
33 if (!readString(object, name, text)) return false;
34 const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), output);
35 return error == std::errc{} && end == text.data() + text.size();
36}
37
38bool readBool(const eve::Value::Object& object, const char* name, bool& output) {
39 const auto* value = member(object, name);
40 const auto* boolean = value ? value->getIf<bool>() : nullptr;
41 if (!boolean) return false;
42 output = *boolean;
43 return true;
44}
45
46bool readOptionalBool(const eve::Value::Object& object, const char* name, bool& output) {
47 const auto* value = member(object, name);
48 if (!value) return true;
49 return readBool(object, name, output);
50}
51
52bool hasSupportedVersion(const eve::Value::Object& object) noexcept {
53 const auto* encoded = member(object, "version");
54 const auto* version = encoded ? encoded->getIf<std::int64_t>() : nullptr;
55 return version && (*version == 1 || *version == 2);
56}
57
58eve::Value floatArray(const std::vector<float>& values) {
59 eve::Value::Array encoded;
60 encoded.reserve(values.size());
61 for (const float value : values) encoded.emplace_back(value);
62 return eve::Value(std::move(encoded));
63}
64
65eve::Value uintArray(const std::vector<std::uint32_t>& values) {
66 eve::Value::Array encoded;
67 encoded.reserve(values.size());
68 for (const auto value : values) encoded.emplace_back(std::int64_t(value));
69 return eve::Value(std::move(encoded));
70}
71
72template <class T>
73bool readNumericArray(const eve::Value* encoded, std::vector<T>& output) {
74 const auto* array = encoded ? encoded->getIf<eve::Value::Array>() : nullptr;
75 if (!array) return false;
76 output.clear();
77 output.reserve(array->size());
78 for (const eve::Value& value : *array) {
79 if constexpr (std::is_same_v<T, float>) {
80 if (const auto* number = value.getIf<double>()) {
81 if (!std::isfinite(*number) || *number < -std::numeric_limits<float>::max() ||
82 *number > std::numeric_limits<float>::max())
83 return false;
84 output.push_back(static_cast<float>(*number));
85 } else if (const auto* integer = value.getIf<std::int64_t>()) {
86 const float converted = static_cast<float>(*integer);
87 if (!std::isfinite(converted)) return false;
88 output.push_back(converted);
89 } else {
90 return false;
91 }
92 } else {
93 const auto* integer = value.getIf<std::int64_t>();
94 if (!integer || *integer < 0 || static_cast<std::uint64_t>(*integer) > std::numeric_limits<T>::max())
95 return false;
96 output.push_back(static_cast<T>(*integer));
97 }
98 }
99 return true;
100}
101
102std::uint64_t hashStreams(std::span<const float> positions, std::span<const float> normals, std::span<const float> uvs,
103 std::span<const std::uint32_t> indices) noexcept {
104 std::uint64_t hash = 14695981039346656037ull;
105 const auto mix = [&hash](const auto* data, std::size_t count) {
106 const auto* bytes = reinterpret_cast<const std::uint8_t*>(data);
107 for (std::size_t i = 0; i < count * sizeof(*data); ++i) {
108 hash ^= bytes[i];
109 hash *= 1099511628211ull;
110 }
111 };
112 mix(positions.data(), positions.size());
113 mix(normals.data(), normals.size());
114 mix(uvs.data(), uvs.size());
115 mix(indices.data(), indices.size());
116 return hash;
117}
118
119std::uint64_t hashDescriptor(const GraphicsArtifactResource& resource) noexcept {
120 return hashStreams(resource.positions, resource.normals, resource.uvs, resource.indices);
121}
122
123const eve::artifact::PartView* selectedMesh(const eve::artifact::PublicationView& publication) noexcept {
124 const eve::artifact::PartView* mesh = nullptr;
125 for (const auto& part : publication.parts) {
126 if (part.kind == eve::artifact::PartKind::MeshData && (part.role == "mesh" || mesh == nullptr)) mesh = &part;
127 }
128 return mesh;
129}
130
131bool validMeshView(const eve::artifact::PartView& mesh) noexcept {
132 if (mesh.positions.empty() || mesh.positions.size() % 3u != 0u || mesh.indices.empty() ||
133 mesh.indices.size() % 3u != 0u)
134 return false;
135 const std::size_t vertices = mesh.positions.size() / 3u;
136 if ((!mesh.normals.empty() && mesh.normals.size() != mesh.positions.size()) ||
137 (!mesh.uvs.empty() && mesh.uvs.size() != vertices * 2u))
138 return false;
139 for (const float value : mesh.positions)
140 if (!std::isfinite(value)) return false;
141 for (const float value : mesh.normals)
142 if (!std::isfinite(value)) return false;
143 for (const float value : mesh.uvs)
144 if (!std::isfinite(value)) return false;
145 for (const auto index : mesh.indices)
146 if (index >= vertices) return false;
147 return true;
148}
149
150} // namespace
151
152void GraphicsArtifactProvider::uploadIfAvailable(Graphics* graphics, RuntimeResource& runtime) noexcept {
153 auto& descriptor = runtime.descriptor;
154 runtime.uploadedMesh = nullptr;
155 if (!graphics) {
156 descriptor.backendName = "cpu";
157 descriptor.uploadState = "not-attempted";
158 descriptor.gpuResident = false;
159 return;
160 }
161 descriptor.gpuResident = false;
162 descriptor.uploadState = "cpu-fallback";
163 try {
164 descriptor.backendName = graphics->getBackendName();
165 runtime.uploadedMesh = graphics->newMeshFromArrays(
166 descriptor.positions.data(), descriptor.normals.empty() ? nullptr : descriptor.normals.data(),
167 descriptor.uvs.empty() ? nullptr : descriptor.uvs.data(),
168 static_cast<int>(descriptor.positions.size() / 3u), descriptor.indices.data(),
169 static_cast<int>(descriptor.indices.size()));
170 if (runtime.uploadedMesh) {
171 const auto backend = graphics->describeMesh(runtime.uploadedMesh);
172 if (!backend || backend->vertexCount != descriptor.positions.size() / 3u ||
173 backend->indexCount != descriptor.indices.size() || backend->vertexStride == 0u ||
174 (backend->indexElementSize != 2u && backend->indexElementSize != 4u)) {
175 (void)graphics->releaseMesh(runtime.uploadedMesh);
176 runtime.uploadedMesh = nullptr;
177 descriptor.uploadState = "cpu-fallback";
178 descriptor.gpuResident = false;
179 return;
180 }
181 descriptor.backendVertexStride = backend->vertexStride;
182 descriptor.backendIndexElementSize = backend->indexElementSize;
183 descriptor.uploadState = "uploaded";
184 descriptor.gpuResident = true;
185 }
186 } catch (...) {
187 if (runtime.uploadedMesh) {
188 try {
189 (void)graphics->releaseMesh(runtime.uploadedMesh);
190 } catch (...) {
191 }
192 }
193 runtime.uploadedMesh = nullptr;
194 descriptor.uploadState = "cpu-fallback";
195 }
196}
197
199public:
201 : owner_(&owner), resource_{std::move(resource), uploadedMesh} {}
203 void commit() noexcept override {
204 if (!owner_) return;
205 owner_->commit(std::move(resource_));
206 owner_ = nullptr;
207 }
208 void rollback() noexcept override {
209 if (owner_) owner_->release(resource_);
210 owner_ = nullptr;
211 resource_ = {};
212 }
213
214private:
215 GraphicsArtifactProvider* owner_ = nullptr;
216 GraphicsArtifactProvider::RuntimeResource resource_;
217};
218
219std::optional<GraphicsArtifactDescriptor> WebGpuArtifactDescriptorAdapter::describe(
220 const eve::artifact::PublicationView& publication) {
221 const auto* mesh = selectedMesh(publication);
222 if (!mesh || !validMeshView(*mesh)) return std::nullopt;
224 result.id = mesh->id;
225 result.buildKey = std::string(mesh->buildKey);
226 result.role = std::string(mesh->role);
227 result.vertexCount = mesh->positions.size() / 3u;
228 result.indexCount = mesh->indices.size();
229 result.checksum = hashStreams(mesh->positions, mesh->normals, mesh->uvs, mesh->indices);
230 return result;
231}
232
234 const eve::artifact::PublicationView& publication) {
235 if (failPrepare_)
237 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "graphics artifact prepare failure was injected"));
238 if (publication.id.isNil() || publication.buildKey.empty())
240 eve::DiagnosticCode::InvalidArgument, "graphics publication requires identity and build key"));
241 if (find(publication.id) != nullptr)
243 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "graphics artifact identity is already published"));
244 const auto* mesh = selectedMesh(publication);
245 if (!mesh || !validMeshView(*mesh))
247 eve::DiagnosticCode::PreconditionViolation, "graphics publication requires a complete mesh leaf"));
248 if (mesh->positions.size() / 3u > static_cast<std::size_t>(std::numeric_limits<int>::max()) ||
249 mesh->indices.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
251 eve::DiagnosticCode::InvalidArgument, "graphics mesh exceeds backend integer limits"));
252 if (nextIndex_ == GraphicsArtifactHandle::invalidIndex)
254 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "graphics artifact handle index exhausted"));
255
257 resource.id = publication.id;
258 resource.buildKey = std::string(publication.buildKey);
259 resource.handle = GraphicsArtifactHandle(nextIndex_, 1);
260 resource.role = std::string(mesh->role);
261 RuntimeResource runtime;
262 runtime.descriptor = std::move(resource);
263 try {
264 runtime.descriptor.positions.assign(mesh->positions.begin(), mesh->positions.end());
265 runtime.descriptor.normals.assign(mesh->normals.begin(), mesh->normals.end());
266 runtime.descriptor.uvs.assign(mesh->uvs.begin(), mesh->uvs.end());
267 runtime.descriptor.indices.assign(mesh->indices.begin(), mesh->indices.end());
268 uploadIfAvailable(graphics_, runtime);
269 resources_.reserve(resources_.size() + 1);
271 std::make_unique<GraphicsArtifactStage>(*this, std::move(runtime.descriptor), runtime.uploadedMesh));
272 } catch (...) {
273 if (runtime.uploadedMesh && graphics_) {
274 try {
275 (void)graphics_->releaseMesh(runtime.uploadedMesh);
276 } catch (...) {
277 }
278 }
280 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "graphics artifact prepare allocation failed"));
281 }
282}
283
284void GraphicsArtifactProvider::commit(RuntimeResource resource) noexcept {
285 resources_.push_back(std::move(resource));
286 ++nextIndex_;
287}
288
289void GraphicsArtifactProvider::release(RuntimeResource& resource) noexcept {
290 if (resource.uploadedMesh && graphics_) {
291 try {
292 (void)graphics_->releaseMesh(resource.uploadedMesh);
293 } catch (...) {
294 }
295 }
296 if (resource.uploadedMesh) {
297 resource.descriptor.gpuResident = false;
298 resource.descriptor.uploadState = "detached";
299 resource.descriptor.backendVertexStride = 0;
300 resource.descriptor.backendIndexElementSize = 0;
301 resource.uploadedMesh = nullptr;
302 }
303}
304
306 const auto found = std::find_if(resources_.begin(), resources_.end(),
307 [id](const auto& resource) { return resource.descriptor.id == id; });
308 return found == resources_.end() ? nullptr : &found->descriptor;
309}
310
312 const auto* resource = find(id);
313 return resource ? hashDescriptor(*resource) : 0;
314}
315
316std::optional<GraphicsArtifactDescriptor> GraphicsArtifactProvider::descriptor(eve::PersistentId id) const {
317 const auto* resource = find(id);
318 if (!resource) return std::nullopt;
320 resource->buildKey,
321 resource->role,
322 resource->positions.size() / 3u,
323 resource->indices.size(),
324 hashDescriptor(*resource),
325 resource->backendVertexStride,
326 resource->backendIndexElementSize};
327}
328
330 const auto* resource = find(id);
331 return resource && resource->gpuResident;
332}
333
335 for (auto& resource : resources_) release(resource);
336 resources_.clear();
337 nextIndex_ = 0;
338 failPrepare_ = false;
339}
340
342 if (graphics_ == graphics) return;
343 if (graphics_) {
344 for (auto& resource : resources_) release(resource);
345 }
346 graphics_ = graphics;
347}
348
350 if (graphics_ != graphics) return;
351 for (auto& resource : resources_) release(resource);
352 graphics_ = nullptr;
353}
354
356 try {
358 resources.reserve(resources_.size());
359 for (const auto& runtime : resources_) {
360 const auto& resource = runtime.descriptor;
362 item.emplace("artifactId", eve::Value(resource.id.format()));
363 item.emplace("buildKey", eve::Value(resource.buildKey));
364 item.emplace("handle", eve::Value(std::to_string(resource.handle.packed())));
365 item.emplace("role", eve::Value(resource.role));
366 item.emplace("positions", floatArray(resource.positions));
367 item.emplace("normals", floatArray(resource.normals));
368 item.emplace("uvs", floatArray(resource.uvs));
369 item.emplace("indices", uintArray(resource.indices));
370 item.emplace("backend", eve::Value(resource.backendName));
371 item.emplace("uploadState", eve::Value(resource.uploadState));
372 item.emplace("gpuResident", eve::Value(resource.gpuResident));
373 resources.emplace_back(std::move(item));
374 }
376 state.emplace("provider", eve::Value("graphics.resource-provider"));
377 state.emplace("version", eve::Value(std::int64_t(2)));
378 state.emplace("unknownFields", eve::Value("ignore"));
379 state.emplace("resources", eve::Value(std::move(resources)));
381 } catch (...) {
383 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "graphics artifact snapshot allocation failed"));
384 }
385}
386
388 if (!resources_.empty())
390 eve::DiagnosticCode::Conflict, "graphics artifact restore requires an empty registry"));
391 const auto* object = state.getIf<eve::Value::Object>();
392 const auto* provider = object ? member(*object, "provider") : nullptr;
393 const auto* providerName = provider ? provider->getIf<std::string>() : nullptr;
394 const auto* versionValue = object ? member(*object, "version") : nullptr;
395 const auto* version = versionValue ? versionValue->getIf<std::int64_t>() : nullptr;
396 const auto* encodedResources = object ? member(*object, "resources") : nullptr;
397 const auto* resources = encodedResources ? encodedResources->getIf<eve::Value::Array>() : nullptr;
398 if (!object || !providerName || *providerName != "graphics.resource-provider" || !hasSupportedVersion(*object) ||
399 !version || !resources)
401 eve::Diagnostic::error(eve::DiagnosticCode::UnknownVersion, "invalid graphics artifact provider state"));
402
403 std::vector<RuntimeResource> candidate;
404 std::unordered_set<eve::PersistentId> identities;
405 std::uint32_t next = 0;
406 const auto cleanup = [&]() noexcept {
407 for (auto& resource : candidate) release(resource);
408 candidate.clear();
409 };
410 const auto fail = [&](eve::Diagnostic diagnostic) -> eve::Result<void> {
411 cleanup();
412 return eve::Result<void>::failure(std::move(diagnostic));
413 };
414 try {
415 candidate.reserve(resources->size());
416 for (const eve::Value& encoded : *resources) {
417 const auto* item = encoded.getIf<eve::Value::Object>();
418 std::string idText;
419 std::string buildKey;
420 std::string role;
421 std::string backend;
422 std::string uploadState;
423 std::uint64_t packed = 0;
424 bool gpuResident = false;
425 RuntimeResource runtime;
426 auto& resource = runtime.descriptor;
427 if (!item || !readString(*item, "artifactId", idText) || !readString(*item, "buildKey", buildKey) ||
428 !readString(*item, "role", role) || !readU64(*item, "handle", packed) ||
429 !readNumericArray(member(*item, "positions"), resource.positions) ||
430 !readNumericArray(member(*item, "normals"), resource.normals) ||
431 !readNumericArray(member(*item, "uvs"), resource.uvs) ||
432 !readNumericArray(member(*item, "indices"), resource.indices))
433 return fail(
434 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "invalid graphics artifact resource"));
435 const auto parsedId = eve::PersistentId::parse(idText);
437 if (!parsedId || parsedId->isNil() || resource.handle.isInvalid() || role.empty() ||
438 !identities.emplace(*parsedId).second || resource.positions.empty() ||
439 resource.positions.size() % 3u != 0u || resource.indices.empty() ||
440 resource.indices.size() % 3u != 0u ||
441 (!resource.normals.empty() && resource.normals.size() != resource.positions.size()) ||
442 (!resource.uvs.empty() && resource.uvs.size() != resource.positions.size() / 3u * 2u))
444 "invalid graphics artifact streams or identity"));
445 for (const auto index : resource.indices)
446 if (index >= resource.positions.size() / 3u)
447 return fail(
448 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "invalid graphics artifact index"));
449 if (*version >= 2) {
450 if (!readString(*item, "backend", backend) || !readString(*item, "uploadState", uploadState) ||
451 !readBool(*item, "gpuResident", gpuResident))
452 return fail(
453 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "invalid graphics upload state"));
454 }
455 resource.id = *parsedId;
456 resource.buildKey = std::move(buildKey);
457 resource.role = std::move(role);
458 if (*version >= 2) {
459 resource.backendName = std::move(backend);
460 resource.uploadState = std::move(uploadState);
461 resource.gpuResident = gpuResident;
462 }
463 uploadIfAvailable(graphics_, runtime);
464 next = std::max(next, resource.handle.index() + 1u);
465 candidate.push_back(std::move(runtime));
466 }
467 } catch (...) {
468 cleanup();
470 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "graphics artifact restore allocation failed"));
471 }
472 resources_.swap(candidate);
473 nextIndex_ = next;
475}
476
478 static GraphicsArtifactProvider provider;
479 return provider;
480}
481
484 eve::cap::provide<eve::artifact::IGraphicsArtifactAdapter>(&graphicsArtifactProvider());
485}
486
490
491} // namespace eve::graphics
double value
std::vector< eve::artifact::PartView > parts
std::string output
std::string descriptor
std::map< std::string, Var > values
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
float u
Definition Grass.cpp:233
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
std::string text
std::uint64_t bytes
std::string name
std::vector< std::weak_ptr< DeviceBytes > > resources
Definition OnnxGpgpu.cpp:55
std::string error
Definition Package.cpp:60
std::vector< Point > vertices
std::string id
Definition PlayHost.cpp:108
std::function< eve::Result< void >()> release
Definition Procgen.cpp:86
Mesh * mesh
double number
bool found
std::string resource
bool boolean
std::uint32_t count
float size
Definition TreeMesh.cpp:156
float(ui::Theme::* member)[4]
uint32_t index
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
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
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 constexpr RuntimeHandle fromPacked(std::uint64_t value) noexcept
Reconstructs a handle from an explicit packed boundary value.
static constexpr index_type invalidIndex
Reserved index value shared by all invalid handles.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
A prepared consumer mutation with a non-throwing visibility boundary.
Id128 public API.
Definition Identity.h:112
constexpr bool isNil() const noexcept
Returns whether this value is the all-zero nil ID.
Definition Identity.h:176
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
Real graphics-module provider for generated mesh publication.
eve::Result< void > restoreState(const eve::Value &state) override
Replace resources from validated backend-neutral CPU descriptors.
eve::Result< eve::Value > snapshotState() const override
Export resource identity, build keys, handles and CPU descriptors.
void bindGraphics(Graphics *graphics) noexcept
Bind a live Graphics resource factory without transferring ownership.
const GraphicsArtifactResource * find(eve::PersistentId id) const noexcept
Find a committed resource; the pointer is borrowed. @ownership Borrowed from this provider; callers m...
void clear() noexcept
Remove all resources and reset the local handle allocator.
std::optional< GraphicsArtifactDescriptor > descriptor(eve::PersistentId id) const
Return the backend-neutral descriptor for one resource.
eve::Result< std::unique_ptr< eve::artifact::PreparedPublication > > prepare(const eve::artifact::PublicationView &publication) override
Stage and copy the mesh leaf from a generated publication.
bool isGpuResident(eve::PersistentId id) const noexcept
Return whether a resource was backed by the live Graphics upload boundary.
std::uint64_t checksum(eve::PersistentId id) const noexcept
Return a deterministic checksum of one resource's CPU descriptor.
void detachGraphics(Graphics *graphics) noexcept
Detach the expected backend and release its provider-owned meshes.
GraphicsArtifactStage(GraphicsArtifactProvider &owner, GraphicsArtifactResource resource, Mesh *uploadedMesh)
void commit() noexcept override
Make the prepared mutation observable; the operation cannot fail.
void rollback() noexcept override
Discard the prepared mutation; idempotent and non-throwing.
virtual bool releaseMesh(Mesh *mesh)
Eagerly releases a mesh created by this Graphics.
Definition Graphics.h:927
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
static std::optional< GraphicsArtifactDescriptor > describe(const eve::artifact::PublicationView &publication)
Describe the selected mesh part, or return empty for invalid input.
Graphics-owned CPU resource provider for generated mesh artifacts.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
void detachGraphicsArtifactProvider(Graphics *graphics) noexcept
Detach a Graphics instance before its derived backend is destroyed.
eve::RuntimeHandle< GraphicsArtifactHandleTag > GraphicsArtifactHandle
Generation-qualified handle for a graphics artifact resource.
void registerGraphicsArtifactProvider(Graphics *graphics)
Register the graphics provider in the common capability registry.
GraphicsArtifactProvider & graphicsArtifactProvider() noexcept
Return the process-owned graphics artifact provider singleton.
bool readU64(const eve::Value::Object &object, const char *name, std::uint64_t &output)
bool readString(const eve::Value::Object &object, const char *name, std::string &output)
bool hasSupportedVersion(const eve::Value::Object &object) noexcept
bool readOptionalBool(const EditorValue &value, const char *name, bool &output)
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
Borrowed immutable view of one generated leaf.
Borrowed immutable view of one complete generated publication.
Backend-neutral mesh descriptor shared by Vulkan and WebGPU adapters.
Queryable backend-neutral mesh resource owned by the graphics module.