name: SRE unified scene overview: Replace SRE's three scene sources, ISceneSource contract and per-group GpuScene with one entity hierarchy (generational handles, a required transform, typed components), an asset library whose glTF prefabs are instantiated without copying GPU data, and a RenderScene that syncs the hierarchy into one geometry pool, one instance/material/joint buffer set and draw lists usable by Direct, batched-instanced and Indirect strategies. Vessels own the per-step script. All 12 gfx.sre goldens are regenerated (spirv and elf); legacy libraries and tests are untouched. todos:
Your decisions:
gfx.sre.* cases get new goldens. Legacy gfx.stress3d.*, gfx.indirect.* and their CSV files stay as they are.ISceneSource, FrameState and k_SceneContractVersion = 1 go. The new Scene is the only representation, with the contract bumped to v2..sp3 and TOML files for gfx11, gfx11_5, gfx12 and gfx13 in the same change.Conflicts between the prompt and the code, and how the plan resolves each:
gfx_scene: loadNodeVertices/loadNodeIndices in engine/gfx/support/scene/src/scene_renderer_scene.cpp:195-207 append the mesh again for every node. Three 100k-vertex cars take about 25 MB instead of 8.4 MB.loadMesh, line 433) and packs each mesh once per group (gpu_scene.cpp lines 194-222).scene_contract.h, sre.rst and scene_contract_test.cpp. Per your decision the contract is replaced, and sre.mdc and sre.rst change in the same commits.sre.mdc forbids changing engine/gfx/support/scene, scene_indirect and app/scene_viewer. The plan respects this. SRE keeps using gfx_scene only for SceneAsset (reading the .scene file) and Camera (projection math).pbr.vert.spv, pbr_amdllpc.*.bin, fur.*, hair.* and meshlet.* are referenced by engine/gfx/support/scene/asset/*_pipeline.json, and pbr_indirect.* is used by IndirectVessel.sre_* files under engine/gfx/graphics_core/test/shaders/.InstancedDraw is not object instancing.
max(stepIndex % 150, 1), sre_furry_orb_vessel.cpp lines 109-111). Hairmark uses it for 500 hair shells (gl_InstanceIndex in hair.vert:38-41, fur.vert:51-55).ShellDraw and adds a separate BatchedDraw for real object instancing.FrameGraph compiles and realizes once, with no reset (frame_graph.h lines 407-420). SreVessel::setUp does this once (sre_vessel.cpp lines 119-124).SreVessel records step N+1 while step N is still on the GPU (lines 200-207).vkCmdUpdateBuffer. The data is captured into the command buffer at record time, so recording ahead stays safe.RenderScene exists. Entities, components and materials stay mutable within the configured capacities.scene/doc/stress3d_vessel.rst and scene_rendering_engine.rst describe a RenderEngine/AutoConstantDataSource design that does not exist in code. The plan is based on the code.vk_command_buffer.cpp:1383-1397).firstInstance with instance-rate attributes is proven by IndirectPbrTechnique.gl_DrawID and gl_BaseInstance are unverified on the TNG driver, so the design does not use them.Legacy Stress3D (gfx_scene)
.scene.drawIndexed per primitive per node, rebinding 8 descriptor sets and a 28-byte push constant each time (scene_renderer_scene.cpp:1463-1515).Legacy indirect (gfx_scene_indirect)
indirect3d::SceneAsset.firstInstance.DrawIndexedIndirectCommands, one per primitive with instanceCount = N, issued as a single drawIndexedIndirect (scene_indirect_renderer.cpp:246-290, 607-660).SRE (GltfSceneSource, IndirectSceneSource and ProceduralSceneSource, feeding GpuScene)
GpuGroup stores each mesh once per group. A mesh used in two groups is stored twice.Instance list is fixed: count, order, mesh and group never change (scene_contract.h line 292).NodeUbo (stress3d_uniforms.h lines 48-55).DirectDraw issues one draw per DrawItem, which is one primitive of one instance.IndirectDraw uses commands built inside IndirectPbrTechnique (indirect_pbr_technique.cpp lines 91-150).NodeUbo descriptor set, bound in every bindDrawState.
packNodeMatrix).SceneUbo.model, taken from the group's first instance. gl_InstanceIndex is the shell index.R32_SINT attributes for node slot, material and skin offset (bindings 6-8), read from a node-matrix SSBO. The material comes from a material SSBO and textures from sampler2D[128].NodeUbo is 8260 bytes, with one buffer and one descriptor set per instance per technique. Each Stress3dUniforms holds its own copy (stress3d_uniforms.h:130-133).
uploadGroups packs per group.gpu_scene.h:61). Storage grows quadratically with the number of meshlet primitives..scene format, plus the separate asset sets scenes/ and scenes_indirect/ built from the same glTF files.computeZoomToFitModel);cameras.back() and lights.back() selection;Consumers of ip/gfx/sre/scene/*: only gfx_sre itself, gfx_sre_test, and the vessel/sre/ and vessel/include/test/gfx/sre/ files. vessel_viewer and scene_viewer do not use SRE.
scene/ (may include vkutil):
entity.h, components.h, scene.h and scene_systems.h (animation and bounds): pure CPU code, no Vulkan.asset_library.h and gltf_loader.h: CPU code, using gfx_scene's SceneAsset.render_scene.h: GPU sync, using vkutil.frame/ stays below scene/. Because scene/ may not include frame/, RenderScene exposes its buffers and the recipe/technique layers import them into the frame graph. This mirrors how techniques import GpuGroup buffers today.gltf_scene_source.*, indirect_scene_source.*, procedural_scene_source.*, gpu_scene.*, scene_conformance.*, and the ISceneSource/FrameState part of scene_contract.h. That file is renamed scene_types.h; it keeps MeshData, MaterialData, TextureData and LightData, adds SkinData and AnimationClip, and sets the version to 2.The design is entity handles plus one dense node table holding the required transform and the hierarchy links, plus one typed component pool per component type.
Why this rather than a pointer-based node tree like GltfSceneSource::Node (unique_ptr nodes with parent and child pointers):
Why not a full ECS library: the repo has no entt or flecs in its Conan dependencies, archetype storage is unnecessary at test scale, and determinism is easier with simple ordered storage.
Entity: {uint32_t index; uint32_t generation;}, following the style of frame::ResourceHandle.
isAlive() returns false and accessors throw std::invalid_argument.Transform per node: local TRS plus an optional glTF matrix (local = TRS × matrix, as both loaders compute today). The node also caches world and a dirty flag.parent, firstChild, lastChild, prevSibling and nextSibling as entity indices, so child order is explicit and sibling order is the order children were added.
GltfSceneSource::buildInstances today.ComponentPool<T> is a sparse-set pool (dense values plus an entity-to-slot map), type-erased behind IComponentPool, with a per-type id from componentTypeId<T>().
Name, MeshRenderer, SkinnedMesh, Light, CameraComponent, AnimationPlayer.editTransform(), edit<T>()) sets a per-entity dirty bit; const get does not.structureVersion, which tells RenderScene to rebuild its draw lists.AssetLibrary owns resources: MeshData, MaterialData, TextureData, SkinData, AnimationClip and Prefab, addressed by the existing ids (MeshId, MaterialId, TextureId) plus new SkinId, AnimationId and PrefabId..scene twice returns the same PrefabId.Scene (the template) owned by the library, with a root entity holding one child root per glTF scene.
instantiate() is a cross-scene subtree clone. Resource ids are copied, never the data, so there is one clone implementation shared by instantiate and clone.GltfLoadOptions::layerPerScene, default true): scene i gets MeshRenderer::layers = 1 << i. This replaces the old "group" concept; furry orb uses groups 0-3.gltf_loader.cpp) built on the existing gltf_accessors.h:
GltfLoadOptions::legacyLeftHanded, applied to camera and light entities. They do not live in technique code.renderer.material semantics): AssetLibrary::cloneMaterial(id) returns a new MaterialId. MeshRenderer::materials[i] overrides the mesh primitive's default material. Materials may be added or edited after the GPU upload, up to RenderSceneCapacity::materials.AssetLibrary::addMesh(makeMeshData(mesh)), with makeMeshData kept from procedural_scene_source.h.RenderScene::registerImageTexture(name) reserves a texture-array slot whose descriptor is written once the frame graph is realized. Furry orb's render-to-texture floor then becomes a cloned material referencing that slot, replacing the current PbrOptions::baseColorImage special case.RenderScene)AssetLibrary:
vertexOffset = 0, as in the indirect path. Each mesh is stored exactly once whatever its layers.RenderSceneCapacity), each a single-binding descriptor set shared by every technique:
View UBO: view, projection, view-projection, camera position;Instances SSBO: InstanceData = world mat4 (column-major, one convention), joint offset, joint count, flags; 80 bytes;Joints SSBO;Materials SSBO;Lights;Textures: sampler2D[k_MaxTextureCount], one binding holding an array.sync(Scene&, StepTime), once per step on the CPU:
structureVersion changed, rebuild every requested DrawList.recordUpdates(cmd): vkCmdUpdateBuffer in chunks of at most 64 KiB (as indirect_pbr_technique.cpp:470-475 does), for dirty ranges only. The first step writes everything.
Recipe::build() records the RenderScene updates first in its update pass, then the techniques' updates.SreVessel stays correct.DrawList, requested by a technique before importResources:
DrawFilter{layers, order}, where order is Hierarchy (pre-order, required for blending) or ByMesh (mesh, then primitive, then instance; the batching order).DrawBatches, each {mesh, primitive, firstIndex, indexCount, vertexCount, material, firstRecord, recordCount, hasMeshlets};DrawRecord stream {instanceSlot, material}, an instance-rate vertex buffer of R32G32_UINT;DrawIndexedIndirectCommand per indexed batch, where firstInstance = firstRecord.Hierarchy order each batch has recordCount = 1; in ByMesh order it covers every instance of the mesh primitive.DirectDraw: for each batch and record, bindDrawState, then drawIndexed(count, 1, first, 0, firstInstance = record).BatchedDraw (new): one drawIndexed(count, recordCount, first, 0, firstRecord) per batch.IndirectDraw: drawIndexedIndirect over the list's command buffer. It now takes a DrawList rather than a technique-built buffer.ShellDraw (renamed InstancedDraw): per record, push {instance, material}, then drawIndexed(count, shellCount(), first, 0, 0). gl_InstanceIndex stays the shell index.MeshletDraw: per record, push {instance, meshletOffset, meshletCount}, then drawMeshTasksEXT(meshletCount). Only this mesh's meshlets are dispatched.sre_* files; legacy files untouched)shaders/glsl/sre_scene.glsl declares:
sampler2DArray, hair texture array, meshlet SSBOs).sre_pbr, sre_fur, sre_caustic, sre_pn_tess, sre_stress_tess, sre_spotlights):
layout(location = 6) in uvec2 inDrawRecord; read through firstInstance.instances[inDrawRecord.x].world and the material is materials[inDrawRecord.y].sre_fur under ShellDraw, sre_hair, sre_meshlet): {uint instance; uint material; ...} comes from a vertex/mesh-stage push constant, so gl_InstanceIndex stays the shell index.
ShellDraw only; the Pbr technique picks the push-constant path when its program is Fur.jointOffset, with no 128-joint UBO cap..pipe file (userDataNode per set, INPUT_RATE_INSTANCE binding 6, push-constant dwords);add_shader_executable targets in graphics_core/test/CMakeLists.txt;device/{navi48,strix1_b0,medusa1,medusa2,krackan2e,alphatrion2,phoenix}/CMakeLists.txt;disassemble_elf, producing shaders/{gfx11,gfx11_5,gfx12,gfx13}/sre_*.sp3 plus pipeline_metadata/<gen>/sre_*.toml;add_elf_shader_pipeline entries.namespace diag::ip::gfx::sre::scene
{
struct Entity
{
uint32_t index = std::numeric_limits<uint32_t>::max();
uint32_t generation = 0U;
[[nodiscard]] bool isValid() const noexcept;
friend bool operator==(const Entity&, const Entity&) = default;
};
struct Transform
{
Vector3 translation{Vector3::k_Zero};
Quaternion rotation{Quaternion::k_Identity};
Vector3 scale{Vector3::k_UnitScale};
// NOLINTBEGIN(readability-redundant-member-init)
std::optional<Matrix4> matrix{}; ///< glTF node matrix, applied after TRS
// NOLINTEND(readability-redundant-member-init)
[[nodiscard]] Matrix4 getLocalMatrix() const;
};
enum class ParentMode : uint8_t { KeepWorld, KeepLocal };
enum class DrawOrder : uint8_t { Hierarchy, ByMesh };
struct Name { std::string value{}; };
struct MeshRenderer { MeshId mesh = 0U; std::vector<MaterialId> materials{}; uint32_t layers = 1U;
bool visible = true; std::optional<vk::Extent2D> fragmentShadingRate{}; };
struct SkinnedMesh { SkinId skin = 0U; std::vector<Entity> joints{}; };
struct Light { LightData data{}; }; ///< pose from the entity's world transform
struct CameraComponent{ Camera camera{}; }; ///< pose from the entity's world transform
struct AnimationPlayer{ AnimationId clip = 0U; std::vector<Entity> targets{}; bool loop = true; };
template<typename... Ts> class View; ///< ascending entity order; begin()/end() yield (Entity, Ts&...)
class Scene final
{
public:
Scene();
~Scene();
TNG_UNCOPYABLE_MOVABLE(Scene)
// Entities
Entity createEntity(std::string_view name = {}, Entity parent = {}, const Transform& local = {});
void destroy(Entity entity); ///< entity and all descendants
[[nodiscard]] bool isAlive(Entity entity) const noexcept;
Entity clone(Entity source, Entity parent = {}); ///< deep subtree clone, resources shared
Entity cloneFrom(const Scene& source, Entity sourceRoot, Entity parent = {}, const Transform& local = {});
// Hierarchy
void setParent(Entity child, Entity parent, ParentMode mode = ParentMode::KeepWorld);
void setSiblingIndex(Entity entity, uint32_t index);
[[nodiscard]] Entity getParent(Entity entity) const;
[[nodiscard]] std::vector<Entity> getChildren(Entity entity) const;
[[nodiscard]] std::span<const Entity> getRoots() const noexcept;
[[nodiscard]] Entity find(Entity root, std::string_view path) const; ///< "a/b/c" by Name
template<typename F> void forEachInSubtree(Entity root, F&& visit) const; ///< pre-order
// Transform
[[nodiscard]] const Transform& getLocalTransform(Entity entity) const;
Transform& editTransform(Entity entity); ///< marks the subtree dirty
void setLocalTransform(Entity entity, const Transform& local);
void setLocalPosition(Entity entity, const Vector3& position);
void setLocalRotation(Entity entity, const Quaternion& rotation);
void setLocalScale(Entity entity, const Vector3& scale);
[[nodiscard]] Matrix4 getWorldMatrix(Entity entity); ///< propagates lazily
void setWorldMatrix(Entity entity, const Matrix4& world);
void updateWorldTransforms();
// Components
template<typename T> T& add(Entity entity, T component = {});
template<typename T> void remove(Entity entity);
template<typename T> [[nodiscard]] bool has(Entity entity) const;
template<typename T> [[nodiscard]] const T& get(Entity entity) const;
template<typename T> [[nodiscard]] const T* tryGet(Entity entity) const;
template<typename T> T& edit(Entity entity); ///< marks the component dirty
template<typename T> T* tryEdit(Entity entity);
template<typename T> [[nodiscard]] T* findInChildren(Entity root); ///< first in pre-order
template<typename... Ts> [[nodiscard]] View<Ts...> view();
[[nodiscard]] uint64_t getStructureVersion() const noexcept;
};
struct GltfLoadOptions { bool layerPerScene = true; bool legacyLeftHanded = true; float defaultCameraAspectRatio = 1.0F; };
class AssetLibrary final
{
public:
PrefabId loadGltf(std::string_view assetPath, const vkutil::DeviceContext& device, const GltfLoadOptions& options);
MeshId addMesh(MeshData mesh);
MaterialId addMaterial(MaterialData material);
TextureId addTexture(TextureData texture);
MaterialId cloneMaterial(MaterialId source);
MaterialData& editMaterial(MaterialId material); ///< marks it dirty for RenderScene
[[nodiscard]] const Prefab& getPrefab(PrefabId prefab) const;
[[nodiscard]] const MeshData& getMesh(MeshId mesh) const;
[[nodiscard]] std::span<const MaterialData> getMaterials() const noexcept;
void freezeResources(); ///< called by RenderScene; later mesh/texture adds throw
};
Entity instantiate(Scene& scene, const AssetLibrary& assets, PrefabId prefab,
Entity parent = {}, const Transform& local = {});
void animate(Scene& scene, const AssetLibrary& assets, const StepTime& time); ///< every AnimationPlayer
[[nodiscard]] BoundingBox computeWorldBounds(Scene& scene, const AssetLibrary& assets, Entity root);
struct RenderSceneCapacity { uint32_t instances = 4096U; uint32_t drawRecords = 16384U; uint32_t materials = 1024U;
uint32_t joints = 8192U; uint32_t lights = 16U; };
struct DrawFilter { uint32_t layers = ~0U; DrawOrder order = DrawOrder::Hierarchy; };
struct DrawListHandle { uint32_t index = std::numeric_limits<uint32_t>::max(); };
struct RenderStats { size_t geometryBytes; size_t instanceBytes; size_t materialBytes; uint32_t instances;
uint32_t batches; uint32_t records; };
class RenderScene final
{
public:
RenderScene(const vkutil::DeviceContext& context, AssetLibrary& assets, const RenderSceneCapacity& capacity);
DrawListHandle requestDrawList(const DrawFilter& filter);
TextureId registerImageTexture(std::string name);
void bindImageTexture(TextureId texture, vk::ImageView view);
void setActiveCamera(Entity camera);
void sync(Scene& scene, const StepTime& time);
void recordUpdates(vk::raii::CommandBuffer& commandBuffer);
[[nodiscard]] const GpuGeometry& getGeometry() const noexcept;
[[nodiscard]] const SceneSets& getSets() const noexcept; ///< View..Lights sets and layouts
[[nodiscard]] std::span<const vkutil::GpuBuffer* const> getUpdatedBuffers() const; ///< for frame-graph import
[[nodiscard]] const DrawList& getDrawList(DrawListHandle handle) const;
[[nodiscard]] const StepTime& getTime() const noexcept;
[[nodiscard]] RenderStats getStats() const noexcept;
};
} // namespace diag::ip::gfx::sre::scene
Technique interface changes (technique.h):
getGroup() becomes getDrawList().bindDrawState(cmd, const scene::DrawBatch&, uint32_t record).getDrawRepeatCount()/bindRepeatState() stay; caustic draws each primitive twice.importResources imports the RenderScene buffers once, through a helper in techniques/common/scene_resources.h.SreVessel gains a script hook (sre_vessel.h):
Scene becomes {AssetLibrary assets; scene::Scene world; unique_ptr<RenderScene> renderScene; techniques}.virtual void onStep(scene::Scene&, scene::AssetLibrary&, const StepTime&) {}.recordStep() runs onStep(), then scene::animate(), then renderScene->sync(), then frameGraph->record().buildRecipe() is the "Start" phase: load prefabs, instantiate, create techniques and passes.loadGltfSource and loadIndirectSource become a single loadPrefab(context, options).Unity example in Vessel pseudo-C++:
SreVessel::Scene SrePrefabHierarchyVessel::buildRecipe(RecipeContext& context)
{
Scene s = makeScene(context); // assets, world, renderScene (capacity defaults)
const auto prefab = s.assets.loadGltf(context.asset, context.device, {});
const auto parent = instantiate(s.world, s.assets, prefab, {}, {.translation = {0, 0, 0}});
s.world.edit<sre::scene::Name>(parent).value = "Parent_Instance";
if (auto* renderer = s.world.findInChildren<sre::scene::MeshRenderer>(parent))
{
const auto material = s.assets.cloneMaterial(s.assets.getMesh(renderer->mesh).primitives[0].material);
s.assets.editMaterial(material).baseColorFactor = {0, 0, 1, 1};
renderer->materials = {material}; // like Unity's renderer.material: a per-instance copy
}
const auto child = instantiate(s.world, s.assets, prefab, {}, {.translation = {2, 0, 0}});
s.world.edit<sre::scene::Name>(child).value = "Child_Instance";
s.world.setLocalScale(child, {0.5F, 0.5F, 0.5F});
s.world.setParent(child, parent, ParentMode::KeepWorld); // Unity SetParent default
s.world.setLocalPosition(child, Vector3::k_Zero);
auto& pbr = s.addTechnique<PbrTechnique>(context.device, *s.renderScene, PbrOptions{.flavor = context.shaderFlavor,
.drawList = {.order = DrawOrder::ByMesh}, .extent = context.extent});
context.recipe.addRasterPass("forward").colorTarget(context.swapchainColor, getSceneClearColor())
.depthTarget(context.swapchainDepth, sre::frame::clearDepth(1.0F)).draw(pbr, sre::draw::BatchedDraw{});
return s;
}
Existing vessels rewritten. None needs scene-specific engine code.
gfx.sre.20):
buildRecipe instantiates the prefab under a turntable entity.computeWorldBounds, computed once.onStep sets the turntable rotation to 2π × fraction.SreVessel::addDefaultCameraAndLight().gfx.sre.1, .2, .110, .111, .120, .121):
media/common/gfx/scenes/*.scene (open question 3).PbrTechnique with DirectDraw; the indirect variant uses IndirectPbrTechnique with IndirectDraw.AnimationPlayer with no per-test code.setActiveCamera(world.find(root, "...")), or by the loader's ordered camera list prefab.cameras[i] for index-based TCDs.gfx.sre.5): addMesh(makeMeshData(asset.makeMesh(0))), then one entity with a MeshRenderer. onStep writes the torus transform. The camera entity is created in buildRecipe. The technique uses ShellDraw{500}.gfx.sre.3):
registerImageTexture("furryOrbTexture").ShellDraw{step % 150}.gfx.sre.4, .11, .12): a technique per layer with matching filters. Meshlets come from per-mesh ranges.Donut generality check:
deferred_shading:
addMesh and addMaterial with a texture.CubeNode (with MeshRenderer) and its child Sun (with Light, directional).DirectDraw, then a lighting compute pass added to the frame graph directly, which SRE already supports.bindless_rendering:
DrawBatch/DrawRecord stream and set 3 (Materials). Its push constant int2(instance, geometry) is exactly DrawRecord.KeepWorld (the default, as in Unity) recomputes local = inverse(parentWorld) × world and decomposes it into TRS. If the result is not representable as TRS (shear from non-uniform scale under rotation), the local is stored in Transform::matrix with TRS reset, and a debug log is written.KeepLocal leaves the local untouched.parent makes the entity a root, appended last.structureVersion is bumped, and there is no orphaning option. To keep children, reparent them first.SkinnedMesh::joints and AnimationPlayer::targets. References to entities outside the subtree are kept as they are.Name is copied unchanged; names are not unique.Entity gets no automatic remapping. A remapEntities hook is optional and documented.get/edit/tryGet are valid only until the next structural change to that component pool; this is documented, and debug builds check it with the guard below.View or forEachInSubtree holds an iteration guard. Structural changes while one is live trigger a TNG_ASSERT in debug builds and are undefined in release.edit are allowed.unordered_map iteration feeds output.StepTime only; there are no wall-clock or thread-pool-dependent results (texture transcoding stays parallel, but each result lands in its own fixed slot).RenderScene writes its full state on the first step.sync() throws std::length_error naming the exceeded capacity. Asset additions after freezeResources() throw std::logic_error.scene_conformance move to checkAssets(const AssetLibrary&). RenderScene runs it at construction, and a debug-only checkScene(const Scene&) covers hierarchy invariants.entity.h, components.h, scene.h/.cpp, scene_systems (animation, bounds), with unit tests. No render changes.scene_types.h, a copy of the contract types plus SkinData/AnimationClip, at version 2.scene_contract.h stays temporarily and aliases the shared types.RenderScene:
BatchedDraw, ShellDraw, and an IndirectDraw(DrawList) overload.Recipe::build() records RenderScene updates.GpuScene and techniques stay.sre_pbr_indirect (SPIR-V only, as today):
IndirectPbrTechnique to RenderScene and switch the food-store and mountain-range indirect vessels.gfx.sre.110, .111, .120 and .121..pipe files, CMake and device entries, regenerated .sp3/TOML for 4 generations, and new goldens:
gfx.sre.20; Pbr);gfx.sre.1);gfx.sre.2);gfx.sre.11; Pbr plus PnTriangles);gfx.sre.12; Meshlet);gfx.sre.5; Hair, ShellDraw);gfx.sre.3; Pbr, Fur, render-to-texture);gfx.sre.4; Caustic, StressTess, Spotlights, 4× MSAA).GltfSceneSource, IndirectSceneSource, ProceduralSceneSource, GpuScene/GpuGroup/DrawItem, scene_conformance, Stress3dUniforms's NodeUbo path, InstancedDraw, the ISceneSource/FrameState part of scene_contract.h, and SreVessel::loadGltfSource/loadIndirectSource.scene_contract_test.cpp with scene_types_test.cpp, which guards v2 the same way.procedural_scene_source_test.cpp and scene_conformance_test.cpp, replaced by the step 1-3 tests.Goldens and CRCs expected to change (all gfx.sre.*; new CSVs under engine/gfx/graphics_core/test/composite/reference/, e.g. gfx-sre.csv, gfx-sre-hairmark.csv, gfx-sre-hairmark_prbs.csv, gfx-sre-indirect-crc32.csv, gfx-sre-indirect-hhash.csv; TCDs repointed):
gfx.sre.1 and .2 (food store, mountain range; spirv and elf, 4K and 1080p):
firstInstance, material SSBO plus texture array;gfx.sre.3 (furry orb): all of the above, plus the fur shell push-constant path and the render-to-texture floor as a material instance.gfx.sre.4 (pet cabaret): the Caustic, Tessellation and Spotlights interfaces; joints move from a UBO to an SSBO.gfx.sre.5 (hairmark, every resolution and PRBS case): hair moves from SceneUbo.model to the instance SSBO plus a push constant; the camera becomes an entity.gfx.sre.11 (stress tess): Pbr and PnTriangles interfaces; the scene set's stage mask is fixed.gfx.sre.12 (dragon): per-mesh meshlet ranges replace the cumulative pool, and the instance transform now applies.gfx.sre.20 (water bottle): the spin moves from SceneUbo.model to a turntable entity, and the matrix order of operations changes.gfx.sre.110, .111, .120, .121:
.scene file may move from scenes_indirect/ to scenes/ (open question 3);gfx.stress3d.*, gfx.indirect.*, gfx.gfxoff, gfx.harvest.3 and the cube tests, plus their CSV files.dump_image.when = "Always" on hardware are compared visually against the legacy dumps. Visual differences must be explained by the intentional fixes above.engine/gfx/support/sre/test/unit/software/, listed in its CMakeLists):
scene_hierarchy_test: create and destroy a subtree, sibling order, roots, pre-order, stale handle detection, deterministic index reuse.scene_transform_test: world propagation, lazy getWorldMatrix, dirty subtree only, setWorldMatrix.scene_reparent_test: KeepWorld and KeepLocal, the shear fallback, cycle rejection.scene_clone_test: deep clone, shared resource ids, internal reference remap (joints, animation targets), outside references kept.scene_component_test: add, remove, get, tryGet, edit dirty bits, views in ascending order, user component types, the iteration guard (death test in debug).asset_library_test: dedup by glTF index and by path; cloneMaterial; freezeResources throwing.gltf_loader_test: a fixture with two nodes sharing a mesh gives one MeshData; layers per scene; defaults.animation_test: sampling at step fractions; bindings after clone.draw_list_test: no Vulkan needed. Test buildDrawLists() (a pure function) for Hierarchy versus ByMesh order, batch merging, firstRecord offsets, indirect command contents, layer filters.render_scene_capacity_test: overflow messages.recipe_test, pbr_technique_test and indirect_pbr_technique_test to RenderScene fakes.RenderScene::getStats() and logged by the new dedup vessel:
geometryBytes must stay constant;instanceBytes must equal 80 × N;DirectDraw, primitives for BatchedDraw and IndirectDraw.buildDrawLists.gfx.sre.30: one scene (food store plus 3 extra clones of a vehicle subtree) with four cases.
DirectDraw with ByMesh order;BatchedDraw;IndirectDraw;DirectDraw with Hierarchy order (opaque only).DrawList data, so the outputs must be byte-identical.Hierarchy-order case is allowed only on opaque content with LessOrEqual depth and no coplanar ties. If it differs, it gets its own column and is excluded from the equality claim.pbr.frag and indirect pbr_indirect.frag shade differently. The plan keeps two techniques on one scene. Merging into one technique would remove another shader set but changes the look of one family.RenderScene exists, because the frame graph is one-shot and uploads would overlap with in-flight steps. Is loading new glTF files mid-run needed? It would require FrameGraph reset and rebuild support plus a fence-safe upload path..scene per model: move the gfx.sre.11x and 12x TCDs from scenes_indirect/ to scenes/ (same glTF source; this halves asset loads). If the two generator outputs differ in content, keep both.k_MaxTextureCount, together with the .pipe dword counts.disassemble_elf is not in the repo. Please confirm the amdllpc version and flags that match the existing .sp3 headers, so the 4 generations are reproducible.render.capacity.*)?scene_contract.h, even though no code of theirs exists.
comments (0)