Loading...
Searching...
No Matches
GpuResourceRegistry.hpp
1#pragma once
2
3#include <score_plugin_gfx_export.h>
4
5#include <ossia/dataflow/geometry_port.hpp> // ossia::gpu_slot_ref
6#include <ossia/detail/flat_map.hpp>
7#include <ossia/detail/hash_map.hpp>
8
9#ifndef OFFSETALLOCATOR_HPP_2026_04_24
10#define OFFSETALLOCATOR_HPP_2026_04_24
11#include <offsetAllocator.hpp>
12#endif
13
14#include <QtGui/private/qrhi_p.h>
15
16#include <array>
17#include <cstddef>
18#include <cstdint>
19#include <memory>
20#include <vector>
21
22namespace score::gfx
23{
24class RenderList;
25
44class SCORE_PLUGIN_GFX_EXPORT GpuResourceRegistry
45{
46public:
47 // Well-known arenas. Size tables live in GpuResourceRegistry.cpp and match the
48 // packed GPU layouts in SceneGPUState.hpp and CameraMath.hpp. Every entry
49 // implies a QRhiBuffer allocation at init time.
50 //
51 // The Raw* arenas are written by source halp nodes at their own operator()()
52 // time: view-independent, aspect-ratio-agnostic, pre-composition. The cooked
53 // arenas are populated by ScenePreprocessor's transform passes, which combine
54 // Raw inputs with the render target's aspect ratio and the scene-graph parent
55 // chain, and are what consumer shaders bind. Material and Env have no
56 // composition dependency, so source nodes write their cooked slot directly.
57 enum class Arena : uint8_t
58 {
59 // ── Shared / source-authored ──────────────────────────────────
60 // These arenas hold view- and filter-independent bytes: every
61 // preprocessor reads the same data regardless of its camera /
62 // render target / upstream scene filtering. The producer owns the
63 // slot; multiple preprocessors consume via gpu_slot_ref + isLive().
64 RawCamera, // RawCameraData — 64 B per slot, UBO
65 RawLight, // RawLightData — 64 B per slot, SSBO
66 RawTransform, // RawLocalTransform — 64 B per slot, SSBO
67 Material, // MaterialGPU — 80 B per slot, SSBO
68 Env, // EnvParamsUBO — 64 B per slot, UBO
69
70 // Cooked outputs (camera UBOs, composed world matrices, per-draw structs,
71 // LightGPU with world direction, MaterialGPU with resolved textureRefs) are
72 // preprocessor-private: they are view- and filter-dependent, so a shared arena
73 // would be wrong when two preprocessors see different filtered views.
74
75 Count_
76 };
77
78 // Fixed-stride slot: the arena buffer is a packed array of stride-byte slots,
79 // slot i at byte offset i * stride. The slot index is the arena-level identity
80 // consumer shaders use, e.g. scene_materials.entries[slot_index]. Allocation is
81 // O(1) through a free-list stack. Trades OffsetAllocator's variable-size
82 // flexibility for a shader-indexable layout and a 1:1 mapping between
83 // internal_index and byte offset, so arena reads need no translation table.
84 struct Slot
85 {
86 static constexpr uint32_t kInvalidIndex = 0xFFFFFFFFu;
87
88 Arena arena{Arena::RawCamera};
89 uint32_t slot_index{kInvalidIndex};
90 uint32_t size{0}; // requested payload size (≤ arena stride)
91 uint32_t generation{}; // stamped on allocate; bumps on free
92
93 bool valid() const noexcept { return slot_index != kInvalidIndex; }
94 };
95
96 GpuResourceRegistry() = default;
98 GpuResourceRegistry& operator=(const GpuResourceRegistry&) = delete;
100
115 void init(QRhi& rhi, QRhiResourceUpdateBatch& batch);
116
122 bool isInitialized() const noexcept { return m_rhi != nullptr; }
123
130 QRhi* boundRhi() const noexcept { return m_rhi; }
131
145 void seedDefaults(QRhiResourceUpdateBatch& batch);
146
154 void uploadDefaultMaterial(QRhiResourceUpdateBatch& batch);
155
165 void destroy(RenderList& renderer);
166
174 void destroy();
175
189 void destroyOwned();
190
195 Slot allocate(Arena arena, uint32_t size);
196
200 void free(Slot& slot);
201
208 QRhiBuffer* buffer(Arena arena) const noexcept;
209
213 uint32_t slotOffset(const Slot& slot) const noexcept;
214
220 uint32_t arenaSlotStride(Arena arena) const noexcept;
221
225 uint32_t arenaSlotCount(Arena arena) const noexcept;
226
235 void updateSlot(
236 QRhiResourceUpdateBatch& res, const Slot& slot, const void* data,
237 uint32_t size) noexcept;
238
247 ossia::gpu_slot_ref toOssiaRef(const Slot& slot) const noexcept
248 {
249 if(!slot.valid())
250 return {};
251 ossia::gpu_slot_ref r;
252 r.arena = (uint32_t)slot.arena;
253 r.offset = slotOffset(slot);
254 r.size = slot.size;
255 r.internal_index = slot.slot_index;
256 r.generation = slot.generation;
257 return r;
258 }
259
274 bool isLiveIn(const ossia::gpu_slot_ref& r, Arena expected) const noexcept
275 {
276 return r.arena == (uint32_t)expected && isLive(r);
277 }
278
286 bool isLive(const ossia::gpu_slot_ref& r) const noexcept
287 {
288 if(r.arena >= (uint32_t)Arena::Count_ || r.size == 0)
289 return false;
290 const auto& a = m_arenas[r.arena];
291 if(r.internal_index >= a.slot_generations.size())
292 return false;
293 return a.slot_generations[r.internal_index] == r.generation;
294 }
295
296 // ─── Material texture arrays ──────────────────────────────────────
297 //
298 // Per-channel static texture arrays shared across every preprocessor in this
299 // RenderList. Static textures dedup by texture_source pointer, so every producer
300 // referencing an asset gets the same layer; dynamic handles (video textures,
301 // runtime GPU outputs) get per-slot bindings in `dynamicTextures`, named
302 // <channel>Dyn<slot> in consumer shaders.
303 //
304 // Sharing is safe because a texture belongs to an asset or a wired GPU handle,
305 // independent of which preprocessor is looking.
306
307 enum class TextureChannel : uint8_t
308 {
309 BaseColor = 0,
310 MetalRough = 1,
311 Normal = 2,
312 Emissive = 3,
313 Occlusion = 4, // Separate glTF occlusionTexture (when distinct from MR).
314 Count_ = 5
315 };
316
317 // Default layer size + max dynamic slots. Matched across channels so
318 // samplers are interchangeable and consumer shaders can declare a
319 // fixed sampler count.
320 static constexpr int kTextureLayerSize = 1024;
321 // One pool shared by every channel: a handle routed to two channels
322 // resolves to one slot.
323 static constexpr int kMaxDynamicSlots = 8;
324
325 // Static buckets in ONE pool shared by every channel, each holding textures
326 // of one (format, pixelSize, colourspace) tuple. baseColor and emissive are
327 // sRGB, metal-rough, normal and occlusion linear, so a model's channels land
328 // in a handful of shared buckets. Past the cap a texture is skipped and the
329 // shader reads tex_ref_none.
330 //
331 // The tex_ref_static encoding reserves a 7-bit bucket field, so the cap can
332 // grow to 128 without changing the packed layout or the shader decode masks,
333 // but the shader's ladder must be enlarged to match and the sampler budget
334 // re-checked.
335 static constexpr int kMaxBuckets = 8;
336
351 {
352 struct Bucket
353 {
354 QRhiTexture* array{}; // QRhiTexture::TextureArray + `flags`
355 QRhiTexture::Format format{QRhiTexture::RGBA8};
356 QSize pixelSize; // all layers in a bucket share this size
357 int layers{}; // current layer count
358
359 // Colourspace, part of the bucket key since one pool serves every
360 // channel. sRGB textures (base colour, emissive) get hardware
361 // sRGB->linear on sample and linear data textures (metal-rough, normal,
362 // occlusion) must not, so they cannot share an array -- but two channels
363 // that agree on it can, which is where the saving comes from. The shader
364 // never has to know: it samples whichever bucket the ref names.
365 QRhiTexture::Flags flags{};
366
367 // The same repeat, trilinear sampler for every bucket; per-texture wrap
368 // modes are applied by the shader from scene_material_wrap.
369 QRhiSampler* sampler{}; // created on first allocation; owned
370
371 // Dedup: texture_source pointer → layer index in this bucket's
372 // `array`. Rebuilt on every materials change, but a source that is
373 // still present keeps its layer, so only new sources are uploaded.
374 ossia::flat_map<const ossia::texture_source*, int> layerMap;
375 // The source occupying each layer, null for a free one. Holding it
376 // keeps its address from being reused by another source while the map
377 // still names it.
378 std::vector<std::shared_ptr<const ossia::texture_source>> layerSources;
379 // Per layer: its alpha is coverage (the base colour of a MASK or BLEND
380 // material), so it was uploaded colour-bled. Kept so a re-upload after
381 // reallocation bleeds it again, and so a source whose role changes is
382 // uploaded again.
383 std::vector<char> layerCoverage;
384 // Some layer's level 0 changed since the chain was last generated.
385 bool mipsDirty{false};
386 };
387
388 // One bucket per distinct (format, pixelSize, colourspace), up to
389 // kMaxBuckets.
390 std::vector<Bucket> buckets;
391
392 // Bumped whenever a bucket's array or sampler is replaced. Every
393 // preprocessor sharing the pool republishes its texture auxiliaries on a
394 // change, not only the one whose rebuild caused it: the others' consumers
395 // still hold the replaced QRhiTexture*.
396 uint64_t generation{0};
397
398 // Monotonic counts of pool rebuilds and of layer uploads into bucket
399 // arrays, re-uploads after a reallocation included. Nothing in the engine
400 // reads them: they make the pool's incremental behaviour observable,
401 // since an upload into an existing array changes no pointer.
402 uint64_t rebuilds{0};
403 uint64_t layerUploads{0};
404
405 // The static sources each preprocessor's scene references, keyed by that
406 // preprocessor. A rebuild keeps the layers the other preprocessors claim
407 // instead of freeing every source its own scene lacks.
408 ossia::flat_map<const void*, std::vector<const ossia::texture_source*>> claims;
409
410 // Dynamic (runtime-GPU) slot map, keyed by QRhiResource::globalResourceId()
411 // rather than the raw QRhiTexture* -- the allocator recycles freed pointer
412 // values, and qrhivulkan.cpp documents the same hazard for SRB tracking, so a
413 // recycled address must look like a fresh resource here too.
414 //
415 // Slots are recycled by LRU: when the map is full and a new id arrives, the slot
416 // with the smallest dynamicSlotLastUse is evicted. lastUse is bumped on every
417 // access, so the evicted slot is the one no live material references.
418 ossia::flat_map<quint64, int> dynamicSlotMap;
419 std::vector<QRhiTexture*> dynamicTextures; // slot idx → texture
420 // slot idx → globalResourceId of dynamicTextures[idx], read while it was
421 // live: changes are detected without touching a possibly freed texture.
422 std::vector<quint64> dynamicTextureIds;
423 std::vector<uint64_t> dynamicSlotLastUse; // slot idx → access counter at last lookup
424 uint64_t dynamicSlotCounter{0}; // monotonic, bumped on each resolve
425 // Value of dynamicSlotCounter at the previous sweepStaleDynamicTextureSlots()
426 // pass. A slot whose dynamicSlotLastUse is <= this was not re-resolved by any
427 // live material since, so its stored QRhiTexture* is orphaned and must be
428 // cleared before it can be bound.
429 uint64_t dynamicSweepCheckpoint{0};
430
431 // Single-bucket accessors for callers that do not loop over buckets[].
432 // Return null / 0 when no bucket has been allocated yet.
433 QRhiTexture* primaryArray() const noexcept
434 {
435 return buckets.empty() ? nullptr : buckets[0].array;
436 }
437 int primaryLayers() const noexcept
438 {
439 return buckets.empty() ? 0 : buckets[0].layers;
440 }
441
442 // Access or lazily create bucket 0 with an owned (format, size).
443 // Kept for init-time fallback allocation only — production code
444 // goes through findOrCreateBucket() which selects the right bucket
445 // for the texture's actual (format, size).
446 Bucket& ensurePrimary(QRhiTexture::Format fmt, QSize sz,
447 QRhiTexture::Flags flags = {})
448 {
449 if(buckets.empty())
450 buckets.emplace_back();
451 auto& b = buckets[0];
452 b.format = fmt;
453 b.pixelSize = sz;
454 b.flags = flags;
455 return b;
456 }
457
458 // Find a bucket matching (fmt, sz, flags), creating one if none matches and
459 // kMaxBuckets is not reached. Returns {bucket_index, pointer}, or
460 // {-1, nullptr} on overflow. Bucket identity is the exact tuple, no
461 // rounding.
462 std::pair<int, Bucket*>
463 findOrCreateBucket(QRhiTexture::Format fmt, QSize sz, QRhiTexture::Flags flags)
464 {
465 for(std::size_t i = 0; i < buckets.size(); ++i)
466 {
467 if(buckets[i].format == fmt && buckets[i].pixelSize == sz
468 && buckets[i].flags == flags)
469 return {(int)i, &buckets[i]};
470 }
471 if((int)buckets.size() >= kMaxBuckets)
472 return {-1, nullptr};
473 buckets.emplace_back();
474 auto& b = buckets.back();
475 b.format = fmt;
476 b.pixelSize = sz;
477 b.flags = flags;
478 return {(int)buckets.size() - 1, &b};
479 }
480 };
481
494 {
495 uint64_t rows{0};
496 uint64_t meta{0};
497 } primitiveCloudUploads;
498
502 TextureChannelState& textureChannel(TextureChannel) noexcept
503 {
504 return m_texturePool;
505 }
506 const TextureChannelState& textureChannel(TextureChannel) const noexcept
507 {
508 return m_texturePool;
509 }
510
512 TextureChannelState& texturePool() noexcept { return m_texturePool; }
513 const TextureChannelState& texturePool() const noexcept { return m_texturePool; }
514
518 static constexpr const char* kSharedArrayName = "materialArray";
519 static constexpr const char* kSharedDynName = "materialDyn";
520
529 static const char* textureChannelArrayName(TextureChannel ch) noexcept;
530
537 static const char* textureChannelDynBaseName(TextureChannel ch) noexcept;
538
544 static QRhiTexture::Flags textureChannelFlags(TextureChannel ch) noexcept;
545
558 int resolveDynamicSlot(TextureChannel channel, void* native_handle) noexcept;
559
560 // ─── Mesh arena manager ───────────────────────────────────────────
561 //
562 // Per-mesh slab allocator over the attribute streams of the MDI concatenated
563 // geometry -- positions, normals, texcoords, tangents, colors, texcoords1,
564 // indices -- each a single growth-capped QRhiBuffer.
565 //
566 // Indirect-draw correctness invariant: one baseVertex is applied to ALL vertex
567 // bindings (VkDrawIndexedIndirectCommand::vertexOffset), so per-mesh byte
568 // offsets must satisfy pos/nrm/tan = baseVertex * 16 and uv = baseVertex * 8.
569 // Independent per-stream allocators cannot guarantee that once alloc/free
570 // traffic fragments the streams: they pick free blocks from different size bins
571 // and the offsets diverge, so the vertex shader reads attributes from the wrong
572 // slab.
573 //
574 // Hence TWO shared allocators: m_vertexAllocator in vertex units and
575 // m_indexAllocator in index units, both capped at 8M slots. Each slab carries
576 // one vertex_slot and one index_slot, and per-stream byte offsets derive as
577 // vertex_slot.offset * stride and index_slot.offset * 4, so lockstep is
578 // structural. Indirect draw takes baseVertex = vertex_slot.offset and
579 // firstIndex = index_slot.offset.
580 //
581 // Cache: a stable_id hit reuses the slab and skips the upload; a miss allocates.
582 // The sweep frees slabs unseen for `grace` frames.
583 //
584 // Backing buffers are pointer-stable for the registry's lifetime, so downstream
585 // bindings resolve once: 128 MB each for positions, normals, tangents and
586 // colors, 64 MB per texcoord stream, 32 MB indices.
587
588 enum class MeshStream : uint8_t
589 {
590 Positions = 0,
591 Normals = 1,
592 Texcoords = 2, // TEXCOORD_0 (primary UV).
593 Tangents = 3,
594 Colors = 4, // glTF COLOR_0, vec4 (vec3 sources padded with alpha=1).
595 Texcoords1 = 5, // glTF TEXCOORD_1 (lightmap / secondary UV).
596 Indices = 6,
597 Count_ = 7
598 };
599
600 // Bytes per element per stream, matching the MDI output layout the rasterizer
601 // presets consume: positions and normals are vec3 padded to vec4 for std430,
602 // tangents and colors vec4 (vec3 sources padded with alpha=1), texcoords vec2,
603 // indices uint32.
604 static constexpr uint32_t kMeshStride[(std::size_t)MeshStream::Count_]
605 = {16, 16, 8, 16, 16, 8, 4};
606
607 // Capacity reserved per stream at init: 128 MB for positions, normals, tangents
608 // and colors (16 B stride, 8M verts), 64 MB for each texcoord stream (8 B),
609 // 32 MB for indices (4 B). A scene exceeding these gets a sentinel allocation
610 // from allocate() and the caller skips the mesh.
611 static constexpr uint32_t kMeshCapBytes[(std::size_t)MeshStream::Count_]
612 = {
613 128u * 1024u * 1024u,
614 128u * 1024u * 1024u,
615 64u * 1024u * 1024u,
616 128u * 1024u * 1024u,
617 128u * 1024u * 1024u, // colors
618 64u * 1024u * 1024u, // texcoords1
619 32u * 1024u * 1024u,
620 };
621
637 struct MeshSlab
638 {
639 uint64_t stable_id{};
640 OffsetAllocator::Allocation vertex_slot{}; // offset/size in vertex units
641 OffsetAllocator::Allocation index_slot{}; // offset/size in index units
642 uint32_t vertex_count{};
643 uint32_t index_count{};
644 uint32_t last_seen_frame{};
645 bool freshly_allocated{}; // true on the frame the slab was created
646 };
647
656 MeshSlab* acquireMeshSlab(
657 uint64_t stable_id,
658 uint32_t vertex_count,
659 uint32_t index_count,
660 uint32_t current_frame) noexcept;
661
663 void markMeshSlabSeen(uint64_t stable_id, uint32_t current_frame) noexcept;
664
667 void sweepMeshSlabs(uint32_t current_frame, uint32_t grace = 2) noexcept;
668
675 void sweepStaleDynamicTextureSlots() noexcept;
676
680 void forgetDynamicTexture(const QRhiTexture* tex) noexcept;
681
687 void drainExpiredPendingReleases(
688 uint32_t current_frame, uint32_t grace = 2) noexcept;
689
695 void releaseMeshSlab(uint64_t stable_id, uint32_t current_frame) noexcept;
696
699 uint32_t meshSlabOffsetBytes(
700 const MeshSlab& slab, MeshStream stream) const noexcept;
701
704 QRhiBuffer* meshStreamBuffer(MeshStream s) const noexcept;
705
709 void uploadMeshStream(
710 QRhiResourceUpdateBatch& res, const MeshSlab& slab,
711 MeshStream s, const void* data, uint32_t size) noexcept;
712
719 {
720 public:
723 : m_registry{r}
724 {
725 if(m_registry)
726 ++m_registry->m_meshUploadBatchDepth;
727 }
728 ~MeshUploadBatch()
729 {
730 if(m_registry && --m_registry->m_meshUploadBatchDepth == 0)
731 m_registry->flushMeshUploads();
732 }
733 MeshUploadBatch(const MeshUploadBatch&) = delete;
734 MeshUploadBatch& operator=(const MeshUploadBatch&) = delete;
735
736 private:
737 GpuResourceRegistry* m_registry{};
738 };
739
741 uint32_t meshStreamUsedBytes(MeshStream s) const noexcept;
742 uint32_t meshStreamFreeBytes(MeshStream s) const noexcept;
743
744private:
745 struct ArenaState
746 {
747 QRhiBuffer* buffer{};
748 uint32_t slot_stride{0}; // bytes per slot (arena layout is a packed
749 // std430-compatible array of this stride)
750 uint32_t slot_count{0}; // total slots (capacity_bytes = stride × count)
751 QRhiBuffer::UsageFlags usage{};
752 QRhiBuffer::Type type{QRhiBuffer::Dynamic};
753
754 // LIFO stack of free slot indices. Push on free, pop on allocate.
755 // O(1) alloc / free, no fragmentation (every slot is the same size).
756 std::vector<uint32_t> free_slots;
757
758 // Per-slot generation, indexed by slot_index. Sized to slot_count
759 // at init() and bumped on every allocate()/free() to that slot.
760 // Consumers check the stamped generation in their gpu_slot_ref via
761 // isLive().
762 std::vector<uint32_t> slot_generations;
763 };
764
765 std::array<ArenaState, (std::size_t)Arena::Count_> m_arenas{};
766
768 TextureChannelState m_texturePool{};
769
770 // Per-stream backing buffers, one QRhiBuffer per attribute. Allocation is not
771 // per-stream: a single m_vertexAllocator hands out vertex-unit slots that every
772 // vertex stream interprets through its own stride, and m_indexAllocator
773 // handles indices, keeping byte offsets in lockstep for baseVertex.
774 struct MeshStreamState
775 {
776 QRhiBuffer* buffer{};
777 uint32_t capacity_bytes{};
778 QRhiBuffer::UsageFlags usage{};
779 };
780 std::array<MeshStreamState, (std::size_t)MeshStream::Count_> m_meshStreams{};
781
782 // Mesh uploads held back by a MeshUploadBatch: one contiguous run per
783 // stream, and the batch it goes to.
784 struct PendingMeshUpload
785 {
786 QRhiResourceUpdateBatch* batch{};
787 uint32_t offset{};
788 std::vector<std::byte> bytes;
789 };
790 std::array<PendingMeshUpload, (std::size_t)MeshStream::Count_> m_pendingMeshUploads{};
791 int m_meshUploadBatchDepth{};
792 void flushMeshUpload(MeshStream s) noexcept;
793 void flushMeshUploads() noexcept;
794
795 // Shared vertex / index allocators (slot units, not bytes).
796 // capacity_slots = min(stream_capacity_bytes / stream_stride) across
797 // the vertex streams = 8M for the default sizes; index pool
798 // capacity = 8M slots.
799 std::unique_ptr<OffsetAllocator::Allocator> m_vertexAllocator;
800 std::unique_ptr<OffsetAllocator::Allocator> m_indexAllocator;
801 uint32_t m_vertexSlotsCapacity{};
802 uint32_t m_indexSlotsCapacity{};
803 uint32_t m_vertexSlotsUsed{};
804 uint32_t m_indexSlotsUsed{};
805
806 ossia::hash_map<uint64_t, MeshSlab> m_meshSlabs;
807
808 // Slabs whose `released_frame` is set are waiting out the grace
809 // period before their OffsetAllocator allocations return to the
810 // free list. Prevents use-after-free when an in-flight draw still
811 // references the old offset.
812 struct PendingRelease
813 {
814 uint64_t stable_id{};
815 uint32_t released_frame{};
816 OffsetAllocator::Allocation vertex_slot{};
817 OffsetAllocator::Allocation index_slot{};
818 };
819 std::vector<PendingRelease> m_pendingReleases;
820
821 QRhi* m_rhi{};
822
823 // Set by seedDefaults() after writing the default-MaterialGPU bytes
824 // into Material arena slot 0. Idempotent guard so repeated calls are
825 // free.
826 bool m_defaults_seeded{false};
827};
828
829} // namespace score::gfx
Definition GpuResourceRegistry.hpp:719
MeshUploadBatch(GpuResourceRegistry *r) noexcept
A null registry makes this a no-op.
Definition GpuResourceRegistry.hpp:722
Per-RenderList arena store for GPU-resident scene data.
Definition GpuResourceRegistry.hpp:45
QRhi * boundRhi() const noexcept
QRhi this registry was init()'d against. Null when not initialised. The owning OutputNode uses this t...
Definition GpuResourceRegistry.hpp:130
TextureChannelState & textureChannel(TextureChannel) noexcept
Definition GpuResourceRegistry.hpp:502
bool isInitialized() const noexcept
True if init() has been called and destroyOwned()/destroy() has not. Used by RenderList::init to gate...
Definition GpuResourceRegistry.hpp:122
bool isLiveIn(const ossia::gpu_slot_ref &r, Arena expected) const noexcept
isLive, additionally requiring the ref to name the expected arena.
Definition GpuResourceRegistry.hpp:274
ossia::gpu_slot_ref toOssiaRef(const Slot &slot) const noexcept
Produce an ossia::gpu_slot_ref that can be stamped on a scene-graph component for the downstream prep...
Definition GpuResourceRegistry.hpp:247
bool isLive(const ossia::gpu_slot_ref &r) const noexcept
Return true if the ref still points at a live allocation.
Definition GpuResourceRegistry.hpp:286
TextureChannelState & texturePool() noexcept
The shared pool, for call sites that are not per-channel.
Definition GpuResourceRegistry.hpp:512
List of nodes to be rendered to an output.
Definition RenderList.hpp:33
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
STL namespace.
Slab handle returned by MeshArenaManager::acquire.
Definition GpuResourceRegistry.hpp:638
Shared state for one of the PBR texture channels. Preprocessors / producers read-modify this in place...
Definition GpuResourceRegistry.hpp:494
Definition GpuResourceRegistry.hpp:85
Channel texture state with multi-bucket support.
Definition GpuResourceRegistry.hpp:351