Loading...
Searching...
No Matches
SceneGPUState.hpp
1#pragma once
2#include <score_plugin_gfx_export.h>
3#include <ossia/dataflow/geometry_port.hpp>
4
5#include <QMatrix4x4>
6
7#include <cstdint>
8#include <vector>
9
10namespace score::gfx
11{
12
13// GPU-friendly structures for packing scene data into UBOs/SSBOs. All matrices
14// are column-major.
15//
16// Raw* structs are written by source nodes (Camera, Light, Transform3D,
17// EnvironmentLoader) into their own GpuResourceRegistry arena slot at their
18// operator()() time: view-independent, no aspect-ratio math or scene-graph
19// composition. The cooked structs (CameraUBOData, LightGPU, MaterialGPU,
20// PerDrawGPU, WorldTransformMat4) are produced by ScenePreprocessor from those
21// arenas plus render-target state, and are what consumer shaders bind.
22//
23// Materials and env are composition-independent, so Raw == Cooked for them and
24// MaterialGPU / EnvParamsUBO are used directly from source nodes.
25
26#pragma pack(push, 1)
27
28// Consumer shaders read RawLightData directly from the RawLight arena
29// and compose world-space direction via world_transforms[transform_slot].
30
31// Scene-level UBO: camera + global scene data.
33{
34 float view[16]{};
35 float projection[16]{};
36 float viewProjection[16]{};
37 float cameraPosition[4]{}; // xyz = position, w = padding
38 float time{};
39 int32_t lightCount{};
40 int32_t materialCount{};
41 float padding0{};
42 float ambientColor[4]{0.03f, 0.03f, 0.03f, 1.f};
43};
44
45// Per-mesh UBO: model transform for the current draw call.
46struct MeshUBO
47{
48 float model[16]{};
49 float modelViewProjection[16]{};
50 float normalMatrix[12]{}; // mat3 in std140 = 3 × vec4 (48 bytes)
51 int32_t materialIndex{};
52 float padding[3]{};
53};
54
55// Packed 32-bit texture reference stored in MaterialGPU::textureRefs[]:
56// bits 31..30 : source (0 = NONE, 1 = STATIC pool, 2 = DYNAMIC pool)
57// bits 29..24 : bucket index within the selected pool
58// bits 23.. 0 : layer index within the bucket's texture array
59//
60// 0xFFFFFFFF is the "no texture" sentinel: the shader falls back to the
61// constant baseColor factor, metallic_factor, and so on.
62inline constexpr uint32_t tex_ref_none() { return 0xFFFFFFFFu; }
63inline constexpr uint32_t tex_ref_static(uint32_t bucket, uint32_t layer)
64{
65 // Packed layout: source:2 | bucket:7 | layer:23. The 7-bit bucket field gives
66 // headroom for 128 buckets against a runtime cap of kMaxBuckets = 16 in
67 // GpuResourceRegistry.hpp; raising the cap only needs larger shader sampler
68 // arrays, not a new encoding. Shader-side decode is `(ref >> 23) & 0x7Fu` for
69 // the bucket and `ref & 0x007FFFFFu` for the layer.
70 return (1u << 30) | ((bucket & 0x7Fu) << 23) | (layer & 0x007FFFFFu);
71}
72// Dynamic texture slot: source=2, bucket unused, low 24 bits hold the
73// per-channel slot index. Consumer shaders branch on the source bits and sample
74// one of a fixed set of direct sampler2D uniforms named <channel>Dyn0 and so on,
75// forwarding the upstream handle as-is. For large runtime textures (8K video,
76// HDR shader outputs) that do not fit the scaled-and-uploaded array path.
77inline constexpr uint32_t tex_ref_dynamic(uint32_t slot)
78{
79 return (2u << 30) | (slot & 0x00FFFFFFu);
80}
81
82// Per-material data for the material SSBO, 80 bytes (5 x vec4). The 16 B of
83// runtime metadata leaves headroom for future fields (animation ID, LOD hint,
84// shader permutation hash) without an ABI break.
86{
87 float baseColor[4]{1.f, 1.f, 1.f, 1.f};
88 // x = metallic, y = roughness, z = occlusion, w = unlit flag
89 float metallicRoughnessOcclusionUnlit[4]{0.f, 0.5f, 1.f, 0.f};
90 // xyz = emissive, w = emissive strength
91 float emissive_strength[4]{0.f, 0.f, 0.f, 1.f};
92 // Packed texture refs: [0] = base color, [1..3] reserved for MR, normal,
93 // emissive. See tex_ref_* helpers for encoding.
94 uint32_t textureRefs[4]{
95 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu};
96
97 // --- Runtime metadata (16 B) ----------------------------------------
98 // Producer-derived bitmask of "which BRDF lobes / features are active"
99 // for this material. Used as:
100 // - Coherence key for NVIDIA Shader Execution Reordering
101 // (`reorderThread(feature_mask)` before closest-hit shading) so
102 // threads in the same warp converge on the same shading path.
103 // - Shader-side specialization in the main closest-hit / fragment
104 // body: `if(fm & HAS_TRANSMISSION) { ... }`.
105 // Bit layout:
106 // bit 0 : has_base_color_texture
107 // bit 1 : has_metal_rough_texture
108 // bit 2 : has_normal_texture
109 // bit 3 : has_emissive_texture
110 // bit 4 : unlit
111 // bit 5 : alpha_non_opaque (mask OR blend)
112 // bit 6 : has_clearcoat (KHR_materials_clearcoat)
113 // bit 7 : has_sheen (KHR_materials_sheen)
114 // bit 8 : has_transmission (KHR_materials_transmission)
115 // bit 9 : has_volume (KHR_materials_volume)
116 // bit 10 : has_specular (KHR_materials_specular)
117 // bit 11 : has_iridescence (KHR_materials_iridescence)
118 // bit 12 : has_anisotropy
119 // bit 13 : has_diffuse_transmission
120 // bit 14 : has_subsurface
121 // bit 15 : thin_walled
122 // bit 16 : alpha_mask (glTF alphaMode = MASK)
123 // bit 17 : alpha_blend (glTF alphaMode = BLEND)
124 // bit 18 : double_sided (glTF doubleSided)
125 // bit 19 : has_separate_occlusion (occlusion ≠ MR source)
126 // bits 20-21 : BC texcoord_set (0 or 1, glTF TEXCOORD_0/1)
127 // bits 22-23 : MR texcoord_set
128 // bits 24-25 : Normal texcoord_set
129 // bits 26-27 : Emissive texcoord_set
130 // bits 28-29 : Occlusion texcoord_set
131 // bit 30 : shadow_caster_disabled (material.shadow_caster == false)
132 // bit 31 : reflection_caster_disabled (material.reflection_caster == false)
133 uint32_t feature_mask{0u};
134
135 // Shader binding table hit-group index for ray tracing pipelines.
136 // Producers with a pre-computed hit-group mapping stamp this at
137 // material-authoring time; 0 means "default lit material" and is the
138 // safe fallback for renderers that haven't computed the mapping yet.
139 uint32_t hit_group_id{0u};
140
141 // 5th texture channel (occlusion). glTF separates occlusionTexture
142 // from metallicRoughnessTexture; conventionally both are sometimes
143 // packed into the same image (occlusion in R, roughness in G,
144 // metallic in B). When they're distinct sources, this slot points
145 // at the occlusion array layer; when they're the same, this stays
146 // at tex_ref_none() and the shader uses MR.r * occlusion_factor.
147 uint32_t occlusion_textureRef{0xFFFFFFFFu};
148
149 // glTF alphaMode = MASK cutoff. Shader does `if(alpha < cutoff)
150 // discard;` when the `alpha_mask` feature_mask bit is set.
151 // Default 0.5 matches the glTF spec default.
152 float alpha_cutoff{0.5f};
153};
154static_assert(sizeof(MaterialGPU) == 80, "MaterialGPU layout must match shader");
155
156// Feature-mask bit flags. Producers OR these together to derive the
157// per-material feature_mask; hit-group shaders branch on them to
158// select the relevant BRDF lobe code path.
159namespace material_feature
160{
161inline constexpr uint32_t has_base_color_texture = 1u << 0;
162inline constexpr uint32_t has_metal_rough_texture = 1u << 1;
163inline constexpr uint32_t has_normal_texture = 1u << 2;
164inline constexpr uint32_t has_emissive_texture = 1u << 3;
165inline constexpr uint32_t unlit = 1u << 4;
166inline constexpr uint32_t alpha_non_opaque = 1u << 5;
167inline constexpr uint32_t has_clearcoat = 1u << 6;
168inline constexpr uint32_t has_sheen = 1u << 7;
169inline constexpr uint32_t has_transmission = 1u << 8;
170inline constexpr uint32_t has_volume = 1u << 9;
171inline constexpr uint32_t has_specular = 1u << 10;
172inline constexpr uint32_t has_iridescence = 1u << 11;
173inline constexpr uint32_t has_anisotropy = 1u << 12;
174inline constexpr uint32_t has_diffuse_transmission = 1u << 13;
175inline constexpr uint32_t has_subsurface = 1u << 14;
176inline constexpr uint32_t thin_walled = 1u << 15;
177// glTF alpha mode (parsed from material.alphaMode). MASK → shader
178// discards fragments with alpha < alpha_cutoff. BLEND → shader emits
179// translucent alpha (caller handles depth/sort separately).
180inline constexpr uint32_t alpha_mask = 1u << 16;
181inline constexpr uint32_t alpha_blend = 1u << 17;
182// glTF doubleSided. When set, shader flips the surface normal for
183// back-facing fragments (so lighting works on both sides). When unset
184// AND the pipeline cull mode is `none` (MDI default), shader discards
185// back-facing fragments to mimic single-sided culling.
186inline constexpr uint32_t double_sided = 1u << 18;
187// Separate occlusion texture present (independent from MR texture).
188// Shader samples mat.occlusion_textureRef instead of using mr.r.
189inline constexpr uint32_t has_separate_occlusion = 1u << 19;
190// Scene-filter opt-outs. "Disabled" semantics (default 0 = participates
191// in the pass) so the common case stays bit-clear. Packed at bits
192// 30/31 — CSF filter shaders test these to drop draws from auxiliary
193// passes (shadow-map, reflection capture).
194inline constexpr uint32_t shadow_caster_disabled = 1u << 30;
195inline constexpr uint32_t reflection_caster_disabled = 1u << 31;
196}
197
198// Per-material EXTENSION data: a parallel SSBO indexed by the same
199// material_index as MaterialGPU. Shaders needing only the 80-byte base material
200// ignore it; OpenPBR-grade shaders declare `scene_materials_ext`.
201//
202// std430-friendly: every member starts on a 16-byte boundary. Field names track
203// OpenPBR_ResolvedInputs / glTF KHR extension names so the shader-side
204// translation is a 1:1 copy.
205//
206// textureRefs[16] uses the same tex_ref_static / tex_ref_dynamic / tex_ref_none
207// encoding as MaterialGPU.textureRefs. The slot ordering documented below MUST
208// match what packMaterialExtensions writes and what the consumer shader's
209// Material_Ext struct reads.
211{
212 // --- Coat / clearcoat (KHR_materials_clearcoat) ---------------------
213 // x = coat_weight, y = coat_roughness, z = coat_ior, w = coat_darkening
214 float coat[4]{0.f, 0.f, 1.5f, 0.f};
215 // x = roughness_anisotropy, y = rotation_cos, z = rotation_sin, w = _pad
216 float coat_anisotropy[4]{0.f, 1.f, 0.f, 0.f};
217
218 // --- Fuzz / sheen (KHR_materials_sheen) -----------------------------
219 // xyz = color, w = roughness
220 float fuzz_color[4]{0.f, 0.f, 0.f, 0.f};
221
222 // --- Transmission + volume (KHR_materials_transmission + _volume) ---
223 // x = transmission_weight, y = transmission_depth,
224 // z = dispersion_scale, w = dispersion_abbe_number
225 float transmission[4]{0.f, 0.f, 0.f, 20.f};
226 // xyz = transmission_color, w = scatter_anisotropy
227 float transmission_color[4]{1.f, 1.f, 1.f, 0.f};
228 // xyz = transmission_scatter (vec3), w = _pad
229 float transmission_scatter[4]{0.f, 0.f, 0.f, 0.f};
230
231 // --- Specular (KHR_materials_specular) + base specular anisotropy ---
232 // x = specular_weight, yzw = specular_color
233 float specular_weight_color[4]{1.f, 1.f, 1.f, 1.f};
234 // x = specular_ior, y = roughness_anisotropy,
235 // z = rotation_cos, w = rotation_sin
236 float specular_ior_anisotropy[4]{1.5f, 0.f, 1.f, 0.f};
237
238 // --- Thin-film iridescence (KHR_materials_iridescence) --------------
239 // x = thin_film_weight (iridescence factor),
240 // y = thin_film_thickness (glTF average of min/max),
241 // z = thin_film_ior, w = _pad
242 float thin_film[4]{0.f, 400.f, 1.3f, 0.f};
243
244 // --- Diffuse transmission (KHR_materials_diffuse_transmission) ------
245 // x = factor, yzw = color
246 float diffuse_transmission[4]{0.f, 1.f, 1.f, 1.f};
247
248 // --- Subsurface (OpenPBR subsurface; not present in stock glTF) -----
249 // x = weight, yzw = color
250 float subsurface_weight_color[4]{0.f, 0.8f, 0.8f, 0.8f};
251 // x = radius, yzw = radius_scale
252 float subsurface_radius_scale[4]{1.f, 1.f, 0.5f, 0.25f};
253
254 // --- Misc scalars + flags -------------------------------------------
255 // x = base_diffuse_roughness (OpenPBR Oren-Nayar knob),
256 // y = thin_walled (bool-as-float 0/1),
257 // z = _pad, w = _pad
258 float flags[4]{0.f, 0.f, 0.f, 0.f};
259
260 // --- Texture refs ---------------------------------------------------
261 // Slot layout:
262 // 0 = coat factor
263 // 1 = coat roughness
264 // 2 = coat normal
265 // 3 = fuzz color (sheen)
266 // 4 = fuzz roughness
267 // 5 = transmission
268 // 6 = specular factor
269 // 7 = specular color
270 // 8 = iridescence (thin-film)
271 // 9 = iridescence thickness
272 // 10 = anisotropy
273 // 11 = diffuse transmission
274 // 12 = diffuse transmission color
275 // 13 = subsurface factor
276 // 14 = subsurface color
277 // 15 = reserved
278 uint32_t textureRefs[16]{
279 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu,
280 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu,
281 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu,
282 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu};
283};
284
285// ─── Raw layouts (source-owned arena slots) ────────────────────────────
286//
287// Written by source halp nodes directly into their GpuResourceRegistry arena
288// slot. ScenePreprocessor reads these, applies aspect-ratio and scene-graph
289// composition, and writes the cooked equivalents consumer shaders bind.
290
291// Camera parameters before matrix composition. No aspect ratio, no
292// view / projection matrices — the preprocessor builds those per render
293// target.
295{
296 float eye[4]{0.f, 0.f, 3.f, 0.f}; // xyz = world-space eye, w = pad
297 float target[4]{0.f, 0.f, 0.f, 0.f}; // xyz = look-at target, w = pad
298 float up[4]{0.f, 1.f, 0.f, 0.f}; // xyz = up, w = pad
299 float yfov{60.f * 3.14159265f / 180.f}; // vertical FOV, radians
300 float znear{0.1f};
301 float zfar{1000.f};
302 uint32_t projection{0}; // 0 = perspective, 1 = orthographic
303};
304
305// Light parameters in local frame. The final world-space direction
306// depends on the node's world transform (composed by the preprocessor
307// from its scene-node parent chain); this struct stores only what the
308// node itself knows.
310{
311 float color[4]{1.f, 1.f, 1.f, 1.f}; // xyz = color, w = intensity
312 float local_direction[4]{0.f, 0.f, -1.f, 0.f}; // xyz = dir (local),
313 // w = type enum:
314 // 0 = directional
315 // 1 = point
316 // 2 = spot
317 // (area / dome modes
318 // collapse to point /
319 // directional; dome
320 // lights are served by
321 // the scene-global env
322 // path, see EnvParamsUBO.)
323 float range_cone[4]{ // x = range (point/spot;
324 0.f, 1.f, 0.7071f, 0.005f}; // 0 = infinite)
325 // y = inner cone cos
326 // z = outer cone cos
327 // w = shadow depth bias
328 // Shadow gate: shadow-receiving shaders MUST multiply the computed shadow term
329 // by `shadow_enabled != 0`. Separate from the per-material
330 // shadow_caster_disabled bit, which controls whether a draw participates in the
331 // depth-only cast pass.
332 uint32_t shadow_enabled{0};
333 uint32_t decay_mode{2}; // 0=const 1=lin 2=quad 3=cubic
334 // RawTransform arena slot index for this light's scene_transform.
335 // Consumer shader reads world_transforms.data[transform_slot] to
336 // get the world matrix, composes world-space direction / position
337 // from local_direction on the fly.
338 uint32_t transform_slot{0};
339 // Receiver-plane / slope-scaled bias for shadow sampling, exposed in the UI
340 // as Light::inputs.shadow_normal_bias. PCF shaders add
341 // `normal_bias * (1 - max(dot(N, Ldir), 0))` to the receiver depth before the
342 // comparison, to kill shadow acne on grazing surfaces.
343 float normal_bias{0.01f};
344};
345static_assert(sizeof(RawLightData) == 64, "RawLightData must stay 64 B");
346
347// Local TRS for a scene_transform, stamped by the producer and uploaded into a
348// RawTransform arena slot. Parent-chain composition stays on the CPU inside
349// ScenePreprocessor's FlattenVisitor; the composed world matrix ends up in the
350// WorldTransform arena at the same offset the RawTransform slot occupies.
352{
353 float translation[4]{0.f, 0.f, 0.f, 0.f}; // xyz + pad
354 float rotation[4]{0.f, 0.f, 0.f, 1.f}; // quaternion xyzw
355 float scale[4]{1.f, 1.f, 1.f, 0.f}; // xyz + pad
356 float _pad[4]{}; // std430 alignment
357};
358
359// Environment parameters (ambient, fog, exposure, gamma). Already
360// view-independent — this is both Raw (source-written) and Cooked
361// (shader-bound) in one struct. Published here so EnvironmentLoader
362// can write its own slot bytes matching what ScenePreprocessor expects
363// on the other end.
365{
366 float ambient[4]{0.03f, 0.03f, 0.03f, 1.f}; // xyz = color, w = intensity
367 float fog_color_density[4]{0.8f, 0.8f, 0.8f, 0.f}; // xyz = color, w = density
368 float fog_range[4]{10.f, 100.f, 0.f, 0.f}; // x = start, y = end,
369 // z = mode, w = enabled (0/1)
370 float exposure_gamma[4]{1.f, 2.2f, 0.f, 0.f}; // x = exposure (linear),
371 // y = gamma, zw = pad
372};
373
374// World-space mat4 emitted by ScenePreprocessor's FlattenVisitor from
375// the scene_node tree (CPU walk with parent-chain accumulation). One
376// entry per producer-authored scene_transform, laid out at the same
377// byte offset as the producer's RawTransform slot so shaders can
378// address either side by `scene_transform::raw_slot.offset`.
380{
381 float m[16]{1.f, 0.f, 0.f, 0.f,
382 0.f, 1.f, 0.f, 0.f,
383 0.f, 0.f, 1.f, 0.f,
384 0.f, 0.f, 0.f, 1.f};
385};
386
387// Shadow cascades UBO, scene-wide, published by ScenePreprocessor as the
388// `shadow_cascades` aux on the output geometry. Shading shaders read it to pick
389// the right cascade per fragment; the depth-only pass reads light_view_proj from
390// it to transform vertices into cascade clip-space, with its per-invocation
391// cascade_index in a separate `shadow_draw_cfg` UBO.
392//
393// std140, 560 B. Fields mirror ossia::shadow_cascades_info in geometry_port.hpp:
394// light_view_proj[8] world -> cascade clip-space per cascade
395// cascade_split_distances[8] view-space far-plane Z of cascade k; slots
396// >= cascade_count read as 0
397// cascade_count how many entries are live (0..8)
399{
400 float light_view_proj[8][16]{};
401 // 8 split distances symmetric with light_view_proj[8].
402 // std140: two consecutive vec4 rows (32 B total).
403 float cascade_split_distances[8]{};
404 uint32_t cascade_count{0};
405 uint32_t _pad0{};
406 uint32_t _pad1{};
407 uint32_t _pad2{};
408};
409static_assert(sizeof(ShadowCascadesUBO) == 560,
410 "ShadowCascadesUBO size = mat4[8] (512) + float[8] (32) + 4×uint (16) = 560 B");
411
412#pragma pack(pop)
413
414// CPU-side flattened scene representation.
416{
417 // Points at either a mesh from geometry_ref (legacy_geometry path) OR at
418 // owned_mesh (mesh_primitive path). `mesh` is always non-null for a valid
419 // draw; one of geometry_ref or owned_mesh keeps the target alive.
420 const ossia::geometry* mesh{};
421 ossia::geometry_spec geometry_ref; // Legacy path: keeps source alive.
422 std::shared_ptr<ossia::geometry> owned_mesh; // Primitive path: built from mesh_primitive.
423
424 // Stable cross-frame identity of the source mesh primitive: the mesh_primitive's
425 // stable_id, or its raw pointer bits for a legacy producer that stamps none.
426 // `mesh` cannot serve -- on the primitive path it points into a freshly
427 // allocated wrapper with a different address every flatten. ScenePreprocessor
428 // uses this to skip vertex/index re-uploads.
429 uint64_t stable_id{};
430
431 QMatrix4x4 worldTransform;
432
433 // Direct shared_ptr to the material — null means "no material / use
434 // the renderer's default factors". Carries the material's gpu_slot_ref
435 // for GPU-side lookup without any scene-wide index array.
436 ossia::material_component_ptr material;
437
438 // Direct shared_ptr to the skin — null means "no skinning". When
439 // present, the ScenePreprocessor attaches a `joint_matrices` auxiliary
440 // buffer to this draw's output geometry; a downstream skinning compute
441 // pass (or user shader) deforms positions/normals using
442 // joints0/weights0 vertex attributes.
443 ossia::skeleton_component_ptr skin;
444
445 // Index into FlatScene::materials after the flatten pass has
446 // deduplicated the material pointers into its flat materials array.
447 // -1 means "material was null / default factors only". Set by
448 // flattenScene after collecting all draws.
449 int materialIndex{-1};
450
451 // Index into FlatScene::skins after dedup. -1 = no skinning.
452 int skinIndex{-1};
453
454 // Local-space AABB of the source mesh_primitive. Copied by the
455 // FlattenVisitor from mesh_primitive::bounds. Empty (inverted) if the
456 // source didn't compute bounds — downstream per_draw_bounds emitter
457 // writes an infinite AABB in that case so GPU culling shaders never
458 // cull the draw.
459 ossia::aabb local_bounds{};
460
461 // RawTransform arena slot of the nearest producer-authored
462 // scene_transform on this draw's walk path (0xFFFFFFFF = none). Stamped
463 // into PerDrawGPU.transform_slot so shaders can look up
464 // world_transforms_prev[slot] for motion vectors / TAA / reprojection.
465 std::uint32_t transform_slot{0xFFFFFFFFu};
466};
467
468// Per-skeleton packed joint matrices: joint_matrix[i] = world_joint × inverse_bind.
469// One std::vector<QMatrix4x4> per skeleton index (parallel to scene_state.skeletons).
471{
472 std::vector<QMatrix4x4> joint_matrices;
473};
474
475// One entry per producer-authored scene_transform seen during the walk. The
476// preprocessor's private world-transforms SSBO is a packed array indexed by the
477// scene_transform's raw_slot.internal_index, and consumer shaders read
478// world_transforms.data[transform_slot].
479//
480// Each preprocessor owns its own buffer, so two preprocessors with different
481// filtered views of one scene compute different world matrices for the same
482// scene_transform without stomping each other.
484{
485 QMatrix4x4 world;
486 uint32_t transform_slot; // RawTransform arena slot index
487};
488
490{
491 std::vector<DrawCall> draws;
492 // RawLight arena slot index per light the walk encountered.
493 // 0xFFFFFFFF for producer-less lights (filtered out when building
494 // scene_light_indices, the shader-facing compact indices list).
495 std::vector<uint32_t> lightArenaSlots;
496 std::vector<MaterialGPU> materials;
497 // Parallel to `materials`: same size, same indexing, zeroed to the OpenPBR spec
498 // defaults for materials that set no extension fields. Consumer shaders either
499 // ignore it or bind it as `scene_materials_ext`.
500 std::vector<MaterialExtensionsGPU> material_extensions;
501 std::vector<SkeletonGPU> skins; // Parallel to scene_state.skeletons.
502
503 // World matrices to upload into the WorldTransform arena, one per
504 // producer-authored scene_transform encountered in the walk whose
505 // raw_slot is valid. Sparse: the arena is indexed by offset, not
506 // by position in this vector.
507 std::vector<WorldTransformEmit> worldTransforms;
508
509 // Loader-emitted scene_data payloads, collected during the walk.
510 // ScenePreprocessor forwards each entry as an auxiliary_buffer on every output
511 // geometry (by name). Lifetime held via shared_ptr.
512 std::vector<ossia::scene_data_ptr> scene_data;
513
514 // Instance components encountered during the walk. Each pair is a
515 // (worldTransform, instance_component_ptr) that the ScenePreprocessor emits as
516 // a dedicated instanced DrawCall with per-instance auxiliaries.
518 {
519 ossia::instance_component_ptr instance;
520 QMatrix4x4 worldTransform;
521 };
522 std::vector<InstanceDraw> instances;
523
524 // Primitive cloud (splat / point-cloud) entries: format-agnostic payloads whose
525 // schema is described by their CSF chain. ScenePreprocessor buckets them by
526 // format_id, one indirect-draw geometry per bucket; an empty format_id is
527 // bucketed individually on the stable id.
529 {
530 ossia::primitive_cloud_component_ptr cloud;
531 QMatrix4x4 worldTransform;
532 // RawTransform arena slot index, or 0xFFFFFFFFu if no producer
533 // transform was on the walk path. Mirrors PerDrawGPU.transform_slot.
534 uint32_t transform_slot{0xFFFFFFFFu};
535 };
536 std::vector<PrimitiveCloudDraw> primitive_clouds;
537
538 // Cameras collected from the scene tree. Each entry keeps its source
539 // camera_component alive, its accumulated world transform (column 3 =
540 // eye position, inverse = view matrix), and the scene_node_id of the
541 // node it was attached to so consumers can resolve `active_camera_id`.
543 {
544 ossia::camera_component_ptr component;
545 QMatrix4x4 worldTransform;
546 ossia::scene_node_id node_id{};
547 };
548 std::vector<CameraEntry> cameras;
549
550 // Index into `cameras` of the currently-active camera. -1 when the scene
551 // has no cameras; in that case downstream falls back to a default eye
552 // placement (see the legacy single-camera fields below, populated from
553 // this slot if valid or from a default otherwise).
554 int activeCameraIndex{-1};
555
556 // Camera (from scene or override) — legacy mirror fields. Kept populated
557 // for consumers that haven't migrated to `cameras[activeCameraIndex]`
558 // yet. Resolved by flattenScene() after the tree walk:
559 // - cameras empty → sensible default (eye at (0,1,3))
560 // - cameras nonempty → copied from cameras[activeCameraIndex]
561 QMatrix4x4 viewMatrix;
562 QMatrix4x4 projectionMatrix;
563 QVector3D cameraPosition;
564 float cameraFov{60.f};
565 float cameraNear{0.1f};
566 float cameraFar{1000.f};
567
568 bool hasCamera{false};
569
570 void clear()
571 {
572 draws.clear();
573 lightArenaSlots.clear();
574 materials.clear();
575 material_extensions.clear();
576 skins.clear();
577 scene_data.clear();
578 instances.clear();
579 primitive_clouds.clear();
580 cameras.clear();
581 worldTransforms.clear();
582 activeCameraIndex = -1;
583 hasCamera = false;
584 // The legacy single-camera mirror is part of the state too: leaving it
585 // populated means a reused FlatScene reports the previous scene's camera
586 // to any consumer that reads it without checking hasCamera.
587 viewMatrix.setToIdentity();
588 projectionMatrix.setToIdentity();
589 cameraPosition = {};
590 cameraFov = 60.f;
591 cameraNear = 0.1f;
592 cameraFar = 1000.f;
593 }
594};
595
596// Flatten a scene_spec into a FlatScene for GPU consumption.
597SCORE_PLUGIN_GFX_EXPORT
598void flattenScene(
599 const ossia::scene_spec& scene,
600 FlatScene& out,
601 float aspectRatio);
602
603// Build a transient ossia::geometry that wraps a mesh_primitive's buffers
604// and attributes. The result is heap-allocated and owned by shared_ptr so
605// callers can keep it alive beyond the flatten pass (see DrawCall::owned_mesh).
606SCORE_PLUGIN_GFX_EXPORT
607std::shared_ptr<ossia::geometry>
608primitiveToGeometry(const ossia::mesh_primitive& prim);
609
610SCORE_PLUGIN_GFX_EXPORT
611MaterialGPU packMaterial(const ossia::material_component& mc);
612MaterialExtensionsGPU packMaterialExtensions(const ossia::material_component& mc);
613}
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
Definition SceneGPUState.hpp:416
Definition SceneGPUState.hpp:365
Definition SceneGPUState.hpp:543
Definition SceneGPUState.hpp:518
Definition SceneGPUState.hpp:529
Definition SceneGPUState.hpp:490
Definition SceneGPUState.hpp:211
Definition SceneGPUState.hpp:86
Definition SceneGPUState.hpp:47
Definition SceneGPUState.hpp:295
Definition SceneGPUState.hpp:310
Definition SceneGPUState.hpp:352
Definition SceneGPUState.hpp:33
Definition SceneGPUState.hpp:399
Definition SceneGPUState.hpp:471
Definition SceneGPUState.hpp:484
Definition SceneGPUState.hpp:380