Rendering · guide
Render quality presets
The quality waterfall behind the Standard Frame's tiers — engine defaults, a project's RenderQuality.vxpreset and per-document overrides, folded per parameter into the numbers the expansion consumes.
Edit this page on GitHubDocuments
What it is#
RenderQualityAsset is a project's RenderQuality.vxpreset: per-tier overrides of the engine's
quality table, doc 39's scalability layer as an asset rather than an ini. Four tiers — Low, Medium,
High, Epic (QualityTier, which lives beside the compositor schema because the *host* states it) —
each carrying up to nine groups of knobs: ResolutionQuality, ShadowQuality,
GlobalIlluminationQuality, ReflectionQuality, PostFidelityQuality, LightQuality,
GeometryQuality, VegetationQuality and TextureQuality. Every field is nullable on the volume
model's terms: a tier
names only what it overrides, and an unset field falls through the waterfall — an unset field is
not a zero, exactly as on PostProcessSettings.
The waterfall is Unreal's, as assets: RenderQuality.EngineDefaults (the engine's own complete
table, expressed as this same asset type) → the project's preset → a per-document overlay on
StandardFrameAsset.Preset — folded per parameter by RenderQuality.Resolve, whose product is
ResolvedQuality, the flat struct of decided numbers the Standard Frame expansion consumes. Which
tier's column is read is the document's quality:, or, when the document declines, the platform's
pick in GraphicsOptions.Quality.
What it is for#
Everything quality-shaped lives in this one asset, and the boundary with the look profile is one
rule: look changes the intent, quality changes only the fidelity and cost of the same intent. Bloom
threshold is look; bloom pyramid levels are quality. DoF aperture is the camera's; DoF sample count
is quality. A runtime settings screen becomes one assignment — GraphicsOptions.Quality — with no
document edits, and a project that thinks Epic's shadows are still too coarse overrides exactly
cascadeResolution in exactly the Epic tier and inherits every sibling number from the engine.
The fold refuses a hole by name rather than reading it as zero: the engine layer is complete by construction, so a resolved field can never be an accidental default — a zero here would be a shadow distance of nothing and an atlas of no tiles, silently.
Using it#
The host that constructs PostEffectFactory hands it the loaded project preset; the platform's
tier travels through CompositorBuilder.Quality, which AppGraphics sets from
GraphicsOptions.Quality:
options.Quality = QualityTier.Medium; // the platform's pickoptions.Factories.Add(new PostEffectFactory { Preset = projectPreset });Resolve is a pure function and takes assets, not addresses — resolving an address from inside the
document transform would put content IO inside a build that must stay pure. Loading the
.vxpreset by address on the host's behalf is a later increment; until then the host loads it and
hands the asset over.
Some entries are carried, not yet consumed, and say so on their doc comments (DfaoSamples,
SurfaceCacheSize, TraceScale, the LightQuality capacities, VirtualGeometry, LodBias and
TextureQuality.ParticleBudgetScale): they map to systems the compositor does not construct today,
and they land in the asset first so a project's tiers do not change shape when their consumers
learn to read them.
TextureQuality's other two are consumed on the vegetation's terms, by the same host and from the
same single fold: streamingPoolMegabytes and mipBias become WorldRenderer.Textures, sized
before the texture source is mounted because a pool that could be resized afterwards would not be a
budget.
VegetationQuality is consumed, by hand-off rather than by reference. Vixen.Rendering.Terrain
cannot see this assembly — the dependency runs the other way — so TerrainFactory declares its own
plain-numbered TerrainVegetationQuality, and AppGraphics folds a resolved tier into
TerrainFactory.Vegetation for every terrain factory it finds in GraphicsOptions.Factories. That
fold is the hand-off: registering the factory is still the whole installation, and a game that
filled the budgets itself is left alone. All seven entries land — the two density scales, the two
cull scales, the grass and foliage cell counts and the near range — plus
terrainStreamingMegabytes, which becomes TerrainStreamer's byte budget.
A !Terrain node may state any of those numbers directly, per field, and a written value out-votes
the factory's tier while its siblings still fall through:
- !Terrain name: Ground foliageCellBudget: 96 # this document has decided # every other budget is the host's resolved tier⚠ A knob added to VegetationQuality is wired only when both halves are added:
TerrainVegetationQuality must grow the same field *and* AppGraphics' fold must assign it.
Either one missing and the number is carried the whole length of the waterfall and dropped at the
last step, which is what happened to the foliage budgets. TerrainNodeTests checks the two records
against each other so the omission fails a test rather than a frame.
The terrain fold sees the whole waterfall, a !StandardFrame's own quality: and inline preset:
included. It has to be read off the document *before* the build: the expansion replaces the frame
node as the compositor builds, so a host asking afterwards would be asking a document that no
longer says anything — which is why, for a while, a frame naming its own tier moved the post chain
and not the ground. PostEffectFactory.QualityOf(document, fallback, project) is that reading, and
it is the same fold the expansion performs rather than a second one that agrees today. The
!Terrain node's own scalars are a further document-level vote and out-vote the result per field.
GrassBladesPerCell exists on the terrain-side record and in no tier: it is the scatter dispatch's
shape rather than a budget, so a document or the game sets it.
Examples#
A project preset that sharpens High's sun and drops Low to a three-quarter render scale:
# RenderQuality.vxpresethigh: !QualityTierOverrides shadows: !ShadowQuality { cascadeResolution: 4096 }low: !QualityTierOverrides resolution: !ResolutionQuality { renderScale: 0.75 }Resolving and reading the fold in code — what the expansion does internally, and what a test asserts against:
using Vixen.Rendering.Compositor;using Vixen.Rendering.PostFx;public static class QualityFold { public static ResolvedQuality ForHigh() { var project = new RenderQualityAsset { High = new() { Shadows = new() { CascadeResolution = 4096 } } }; // The override lands; every sibling falls through to the engine's High column. return RenderQuality.Resolve(QualityTier.High, project); }}See also#
- The Standard Frame — the node whose expansion consumes the resolved
- numbers, and the
quality:/preset:knobs that pick the column and stack the top layer. - The post-processing node kinds — the nodes the fidelity group's numbers
- land on.
docs/plan/39-standard-frame-and-render-presets.md— the design, and the waterfall's Unreal and- Unity ancestry.