render: apply MSAA and shadow map resolution changes live
This commit is contained in:
parent
d5a9c80260
commit
cb14799e47
3 changed files with 108 additions and 40 deletions
|
|
@ -86,9 +86,10 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_debug, 0, "Skate 3",
|
||||||
REXCVAR_DEFINE_INT32(skate3_native_render_scene_msaa, 4, "Skate 3",
|
REXCVAR_DEFINE_INT32(skate3_native_render_scene_msaa, 4, "Skate 3",
|
||||||
"MSAA sample count for the native scene (1 = off, 2/4/8). Distant "
|
"MSAA sample count for the native scene (1 = off, 2/4/8). Distant "
|
||||||
"thin geometry (railings, wires) shimmers without it; mipmaps only "
|
"thin geometry (railings, wires) shimmers without it; mipmaps only "
|
||||||
"fix texture aliasing.")
|
"fix texture aliasing. Applies live: the scene pipeline family and "
|
||||||
|
"MSAA targets rebuild on change.")
|
||||||
.range(1, 8)
|
.range(1, 8)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_world_v2_tan_sign, 1.0, "Skate 3",
|
REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_world_v2_tan_sign, 1.0, "Skate 3",
|
||||||
"World-shading v2: polarity relating the stored frame's "
|
"World-shading v2: polarity relating the stored frame's "
|
||||||
"cross(binormal, normal) x handedness to the game's tangent "
|
"cross(binormal, normal) x handedness to the game's tangent "
|
||||||
|
|
@ -650,9 +651,10 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_shadow_tile, 0, "Skate 3",
|
||||||
"original-console look, blocky up close. 0 = auto: 512 x "
|
"original-console look, blocky up close. 0 = auto: 512 x "
|
||||||
"the render resolution scale (the Resolution Scale "
|
"the render resolution scale (the Resolution Scale "
|
||||||
"setting), matching the emulated renderer's shadow "
|
"setting), matching the emulated renderer's shadow "
|
||||||
"crispness at any render resolution.")
|
"crispness at any render resolution. Applies live: the "
|
||||||
|
"atlas chain recreates on change.")
|
||||||
.range(0, 4096)
|
.range(0, 4096)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_shadow_caster_parity, true,
|
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_shadow_caster_parity, true,
|
||||||
"Skate 3",
|
"Skate 3",
|
||||||
"Character pieces cast dynamic shadows only when the game "
|
"Character pieces cast dynamic shadows only when the game "
|
||||||
|
|
@ -696,9 +698,9 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_shadow_static_size, 4096,
|
||||||
"Static sun-shadow map resolution per cascade tile "
|
"Static sun-shadow map resolution per cascade tile "
|
||||||
"(the map is three tiles). At the default radius the "
|
"(the map is three tiles). At the default radius the "
|
||||||
"inner cascade gets ~1.5 cm texels, mid ~4 cm, far "
|
"inner cascade gets ~1.5 cm texels, mid ~4 cm, far "
|
||||||
"~9 cm.")
|
"~9 cm. Applies live: the map recreates on change.")
|
||||||
.range(1024, 8192)
|
.range(1024, 8192)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_shadow_pcss, true, "Skate 3",
|
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_shadow_pcss, true, "Skate 3",
|
||||||
"Contact-hardening soft shadows (PCSS): a blocker search "
|
"Contact-hardening soft shadows (PCSS): a blocker search "
|
||||||
"estimates the caster distance per pixel and the filter "
|
"estimates the caster distance per pixel and the filter "
|
||||||
|
|
|
||||||
|
|
@ -2420,9 +2420,9 @@ bool EnsureRootSignature(const NativeGuestOutputRenderContext& context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MSAA level selection + the main scene PSO and its culling/blend/depth
|
// The main scene PSO and its culling/blend/depth variants (cull-back
|
||||||
// variants (cull-back sheets, transparent, entity fade, hair 2-pass,
|
// sheets, transparent, entity fade, hair 2-pass, no-depth, outline mask),
|
||||||
// no-depth, outline mask).
|
// built at the g_r.msaa sample count EnsurePipeline selected.
|
||||||
bool EnsureScenePsoFamily(const NativeGuestOutputRenderContext& context) {
|
bool EnsureScenePsoFamily(const NativeGuestOutputRenderContext& context) {
|
||||||
nrhi::Device* device = context.device;
|
nrhi::Device* device = context.device;
|
||||||
// Scene color format: the float HDR intermediate when the HDR post chain
|
// Scene color format: the float HDR intermediate when the HDR post chain
|
||||||
|
|
@ -2431,16 +2431,10 @@ bool EnsureScenePsoFamily(const NativeGuestOutputRenderContext& context) {
|
||||||
const nrhi::Format scene_fmt = g_r.hdr_active
|
const nrhi::Format scene_fmt = g_r.hdr_active
|
||||||
? g_r.hdr_scene_format
|
? g_r.hdr_scene_format
|
||||||
: context.guest_output->format();
|
: context.guest_output->format();
|
||||||
// MSAA level: the requested count, reduced to what the scene color format
|
// g_r.msaa is selected by EnsurePipeline (which triggers this rebuild on
|
||||||
// supports (1 disables and renders directly into the 1x target). The
|
// change).
|
||||||
// RHI walks the requested count down in powers of two, exactly like the
|
|
||||||
// old CheckFeatureSupport loop.
|
|
||||||
const int32_t req = REXCVAR_GET(skate3_native_render_scene_msaa);
|
|
||||||
uint32_t msaa = req >= 8 ? 8u : req >= 4 ? 4u : req >= 2 ? 2u : 1u;
|
|
||||||
msaa = device->GetSupportedSampleCount(scene_fmt, msaa);
|
|
||||||
g_r.msaa = msaa;
|
|
||||||
|
|
||||||
// The family rebuilds on HDR toggles / output-format changes: retire the
|
// The family rebuilds on HDR/MSAA toggles / output-format changes: retire the
|
||||||
// previous pipelines (in-flight frames keep them alive via the deferred
|
// previous pipelines (in-flight frames keep them alive via the deferred
|
||||||
// destruction queue).
|
// destruction queue).
|
||||||
for (nrhi::Pipeline** p :
|
for (nrhi::Pipeline** p :
|
||||||
|
|
@ -3189,6 +3183,38 @@ bool EnsurePhotoFxPipeline(const NativeGuestOutputRenderContext& context) {
|
||||||
// Shadow atlas targets + the always-bound b1 receiver constant buffer.
|
// Shadow atlas targets + the always-bound b1 receiver constant buffer.
|
||||||
bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
||||||
nrhi::Device* device = context.device;
|
nrhi::Device* device = context.device;
|
||||||
|
// Cascade tile size: explicit, or auto = the game's 512 tiles at the
|
||||||
|
// RENDER resolution scale. guest_output is the scaled frontbuffer (720p x
|
||||||
|
// the draw resolution scale, independent of window/monitor size), so
|
||||||
|
// deriving from its height follows the Resolution Scale setting, including
|
||||||
|
// any device-limit clamping the texture cache applied, and gives the same
|
||||||
|
// effective shadow raster the emulated GPU renders at that scale.
|
||||||
|
const int32_t tile_cfg = REXCVAR_GET(skate3_native_render_scene_shadow_tile);
|
||||||
|
const uint32_t want_tile =
|
||||||
|
tile_cfg > 0
|
||||||
|
? uint32_t(tile_cfg)
|
||||||
|
: std::min(512u * std::max(1u, (context.guest_output_height + 719u) /
|
||||||
|
720u),
|
||||||
|
4096u);
|
||||||
|
if (g_r.shadow_raw != nullptr && g_r.shadow_tile != want_tile) {
|
||||||
|
// Hot tile-size change: retire the atlas chain; recreated below. The
|
||||||
|
// new atlas starts in RENDER_TARGET state and is re-rendered by this
|
||||||
|
// frame's shadow pass.
|
||||||
|
nrhi::Texture** targets[3] = {&g_r.shadow_raw, &g_r.shadow_mid,
|
||||||
|
&g_r.shadow_final};
|
||||||
|
nrhi::TextureView** views[3] = {&g_r.shadow_srv_raw, &g_r.shadow_srv_mid,
|
||||||
|
&g_r.shadow_srv_final};
|
||||||
|
for (int t = 0; t < 3; ++t) {
|
||||||
|
if (*views[t] != nullptr) {
|
||||||
|
device->DestroyDeferred(*views[t]);
|
||||||
|
*views[t] = nullptr;
|
||||||
|
}
|
||||||
|
if (*targets[t] != nullptr) {
|
||||||
|
device->DestroyDeferred(*targets[t]);
|
||||||
|
*targets[t] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!g_r.shadow_raw && REXCVAR_GET(skate3_native_render_scene_shadows)) {
|
if (!g_r.shadow_raw && REXCVAR_GET(skate3_native_render_scene_shadows)) {
|
||||||
// Dynamic-shadow atlas chain: raw casters -> hblur intermediate ->
|
// Dynamic-shadow atlas chain: raw casters -> hblur intermediate ->
|
||||||
// blurred final (the texture the scene pass samples). Three fixed-size
|
// blurred final (the texture the scene pass samples). Three fixed-size
|
||||||
|
|
@ -3196,22 +3222,7 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
||||||
// half-float ulp at the typical ~0.85 depth is ~6 mm of world height,
|
// half-float ulp at the typical ~0.85 depth is ~6 mm of world height,
|
||||||
// too coarse for board/feet-height casters 1-2 cm off the ground),
|
// too coarse for board/feet-height casters 1-2 cm off the ground),
|
||||||
// 3 tiles of tile x tile each.
|
// 3 tiles of tile x tile each.
|
||||||
const int32_t tile_cfg = REXCVAR_GET(skate3_native_render_scene_shadow_tile);
|
g_r.shadow_tile = want_tile;
|
||||||
if (tile_cfg > 0) {
|
|
||||||
g_r.shadow_tile = uint32_t(tile_cfg);
|
|
||||||
} else {
|
|
||||||
// Auto: the game's 512 tiles at the RENDER resolution scale.
|
|
||||||
// guest_output is the scaled frontbuffer (720p x the draw resolution
|
|
||||||
// scale, independent of window/monitor size), so deriving from its
|
|
||||||
// height follows the Resolution Scale setting, including any
|
|
||||||
// device-limit clamping the texture cache applied, and gives the
|
|
||||||
// same effective shadow raster the emulated GPU renders at that
|
|
||||||
// scale. Resolved once at atlas creation (the cvar is
|
|
||||||
// restart-scoped).
|
|
||||||
const uint32_t scale =
|
|
||||||
std::max(1u, (context.guest_output_height + 719u) / 720u);
|
|
||||||
g_r.shadow_tile = std::min(512u * scale, 4096u);
|
|
||||||
}
|
|
||||||
nrhi::TextureDesc desc;
|
nrhi::TextureDesc desc;
|
||||||
desc.width = g_r.shadow_tile * 3;
|
desc.width = g_r.shadow_tile * 3;
|
||||||
desc.height = g_r.shadow_tile;
|
desc.height = g_r.shadow_tile;
|
||||||
|
|
@ -3271,15 +3282,27 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
||||||
g_r.world_shadow_in_srv = false;
|
g_r.world_shadow_in_srv = false;
|
||||||
g_r.world_shadow_primed = false;
|
g_r.world_shadow_primed = false;
|
||||||
}
|
}
|
||||||
|
const uint32_t want_static_size = uint32_t(std::clamp(
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_shadow_static_size), 1024, 8192));
|
||||||
|
if (g_r.static_sun != nullptr && g_r.static_sun_size != want_static_size) {
|
||||||
|
// Hot size change: retire the map; recreated below. The recreated map
|
||||||
|
// is uninitialized, so force the cross-frame cache to re-render it
|
||||||
|
// (RenderStaticSunMap rebuilds whenever nsm_built_radius <= 0).
|
||||||
|
if (g_r.static_sun_srv != nullptr) {
|
||||||
|
device->DestroyDeferred(g_r.static_sun_srv);
|
||||||
|
g_r.static_sun_srv = nullptr;
|
||||||
|
}
|
||||||
|
device->DestroyDeferred(g_r.static_sun);
|
||||||
|
g_r.static_sun = nullptr;
|
||||||
|
g_r.nsm_built_radius = 0.0f;
|
||||||
|
}
|
||||||
if (!g_r.static_sun && g_r.shadow_raw != nullptr &&
|
if (!g_r.static_sun && g_r.shadow_raw != nullptr &&
|
||||||
REXCVAR_GET(skate3_native_render_scene_shadow_static_casters)) {
|
REXCVAR_GET(skate3_native_render_scene_shadow_static_casters)) {
|
||||||
// Native static sun-shadow map (see RendererState). THREE cascade
|
// Native static sun-shadow map (see RendererState). THREE cascade
|
||||||
// tiles side by side: inner (r/6, centimeter contact detail with
|
// tiles side by side: inner (r/6, centimeter contact detail with
|
||||||
// useful reach), mid (r/2) and far (full radius, large-caster
|
// useful reach), mid (r/2) and far (full radius, large-caster
|
||||||
// coverage); size is per tile, restart-scoped.
|
// coverage); size is per tile.
|
||||||
const int32_t size_cfg =
|
g_r.static_sun_size = want_static_size;
|
||||||
REXCVAR_GET(skate3_native_render_scene_shadow_static_size);
|
|
||||||
g_r.static_sun_size = uint32_t(std::clamp(size_cfg, 1024, 8192));
|
|
||||||
nrhi::TextureDesc desc;
|
nrhi::TextureDesc desc;
|
||||||
desc.width = g_r.static_sun_size * 3;
|
desc.width = g_r.static_sun_size * 3;
|
||||||
desc.height = g_r.static_sun_size;
|
desc.height = g_r.static_sun_size;
|
||||||
|
|
@ -3545,7 +3568,8 @@ bool EnsureOutputSizedTargets(const NativeGuestOutputRenderContext& context) {
|
||||||
g_r.hdr_active ? g_r.hdr_scene_format : nrhi::Format::kUnknown;
|
g_r.hdr_active ? g_r.hdr_scene_format : nrhi::Format::kUnknown;
|
||||||
if (!g_r.depth || g_r.depth_width != width || g_r.depth_height != height ||
|
if (!g_r.depth || g_r.depth_width != width || g_r.depth_height != height ||
|
||||||
g_r.targets_hdr != g_r.hdr_active ||
|
g_r.targets_hdr != g_r.hdr_active ||
|
||||||
g_r.targets_scene_fmt != want_scene_fmt) {
|
g_r.targets_scene_fmt != want_scene_fmt ||
|
||||||
|
g_r.targets_msaa != g_r.msaa) {
|
||||||
if (g_r.depth) {
|
if (g_r.depth) {
|
||||||
g_r.device->DestroyDeferred(g_r.depth);
|
g_r.device->DestroyDeferred(g_r.depth);
|
||||||
g_r.depth = nullptr;
|
g_r.depth = nullptr;
|
||||||
|
|
@ -3615,6 +3639,18 @@ bool EnsureOutputSizedTargets(const NativeGuestOutputRenderContext& context) {
|
||||||
vd.dimension = nrhi::ViewDimension::k2DMS;
|
vd.dimension = nrhi::ViewDimension::k2DMS;
|
||||||
g_r.msaa_srv_slot = device->CreateTextureView(g_r.msaa_color, vd);
|
g_r.msaa_srv_slot = device->CreateTextureView(g_r.msaa_color, vd);
|
||||||
g_r.msaa_srv_allocated = g_r.msaa_srv_slot != nullptr;
|
g_r.msaa_srv_allocated = g_r.msaa_srv_slot != nullptr;
|
||||||
|
} else {
|
||||||
|
// MSAA switched off: retire the multisample color target and its
|
||||||
|
// resolve view (the scene renders directly into the 1x target).
|
||||||
|
if (g_r.msaa_srv_slot) {
|
||||||
|
g_r.device->DestroyDeferred(g_r.msaa_srv_slot);
|
||||||
|
g_r.msaa_srv_slot = nullptr;
|
||||||
|
g_r.msaa_srv_allocated = false;
|
||||||
|
}
|
||||||
|
if (g_r.msaa_color) {
|
||||||
|
g_r.device->DestroyDeferred(g_r.msaa_color);
|
||||||
|
g_r.msaa_color = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1x float scene plane for the HDR post chain: the MSAA resolve
|
// 1x float scene plane for the HDR post chain: the MSAA resolve
|
||||||
|
|
@ -3654,6 +3690,7 @@ bool EnsureOutputSizedTargets(const NativeGuestOutputRenderContext& context) {
|
||||||
}
|
}
|
||||||
g_r.targets_hdr = g_r.hdr_active;
|
g_r.targets_hdr = g_r.hdr_active;
|
||||||
g_r.targets_scene_fmt = want_scene_fmt;
|
g_r.targets_scene_fmt = want_scene_fmt;
|
||||||
|
g_r.targets_msaa = g_r.msaa;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -3677,11 +3714,37 @@ bool EnsurePipeline(const NativeGuestOutputRenderContext& context) {
|
||||||
REXCVAR_GET(skate3_native_render_scene_hdr_packed)
|
REXCVAR_GET(skate3_native_render_scene_hdr_packed)
|
||||||
? nrhi::Format::kR11G11B10_FLOAT
|
? nrhi::Format::kR11G11B10_FLOAT
|
||||||
: nrhi::Format::kR16G16B16A16_FLOAT;
|
: nrhi::Format::kR16G16B16A16_FLOAT;
|
||||||
|
// MSAA level: the requested count, reduced to what the scene color format
|
||||||
|
// supports (1 disables and renders directly into the 1x target). The RHI
|
||||||
|
// walks the requested count down in powers of two, exactly like the old
|
||||||
|
// CheckFeatureSupport loop. Hot: a change rebuilds the pipeline family
|
||||||
|
// (the AO/SSR/volumetric passes and the MSAA targets latch on g_r.msaa
|
||||||
|
// and follow).
|
||||||
|
const nrhi::Format scene_fmt_want =
|
||||||
|
hdr_want ? hdr_fmt_want : context.guest_output->format();
|
||||||
|
const int32_t msaa_req = REXCVAR_GET(skate3_native_render_scene_msaa);
|
||||||
|
uint32_t msaa_want =
|
||||||
|
msaa_req >= 8 ? 8u : msaa_req >= 4 ? 4u : msaa_req >= 2 ? 2u : 1u;
|
||||||
|
msaa_want = device->GetSupportedSampleCount(scene_fmt_want, msaa_want);
|
||||||
if (!g_r.pso || g_r.rtv_format != context.guest_output->format() ||
|
if (!g_r.pso || g_r.rtv_format != context.guest_output->format() ||
|
||||||
g_r.hdr_active != hdr_want ||
|
g_r.hdr_active != hdr_want ||
|
||||||
(hdr_want && g_r.hdr_scene_format != hdr_fmt_want)) {
|
(hdr_want && g_r.hdr_scene_format != hdr_fmt_want) ||
|
||||||
|
g_r.msaa != msaa_want) {
|
||||||
|
if (g_r.msaa != msaa_want && g_r.pfx_ready) {
|
||||||
|
// The photo-postfx depth-pack pass is compiled against the depth
|
||||||
|
// buffer's sample count (PFX_MSAA variant); retire the chain's PSOs
|
||||||
|
// so the next photo-editor frame rebuilds them.
|
||||||
|
for (nrhi::Pipeline*& p : g_r.pfx_pso) {
|
||||||
|
if (p != nullptr) {
|
||||||
|
device->DestroyDeferred(p);
|
||||||
|
p = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_r.pfx_ready = false;
|
||||||
|
}
|
||||||
g_r.hdr_active = hdr_want;
|
g_r.hdr_active = hdr_want;
|
||||||
g_r.hdr_scene_format = hdr_fmt_want;
|
g_r.hdr_scene_format = hdr_fmt_want;
|
||||||
|
g_r.msaa = msaa_want;
|
||||||
if (!EnsureScenePsoFamily(context) || !EnsureResolvePso(context) ||
|
if (!EnsureScenePsoFamily(context) || !EnsureResolvePso(context) ||
|
||||||
!EnsureBlurPsos(context) || !EnsureOutlineEdgePso(context) ||
|
!EnsureBlurPsos(context) || !EnsureOutlineEdgePso(context) ||
|
||||||
!Ensure2dPso(context) || !EnsureSplinePsos(context) ||
|
!Ensure2dPso(context) || !EnsureSplinePsos(context) ||
|
||||||
|
|
|
||||||
|
|
@ -648,6 +648,9 @@ struct RendererState {
|
||||||
// Scene float format the output-sized targets were built with (kUnknown
|
// Scene float format the output-sized targets were built with (kUnknown
|
||||||
// when classic); packed-format toggles rebuild them.
|
// when classic); packed-format toggles rebuild them.
|
||||||
nrhi::Format targets_scene_fmt = nrhi::Format::kUnknown;
|
nrhi::Format targets_scene_fmt = nrhi::Format::kUnknown;
|
||||||
|
// MSAA level the output-sized targets were built with; hot MSAA changes
|
||||||
|
// rebuild the depth buffer and the multisample color target.
|
||||||
|
uint32_t targets_msaa = 0;
|
||||||
// Screen-space reflections (ssr.hlsl + scene.hlsl ps_refl_gbuf): a
|
// Screen-space reflections (ssr.hlsl + scene.hlsl ps_refl_gbuf): a
|
||||||
// half-res reflection G-buffer re-rendered from the frame's reflective
|
// half-res reflection G-buffer re-rendered from the frame's reflective
|
||||||
// items (env fams 5/6/13 + water), a half-res screen-space march over the
|
// items (env fams 5/6/13 + water), a half-res screen-space march over the
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue