render: graphics build-up showcase mode
This commit is contained in:
parent
38343207ff
commit
ccf84fade3
12 changed files with 15943 additions and 12638 deletions
|
|
@ -360,9 +360,21 @@ float4 ps_bloom_up(VSOut i) : SV_Target {
|
||||||
// upsample; the white fallback rides here with intensity 0 when bloom is
|
// upsample; the white fallback rides here with intensity 0 when bloom is
|
||||||
// off).
|
// off).
|
||||||
float4 ps_tonemap(VSOut i) : SV_Target {
|
float4 ps_tonemap(VSOut i) : SV_Target {
|
||||||
|
// Graphics build-up showcase (see scene.hlsl ShowcaseMask): sh_v2.y/z =
|
||||||
|
// 256 + the layer mask left/right of the split at sh_v2.w (output
|
||||||
|
// pixels), 0 = off. Post layers reveal per pixel by their mask bits:
|
||||||
|
// AO 16, volumetrics 64, bloom 128 (SSR gates in its own composite).
|
||||||
|
float scv = i.pos.x < sh_v2.w ? sh_v2.y : sh_v2.z;
|
||||||
|
int sc_mask = scv < 255.5 ? -1 : (int)(scv + 0.5) - 256;
|
||||||
float ao = ao_plane.SampleLevel(smp_linear, i.uv, 0);
|
float ao = ao_plane.SampleLevel(smp_linear, i.uv, 0);
|
||||||
|
if (sc_mask >= 0 && (sc_mask & 16) == 0) {
|
||||||
|
ao = 1.0;
|
||||||
|
}
|
||||||
float3 x = max(tex0.SampleLevel(smp_point, i.uv, 0).rgb, 0.0) * ao;
|
float3 x = max(tex0.SampleLevel(smp_point, i.uv, 0).rgb, 0.0) * ao;
|
||||||
float3 bloom = tex1.SampleLevel(smp_linear, i.uv, 0).rgb * p0.z;
|
float3 bloom = tex1.SampleLevel(smp_linear, i.uv, 0).rgb * p0.z;
|
||||||
|
if (sc_mask >= 0 && (sc_mask & 128) == 0) {
|
||||||
|
bloom = 0.0;
|
||||||
|
}
|
||||||
// Volumetric terms, applied AFTER the AO multiply (in-air light is not
|
// Volumetric terms, applied AFTER the AO multiply (in-air light is not
|
||||||
// subject to surface occlusion). The shaft plane carries the marched
|
// subject to surface occlusion). The shaft plane carries the marched
|
||||||
// SHADOWED-air term (see ps_vol_shafts) and dims MULTIPLICATIVELY;
|
// SHADOWED-air term (see ps_vol_shafts) and dims MULTIPLICATIVELY;
|
||||||
|
|
@ -376,7 +388,8 @@ float4 ps_tonemap(VSOut i) : SV_Target {
|
||||||
// across depth discontinuities) so shaft edges never bleed onto near
|
// across depth discontinuities) so shaft edges never bleed onto near
|
||||||
// silhouettes.
|
// silhouettes.
|
||||||
float shaft = 0.0;
|
float shaft = 0.0;
|
||||||
if (vs0.w > 0.0) {
|
bool sc_vol = sc_mask < 0 || (sc_mask & 64) != 0;
|
||||||
|
if (vs0.w > 0.0 && sc_vol) {
|
||||||
float dpix = lin_depth.SampleLevel(smp_point, i.uv, 0);
|
float dpix = lin_depth.SampleLevel(smp_point, i.uv, 0);
|
||||||
float2 vt = size.zw * 2.0; // one shaft-plane texel in uv
|
float2 vt = size.zw * 2.0; // one shaft-plane texel in uv
|
||||||
float2 base = (floor(i.uv / vt - 0.5) + 0.5) * vt;
|
float2 base = (floor(i.uv / vt - 0.5) + 0.5) * vt;
|
||||||
|
|
@ -400,7 +413,7 @@ float4 ps_tonemap(VSOut i) : SV_Target {
|
||||||
// value only; the game's own fog already renders the isotropic part,
|
// value only; the game's own fog already renders the isotropic part,
|
||||||
// so adding a floor here just veils the whole frame.
|
// so adding a floor here just veils the whole frame.
|
||||||
float3 haze = float3(0.0, 0.0, 0.0);
|
float3 haze = float3(0.0, 0.0, 0.0);
|
||||||
if (vs1.w > 0.0) {
|
if (vs1.w > 0.0 && sc_vol) {
|
||||||
float d = lin_depth.SampleLevel(smp_point, i.uv, 0);
|
float d = lin_depth.SampleLevel(smp_point, i.uv, 0);
|
||||||
float path = 1.0 - exp(-max(vs2.w, 0.0005) * d);
|
float path = 1.0 - exp(-max(vs2.w, 0.0005) * d);
|
||||||
float3 ray = normalize(ViewPos(i.uv, 1.0));
|
float3 ray = normalize(ViewPos(i.uv, 1.0));
|
||||||
|
|
@ -431,6 +444,15 @@ float4 ps_tonemap(VSOut i) : SV_Target {
|
||||||
// Dimming ceiling: shadowed air thins the light, it does not extinguish
|
// Dimming ceiling: shadowed air thins the light, it does not extinguish
|
||||||
// it; without the ceiling a fully shaded sun-facing view (under an
|
// it; without the ceiling a fully shaded sun-facing view (under an
|
||||||
// overpass) saturated to a solid black void.
|
// overpass) saturated to a solid black void.
|
||||||
return float4(
|
float3 outc =
|
||||||
ToneMapScene((x + haze) * (1.0 - 0.55 * saturate(shaft)) + bloom), 1.0);
|
ToneMapScene((x + haze) * (1.0 - 0.55 * saturate(shaft)) + bloom);
|
||||||
|
// Showcase divider: a soft white line at the split while a wipe is in
|
||||||
|
// flight (the two sides show different stages).
|
||||||
|
if (sh_v2.w > 0.5 && abs(sh_v2.y - sh_v2.z) > 0.5) {
|
||||||
|
float half_w = max(size.x * 0.0012, 1.5);
|
||||||
|
float d = abs(i.pos.x - sh_v2.w);
|
||||||
|
outc = lerp(outc, float3(1.0, 1.0, 1.0),
|
||||||
|
saturate(1.0 - d / half_w) * 0.85);
|
||||||
|
}
|
||||||
|
return float4(outc, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,12 +135,30 @@ VSOut vs_main(float3 p : POSITION, float2 uv : TEXCOORD0, float2 uv2 : TEXCOORD1
|
||||||
: float4(mul(sb, (float3x3)world), bw.w);
|
: float4(mul(sb, (float3x3)world), bw.w);
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
// ---- Graphics build-up showcase -------------------------------------------
|
||||||
|
// sh_v2.yzw carry the showcase split state: y/z = the LAYER MASK shown
|
||||||
|
// left/right of the vertical split at w (in output pixels), encoded as
|
||||||
|
// 256 + mask so an all-zero row (showcase off) is unambiguous. Mask bits
|
||||||
|
// (the contract shared with hdr.hlsl, ssr.hlsl and the CPU sequencer,
|
||||||
|
// see kShowcaseLayers in skate3_native_scene.h):
|
||||||
|
// 1 albedo textures 2 baked lighting 4 materials & surface detail
|
||||||
|
// 8 dynamic shadows 16 ambient occlusion 32 reflections (SSR)
|
||||||
|
// 64 volumetrics 128 bloom
|
||||||
|
// The material bits are progressive looks (materials subsumes lighting
|
||||||
|
// subsumes albedo); the rest are independent, so the sequencer can reveal
|
||||||
|
// them in any order or grouping. Returns -1 when the showcase is off.
|
||||||
|
int ShowcaseMask(float px_x) {
|
||||||
|
float v = px_x < sh_v2.w ? sh_v2.y : sh_v2.z;
|
||||||
|
return v < 255.5 ? -1 : (int)(v + 0.5) - 256;
|
||||||
|
}
|
||||||
#include "scene_shadows.hlsli"
|
#include "scene_shadows.hlsli"
|
||||||
#include "scene_common.hlsli"
|
#include "scene_common.hlsli"
|
||||||
#include "scene_char.hlsli"
|
#include "scene_char.hlsli"
|
||||||
#include "scene_dynobj.hlsli"
|
#include "scene_dynobj.hlsli"
|
||||||
#include "scene_water.hlsli"
|
#include "scene_water.hlsli"
|
||||||
// The full material shading for every family (ps_main forwards here).
|
// The full material shading for every family (see ps_main below; the
|
||||||
|
// showcase wrapper substitutes the early build-up stages' looks over this
|
||||||
|
// result's rgb while keeping its alpha).
|
||||||
float4 ShadePixel(VSOut i) {
|
float4 ShadePixel(VSOut i) {
|
||||||
if (tint.a > 0.0) {
|
if (tint.a > 0.0) {
|
||||||
return float4(PassGamma(tint.rgb), tint.a);
|
return float4(PassGamma(tint.rgb), tint.a);
|
||||||
|
|
@ -679,7 +697,88 @@ float4 ShadePixel(VSOut i) {
|
||||||
return float4(PassGamma(lit), 1.0);
|
return float4(PassGamma(lit), 1.0);
|
||||||
}
|
}
|
||||||
float4 ps_main(VSOut i) : SV_Target {
|
float4 ps_main(VSOut i) : SV_Target {
|
||||||
return ShadePixel(i);
|
float4 c = ShadePixel(i);
|
||||||
|
int mask = ShowcaseMask(i.pos.x);
|
||||||
|
if (mask < 0 || (mask & 4) != 0) {
|
||||||
|
return c; // showcase off, or the full material layer is revealed
|
||||||
|
}
|
||||||
|
// Pre-material build-up looks replace the shaded rgb but keep the
|
||||||
|
// branch's own alpha, so alpha tests, coverage channels, fades and blend
|
||||||
|
// routing all behave exactly as in the full render. The gamma-space
|
||||||
|
// looks encode through PassGamma like the legacy branches; the exact-
|
||||||
|
// chain lighting look goes through ToneOut. The sky dome (exact family
|
||||||
|
// -40 / the legacy unlit marker) is authored content; it arrives
|
||||||
|
// complete with the lighting layer and never receives cast shadows.
|
||||||
|
bool sky = (cam_pos.w < -39.5 && cam_pos.w > -40.5) || tint.b > 0.0;
|
||||||
|
if (sky && (mask & 2) != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
float3 n = dot(i.nrm, i.nrm) > 0.01
|
||||||
|
? normalize(i.nrm)
|
||||||
|
: normalize(cross(ddx(i.rpos), ddy(i.rpos)));
|
||||||
|
float3 ldir = dot(sh_sun.xyz, sh_sun.xyz) > 0.1
|
||||||
|
? sh_sun.xyz
|
||||||
|
: normalize(float3(0.4, 0.8, 0.3));
|
||||||
|
// The dynamic-shadow layer applies to every pre-material look (the
|
||||||
|
// helpers self-gate on the mask, so uncovered frames sample lit).
|
||||||
|
float s = 1.0;
|
||||||
|
if (!sky && (mask & 8) != 0) {
|
||||||
|
s = min(SampleCsmShadowSoft(i.rpos + cam_pos.xyz,
|
||||||
|
tint.g > 0.0 ? 0.012 : 0.0, i.nrm, i.pos.xy),
|
||||||
|
SampleStaticSun(i.rpos + cam_pos.xyz, i.nrm, i.pos.xy));
|
||||||
|
}
|
||||||
|
// Base color: the diffuse texture once the albedo layer is revealed,
|
||||||
|
// clay grey before it.
|
||||||
|
float3 base = (mask & 1) != 0 ? diffuse.Sample(smp, i.uv).rgb
|
||||||
|
: float3(0.62, 0.62, 0.62);
|
||||||
|
if ((mask & 2) != 0 && tint.r > 0.0 && sh_sun.w > 0.01) {
|
||||||
|
// Baked lighting on a lightmapped surface: the REAL diffuse chain,
|
||||||
|
// lightmap^2 x the flat-map kd x fog/exposure/material multiplier,
|
||||||
|
// the exact-family formula minus surface detail, so the later step
|
||||||
|
// to full materials only adds normal/detail maps, specular,
|
||||||
|
// reflections and weathering instead of also re-grading the frame's
|
||||||
|
// tone. The shadow layer joins with the game's own lightmap min-clamp.
|
||||||
|
float3 lm = lightmap.SampleLevel(smp_clamp, i.uv2, 0.0).rgb;
|
||||||
|
float3 lml = lm * lm;
|
||||||
|
if ((mask & 8) != 0) {
|
||||||
|
lml = min(lml, s + sh_color.rgb);
|
||||||
|
}
|
||||||
|
float3 lin = lml * 0.93429 * base * base;
|
||||||
|
float fdist = length(i.rpos);
|
||||||
|
float f1 = saturate(fdist * sh_fogp.x + sh_fogp.y);
|
||||||
|
if (sh_fogp.z != 1.0) {
|
||||||
|
f1 = pow(max(f1, 1e-6), sh_fogp.z);
|
||||||
|
}
|
||||||
|
float3 xe = (lin * ((1.0 + sh_fogc.a * f1) * sh_env.x) +
|
||||||
|
sh_fogc.rgb * f1) * sh_sun.w;
|
||||||
|
return ToneOut(xe, c.a, false);
|
||||||
|
}
|
||||||
|
float3 col;
|
||||||
|
if ((mask & 2) != 0) {
|
||||||
|
// Lighting without exact frame rows: the legacy-branch convention
|
||||||
|
// (lightmap x2 in gamma space), or a neutral wrapped lambert for
|
||||||
|
// surfaces without a lightmap (characters, props).
|
||||||
|
float3 lite;
|
||||||
|
if (tint.r > 0.0) {
|
||||||
|
lite = lightmap.SampleLevel(smp_clamp, i.uv2, 0.0).rgb * 2.0;
|
||||||
|
} else {
|
||||||
|
float wrap = 0.35 + 0.65 * saturate(dot(n, ldir));
|
||||||
|
lite = float3(wrap, wrap, wrap);
|
||||||
|
}
|
||||||
|
col = base * lite;
|
||||||
|
} else if ((mask & 1) != 0) {
|
||||||
|
col = base; // raw diffuse, unlit
|
||||||
|
} else {
|
||||||
|
// Clay: grey with a simple sun-lambert so the geometry reads (a fully
|
||||||
|
// flat grey collapses everything into silhouettes).
|
||||||
|
col = base * (0.61 + 0.68 * saturate(dot(n, ldir)));
|
||||||
|
}
|
||||||
|
// Shadow layer on the gamma-space looks: a soft darkening multiply (the
|
||||||
|
// exact-chain look above uses the game's own clamp instead).
|
||||||
|
if (!sky && (mask & 8) != 0) {
|
||||||
|
col *= s * 0.65 + 0.35;
|
||||||
|
}
|
||||||
|
return float4(PassGamma(saturate(col)), c.a);
|
||||||
}
|
}
|
||||||
// ---- SSR reflection G-buffer (consumed by ssr.hlsl ps_march) --------------
|
// ---- SSR reflection G-buffer (consumed by ssr.hlsl ps_march) --------------
|
||||||
// Rendered after the main pass from the frame's reflective items only (env
|
// Rendered after the main pass from the frame's reflective items only (env
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Shadow receivers: the game-exact CSM atlas sampler, its
|
// Shadow receivers: the game-exact CSM atlas sampler, its
|
||||||
// contact-hardening PCSS variant, and the native static sun-shadow map
|
// contact-hardening PCSS variant, and the native static sun-shadow map
|
||||||
// sampler. Expects the including file to declare the b1 rows
|
// sampler. Expects the including file to declare the b1 rows
|
||||||
// (scene_frame_cb.hlsli) and the shadow_atlas / static_sun textures
|
// (scene_frame_cb.hlsli), the shadow_atlas / static_sun textures and
|
||||||
// before this include.
|
// ShowcaseMask before this include.
|
||||||
// Native CSM atlas sample at a world position: finest covering cascade,
|
// Native CSM atlas sample at a world position: finest covering cascade,
|
||||||
// s = saturate(infront + 1 - coverage). Returns 1 (lit) when uncovered or
|
// s = saturate(infront + 1 - coverage). Returns 1 (lit) when uncovered or
|
||||||
// shadows are off. extra_bias suppresses receiver self-shadow acne on
|
// shadows are off. extra_bias suppresses receiver self-shadow acne on
|
||||||
|
|
@ -120,6 +120,11 @@ float PcssRotation(float2 px) {
|
||||||
}
|
}
|
||||||
float SampleCsmShadowSoft(float3 wp, float extra_bias, float3 nrm,
|
float SampleCsmShadowSoft(float3 wp, float extra_bias, float3 nrm,
|
||||||
float2 px) {
|
float2 px) {
|
||||||
|
// Showcase: until the shadow layer is revealed, render fully lit.
|
||||||
|
int sc_mask = ShowcaseMask(px.x);
|
||||||
|
if (sc_mask >= 0 && (sc_mask & 8) == 0) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
if (sh_pcss.x <= 0.5) {
|
if (sh_pcss.x <= 0.5) {
|
||||||
return SampleCsmShadow(wp, extra_bias);
|
return SampleCsmShadow(wp, extra_bias);
|
||||||
}
|
}
|
||||||
|
|
@ -235,6 +240,11 @@ float SampleStaticSun(float3 wp, float3 nrm, float2 px) {
|
||||||
if (nsm_p.x <= 0.0) {
|
if (nsm_p.x <= 0.0) {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
// Showcase: until the shadow layer is revealed, render fully lit.
|
||||||
|
int sc_mask = ShowcaseMask(px.x);
|
||||||
|
if (sc_mask >= 0 && (sc_mask & 8) == 0) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
float2 uc_far = float2(dot(nsm_x.xyz, wp) + nsm_x.w,
|
float2 uc_far = float2(dot(nsm_x.xyz, wp) + nsm_x.w,
|
||||||
dot(nsm_y.xyz, wp) + nsm_y.w);
|
dot(nsm_y.xyz, wp) + nsm_y.w);
|
||||||
// Screen-pixel footprint in far-tile uc, BEFORE any divergent return
|
// Screen-pixel footprint in far-tile uc, BEFORE any divergent return
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -38,7 +38,9 @@ cbuffer C : register(b0) {
|
||||||
float4 p0; // x = march steps, y = thickness (view units),
|
float4 p0; // x = march steps, y = thickness (view units),
|
||||||
// z = composite intensity, w = debug mode
|
// z = composite intensity, w = debug mode
|
||||||
float4 p1; // x = max ray distance (view units)
|
float4 p1; // x = max ray distance (view units)
|
||||||
float4 p2; // spare
|
float4 p2; // graphics build-up showcase: x = split position in output
|
||||||
|
// pixels, y/z = SSR visibility (0/1) left/right of the
|
||||||
|
// split (both 1 when the showcase is off)
|
||||||
};
|
};
|
||||||
// Per-pass t0/t1/t2 roles (each entry uses one of the overlapping
|
// Per-pass t0/t1/t2 roles (each entry uses one of the overlapping
|
||||||
// declarations; the rest drop out at compile):
|
// declarations; the rest drop out at compile):
|
||||||
|
|
@ -283,6 +285,11 @@ float4 ps_march(VSOut i) : SV_Target {
|
||||||
}
|
}
|
||||||
|
|
||||||
float4 ps_composite(VSOut i) : SV_Target {
|
float4 ps_composite(VSOut i) : SV_Target {
|
||||||
|
// Showcase gate: alpha 0 leaves the scene plane untouched on a side
|
||||||
|
// whose build-up stage has not revealed reflections yet.
|
||||||
|
if ((i.pos.x < p2.x ? p2.y : p2.z) < 0.5f) {
|
||||||
|
return float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
float4 g = tex1c.SampleLevel(smp_point, i.uv, 0);
|
float4 g = tex1c.SampleLevel(smp_point, i.uv, 0);
|
||||||
float wrefl = abs(g.a);
|
float wrefl = abs(g.a);
|
||||||
float dbg = p0.w;
|
float dbg = p0.w;
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,8 @@ extern const rex::PPCImageInfo eawebkit_PPCImageConfig;
|
||||||
// lift via config because they overlap with auto-discovered parent functions.
|
// lift via config because they overlap with auto-discovered parent functions.
|
||||||
extern "C" REX_FUNC(__restgprlr_19);
|
extern "C" REX_FUNC(__restgprlr_19);
|
||||||
|
|
||||||
|
// Defined in skate3_native_scene.cpp; toggled by the showcase hotkey below.
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_showcase);
|
||||||
// Defined in skate3_native_scene.cpp; the freecam hotkey below toggles it,
|
// Defined in skate3_native_scene.cpp; the freecam hotkey below toggles it,
|
||||||
// and while it captures input the guest input system is gated off.
|
// and while it captures input the guest input system is gated off.
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam);
|
||||||
|
|
@ -762,6 +764,12 @@ void Skate3BaseApp::OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) {
|
||||||
"Native render debug menu", [this] {
|
"Native render debug menu", [this] {
|
||||||
ToggleNativeDebug();
|
ToggleNativeDebug();
|
||||||
});
|
});
|
||||||
|
rex::ui::RegisterBind("bind_skate3_showcase", "Home",
|
||||||
|
"Graphics build-up showcase", [] {
|
||||||
|
REXCVAR_SET(
|
||||||
|
skate3_native_render_scene_showcase,
|
||||||
|
!REXCVAR_GET(skate3_native_render_scene_showcase));
|
||||||
|
});
|
||||||
rex::ui::RegisterBind("bind_skate3_freecam", "End",
|
rex::ui::RegisterBind("bind_skate3_freecam", "End",
|
||||||
"Drone camera (free fly)", [] {
|
"Drone camera (free fly)", [] {
|
||||||
REXCVAR_SET(
|
REXCVAR_SET(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
|
@ -84,10 +86,15 @@ REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_rate);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_amp);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_synthetic_pan_amp);
|
||||||
// SDK: emulated-draw suppression while the native output is active.
|
// SDK: emulated-draw suppression while the native output is active.
|
||||||
REXCVAR_DECLARE(bool, native_render_suppress_emulated_draws);
|
REXCVAR_DECLARE(bool, native_render_suppress_emulated_draws);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_showcase);
|
||||||
|
REXCVAR_DECLARE(double, skate3_native_render_scene_showcase_hold);
|
||||||
|
REXCVAR_DECLARE(double, skate3_native_render_scene_showcase_wipe);
|
||||||
|
REXCVAR_DECLARE(std::string, skate3_native_render_scene_showcase_order);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_freecam_speed);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_freecam_speed);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_freecam_look_speed);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_freecam_look_speed);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam_capture_input);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_freecam_capture_input);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_ssr);
|
||||||
|
|
||||||
namespace skate3 {
|
namespace skate3 {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -115,6 +122,118 @@ double CvarSlider(const char* label, double value, float lo, float hi,
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Showcase setup window ------------------------------------------------
|
||||||
|
// Editor for skate3_native_render_scene_showcase_order: one row per build-up
|
||||||
|
// layer, reorderable, each optionally joined to the previous row's wipe. The
|
||||||
|
// cvar string is the single source of truth ("tok,tok+tok,-tok": ',' starts
|
||||||
|
// a new wipe, '+' joins, '-' disables in place); the rows parse from it
|
||||||
|
// every frame and any edit writes it straight back, so the sequencer, this
|
||||||
|
// window and hand edits in config all stay consistent.
|
||||||
|
|
||||||
|
bool s_showcase_setup_open = false;
|
||||||
|
|
||||||
|
struct ShowcaseRow {
|
||||||
|
const skate3::native_scene::ShowcaseLayer* layer;
|
||||||
|
bool enabled;
|
||||||
|
bool joined;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ShowcaseRow> ParseShowcaseRows(const std::string& order) {
|
||||||
|
using skate3::native_scene::kShowcaseLayers;
|
||||||
|
std::vector<ShowcaseRow> rows;
|
||||||
|
std::string tok;
|
||||||
|
bool cur_joined = false; // the separator before the accumulating token
|
||||||
|
const auto flush = [&]() {
|
||||||
|
while (!tok.empty() && (tok.front() == ' ' || tok.front() == '\t')) {
|
||||||
|
tok.erase(tok.begin());
|
||||||
|
}
|
||||||
|
while (!tok.empty() && (tok.back() == ' ' || tok.back() == '\t')) {
|
||||||
|
tok.pop_back();
|
||||||
|
}
|
||||||
|
bool enabled = true;
|
||||||
|
if (!tok.empty() && tok[0] == '-') {
|
||||||
|
enabled = false;
|
||||||
|
tok.erase(tok.begin());
|
||||||
|
}
|
||||||
|
for (char& ch : tok) {
|
||||||
|
if (ch >= 'A' && ch <= 'Z') {
|
||||||
|
ch = char(ch + 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& layer : kShowcaseLayers) {
|
||||||
|
if (tok != layer.token) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool present = false;
|
||||||
|
for (const auto& row : rows) {
|
||||||
|
present = present || row.layer == &layer;
|
||||||
|
}
|
||||||
|
if (!present) {
|
||||||
|
rows.push_back({&layer, enabled, !rows.empty() && cur_joined});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok.clear();
|
||||||
|
};
|
||||||
|
for (char ch : order) {
|
||||||
|
if (ch == ',' || ch == '+') {
|
||||||
|
flush();
|
||||||
|
cur_joined = ch == '+';
|
||||||
|
} else {
|
||||||
|
tok += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
// Layers missing from the string append disabled, so they can always be
|
||||||
|
// re-enabled from the window.
|
||||||
|
for (const auto& layer : kShowcaseLayers) {
|
||||||
|
bool present = false;
|
||||||
|
for (const auto& row : rows) {
|
||||||
|
present = present || row.layer == &layer;
|
||||||
|
}
|
||||||
|
if (!present) {
|
||||||
|
rows.push_back({&layer, false, false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BuildShowcaseOrder(const std::vector<ShowcaseRow>& rows) {
|
||||||
|
std::string out;
|
||||||
|
for (size_t k = 0; k < rows.size(); ++k) {
|
||||||
|
if (k > 0) {
|
||||||
|
out += rows[k].joined ? '+' : ',';
|
||||||
|
}
|
||||||
|
if (!rows[k].enabled) {
|
||||||
|
out += '-';
|
||||||
|
}
|
||||||
|
out += rows[k].layer->token;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors the sequencer's availability rules (TickShowcase): the material
|
||||||
|
// looks need nothing; shadow/post layers need their feature (post gates
|
||||||
|
// live in the HDR tonemap).
|
||||||
|
bool ShowcaseLayerAvailable(uint32_t bit) {
|
||||||
|
const bool hdr_on = REXCVAR_GET(skate3_native_render_scene_hdr);
|
||||||
|
switch (bit) {
|
||||||
|
case 8u:
|
||||||
|
return REXCVAR_GET(skate3_native_render_scene_shadows);
|
||||||
|
case 16u:
|
||||||
|
return hdr_on && REXCVAR_GET(skate3_native_render_scene_ssao);
|
||||||
|
case 32u:
|
||||||
|
return hdr_on && REXCVAR_GET(skate3_native_render_scene_ssr);
|
||||||
|
case 64u:
|
||||||
|
return hdr_on && (REXCVAR_GET(skate3_native_render_scene_shafts) ||
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_haze));
|
||||||
|
case 128u:
|
||||||
|
return hdr_on && REXCVAR_GET(skate3_native_render_scene_bloom);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Drone-camera controls (same cvars everywhere the section is embedded;
|
// Drone-camera controls (same cvars everywhere the section is embedded;
|
||||||
// edits apply live).
|
// edits apply live).
|
||||||
void DrawFreecamControls() {
|
void DrawFreecamControls() {
|
||||||
|
|
@ -148,6 +267,130 @@ void DrawFreecamControls() {
|
||||||
"Z/X zoom, Shift fast, Ctrl slow.");
|
"Z/X zoom, Shift fast, Ctrl slow.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawShowcaseSetupWindow(bool* p_open) {
|
||||||
|
using skate3::native_scene::kShowcaseLayers;
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Showcase Setup", p_open, ImGuiWindowFlags_NoCollapse)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool running = REXCVAR_GET(skate3_native_render_scene_showcase);
|
||||||
|
if (ImGui::Button(running ? "Cancel showcase" : "Start showcase (Home)",
|
||||||
|
ImVec2(-1.0f, 0.0f))) {
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase, !running);
|
||||||
|
}
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase_hold,
|
||||||
|
CvarSlider("stage hold (s)",
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_showcase_hold),
|
||||||
|
0.0f, 10.0f, "%.1f",
|
||||||
|
"Seconds each layer holds fullscreen between wipes"));
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase_wipe,
|
||||||
|
CvarSlider("wipe duration (s)",
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_showcase_wipe),
|
||||||
|
0.5f, 10.0f, "%.1f",
|
||||||
|
"Seconds each split wipe takes to cross the screen"));
|
||||||
|
|
||||||
|
ImGui::SeparatorText("Layer order");
|
||||||
|
ImGui::TextDisabled(
|
||||||
|
"Every run starts from clay geometry. \"join\" reveals a layer\n"
|
||||||
|
"inside the previous layer's wipe instead of its own.");
|
||||||
|
std::vector<ShowcaseRow> rows =
|
||||||
|
ParseShowcaseRows(REXCVAR_GET(skate3_native_render_scene_showcase_order));
|
||||||
|
bool changed = false;
|
||||||
|
for (int k = 0; k < int(rows.size()); ++k) {
|
||||||
|
ImGui::PushID(k);
|
||||||
|
bool en = rows[k].enabled;
|
||||||
|
if (ImGui::Checkbox("##on", &en)) {
|
||||||
|
rows[k].enabled = en;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(k == 0);
|
||||||
|
if (ImGui::ArrowButton("up", ImGuiDir_Up) && k > 0) {
|
||||||
|
std::swap(rows[k - 1], rows[k]);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(k + 1 == int(rows.size()));
|
||||||
|
if (ImGui::ArrowButton("down", ImGuiDir_Down) && k + 1 < int(rows.size())) {
|
||||||
|
std::swap(rows[k], rows[k + 1]);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(k == 0);
|
||||||
|
bool joined = k > 0 && rows[k].joined;
|
||||||
|
if (ImGui::Checkbox("join", &joined)) {
|
||||||
|
rows[k].joined = joined;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ShowcaseLayerAvailable(rows[k].layer->bit)) {
|
||||||
|
ImGui::TextUnformatted(rows[k].layer->label);
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("%s (feature off)", rows[k].layer->label);
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
if (!rows.empty()) {
|
||||||
|
rows[0].joined = false;
|
||||||
|
}
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase_order,
|
||||||
|
BuildShowcaseOrder(rows));
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Reset to default order")) {
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase_order,
|
||||||
|
std::string(skate3::native_scene::kShowcaseOrderDefault));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live preview of the resulting run, mirrors the sequencer's step
|
||||||
|
// builder (cumulative layer masks; materials subsumes albedo/lighting;
|
||||||
|
// a final full-render step covers whatever the list leaves out).
|
||||||
|
ImGui::SeparatorText("Run preview");
|
||||||
|
uint32_t avail_mask = 0;
|
||||||
|
for (const auto& layer : kShowcaseLayers) {
|
||||||
|
if (ShowcaseLayerAvailable(layer.bit)) {
|
||||||
|
avail_mask |= layer.bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int step_no = 1;
|
||||||
|
ImGui::Text("%d. clay geometry", step_no++);
|
||||||
|
uint32_t cum = 0;
|
||||||
|
size_t k = 0;
|
||||||
|
while (k < rows.size()) {
|
||||||
|
uint32_t add = 0;
|
||||||
|
std::string label;
|
||||||
|
size_t j = k;
|
||||||
|
do {
|
||||||
|
const ShowcaseRow& row = rows[j];
|
||||||
|
if (row.enabled && (avail_mask & row.layer->bit) != 0 &&
|
||||||
|
(cum & row.layer->bit) == 0 && (add & row.layer->bit) == 0) {
|
||||||
|
add |= row.layer->bit;
|
||||||
|
if (!label.empty()) {
|
||||||
|
label += " & ";
|
||||||
|
}
|
||||||
|
label += row.layer->label;
|
||||||
|
}
|
||||||
|
++j;
|
||||||
|
} while (j < rows.size() && rows[j].joined);
|
||||||
|
if (add != 0) {
|
||||||
|
cum |= add;
|
||||||
|
ImGui::Text("%d. + %s", step_no++, label.c_str());
|
||||||
|
}
|
||||||
|
k = j;
|
||||||
|
}
|
||||||
|
const bool covered = (cum & 4u) != 0 && ((avail_mask & ~cum) & 0xF8u) == 0u;
|
||||||
|
if (!covered) {
|
||||||
|
ImGui::Text("%d. full render", step_no++);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawFreecamControls();
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void NativeDebugDialog::Show() {
|
void NativeDebugDialog::Show() {
|
||||||
|
|
@ -240,6 +483,25 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
|
||||||
"World env materials cull FRONT like the game's material "
|
"World env materials cull FRONT like the game's material "
|
||||||
"XMLs; off = legacy cull-none (shows interior faces)"));
|
"XMLs; off = legacy cull-none (shows interior faces)"));
|
||||||
|
|
||||||
|
ImGui::SeparatorText("Graphics showcase");
|
||||||
|
{
|
||||||
|
const bool running = REXCVAR_GET(skate3_native_render_scene_showcase);
|
||||||
|
if (ImGui::Button(running ? "Cancel showcase" : "Start showcase (Home)")) {
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase, !running);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip(
|
||||||
|
"Strip the frame to clay geometry, then rebuild it layer by\n"
|
||||||
|
"layer, each layer revealed by a live vertical split wiping\n"
|
||||||
|
"across the screen. Layer order, grouping and pacing are edited\n"
|
||||||
|
"in the setup window.");
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Setup...")) {
|
||||||
|
s_showcase_setup_open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DrawFreecamControls();
|
DrawFreecamControls();
|
||||||
|
|
||||||
ImGui::SeparatorText("HDR post effects (live)");
|
ImGui::SeparatorText("HDR post effects (live)");
|
||||||
|
|
@ -674,6 +936,9 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
|
||||||
ImGui::TextDisabled("MSAA + shadow tile size are restart-only (skate3.toml).");
|
ImGui::TextDisabled("MSAA + shadow tile size are restart-only (skate3.toml).");
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
if (s_showcase_setup_open) {
|
||||||
|
DrawShowcaseSetupWindow(&s_showcase_setup_open);
|
||||||
|
}
|
||||||
if (!open) {
|
if (!open) {
|
||||||
Hide();
|
Hide();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,38 @@ REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_haze_density, 0.005,
|
||||||
"200 units).")
|
"200 units).")
|
||||||
.range(0.0, 0.05)
|
.range(0.0, 0.05)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_showcase, false, "Skate 3",
|
||||||
|
"Run the graphics build-up showcase: the scene is stripped to "
|
||||||
|
"flat clay geometry, then rebuilt layer by layer, each layer "
|
||||||
|
"revealed by a vertical split wiping across the screen with "
|
||||||
|
"both sides rendered live. Layer order and grouping come from "
|
||||||
|
"skate3_native_render_scene_showcase_order (edited in the F12 "
|
||||||
|
"showcase setup window). Clears itself when the sequence "
|
||||||
|
"finishes; set to false mid-run to cancel. Also on the Home "
|
||||||
|
"key by default (bind_skate3_showcase). Layers whose feature "
|
||||||
|
"is disabled (or unavailable without the HDR intermediate) "
|
||||||
|
"are skipped.")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
REXCVAR_DEFINE_STRING(
|
||||||
|
skate3_native_render_scene_showcase_order,
|
||||||
|
skate3::native_scene::kShowcaseOrderDefault, "Skate 3",
|
||||||
|
"Showcase layer order: comma-separated reveal steps, '+' joins layers "
|
||||||
|
"into one wipe, a '-' prefix disables a layer while keeping its "
|
||||||
|
"position. Tokens: albedo, lighting, materials, shadows, ao, ssr, vol, "
|
||||||
|
"bloom (the run always starts from clay geometry, and a final "
|
||||||
|
"full-render step is appended when the list leaves layers unrevealed). "
|
||||||
|
"Example: \"albedo,lighting,materials,shadows+ao,-ssr,vol+bloom\".")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_showcase_hold, 2.5, "Skate 3",
|
||||||
|
"Showcase: seconds each build-up stage holds fullscreen "
|
||||||
|
"between wipes.")
|
||||||
|
.range(0.0, 30.0)
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_showcase_wipe, 3.0, "Skate 3",
|
||||||
|
"Showcase: seconds each split wipe takes to cross the "
|
||||||
|
"screen.")
|
||||||
|
.range(0.2, 30.0)
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_freecam, false, "Skate 3",
|
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_freecam, false, "Skate 3",
|
||||||
"Detach the render camera from the game (drone / free-fly "
|
"Detach the render camera from the game (drone / free-fly "
|
||||||
"cam): WASD fly, E/Space up, Q/C down, arrow keys or "
|
"cam): WASD fly, E/Space up, Q/C down, arrow keys or "
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,34 @@ struct SubmitRecord {
|
||||||
uint32_t c;
|
uint32_t c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Graphics build-up showcase layer registry: the order-string tokens
|
||||||
|
// (cvar skate3_native_render_scene_showcase_order), the per-layer shader
|
||||||
|
// mask bits (contract in scene.hlsl ShowcaseMask: the split rows carry
|
||||||
|
// 256 + the cumulative mask, 0 = showcase off) and the display labels.
|
||||||
|
// Shared by the sequencer (skate3_native_scene_gpu.cpp) and the F12 setup
|
||||||
|
// window (skate3_native_debug_dialog.cpp). The material bits are
|
||||||
|
// progressive looks (materials subsumes lighting subsumes albedo); the
|
||||||
|
// rest are independent and can reveal in any order or grouping.
|
||||||
|
struct ShowcaseLayer {
|
||||||
|
const char* token;
|
||||||
|
uint32_t bit;
|
||||||
|
const char* label;
|
||||||
|
};
|
||||||
|
inline constexpr ShowcaseLayer kShowcaseLayers[] = {
|
||||||
|
{"albedo", 1u, "albedo textures"},
|
||||||
|
{"lighting", 2u, "baked lighting"},
|
||||||
|
{"materials", 4u, "materials & surface detail"},
|
||||||
|
{"shadows", 8u, "dynamic shadows"},
|
||||||
|
{"ao", 16u, "ambient occlusion"},
|
||||||
|
{"ssr", 32u, "reflections"},
|
||||||
|
{"vol", 64u, "volumetrics"},
|
||||||
|
{"bloom", 128u, "bloom"},
|
||||||
|
};
|
||||||
|
inline constexpr size_t kShowcaseLayerCount =
|
||||||
|
sizeof(kShowcaseLayers) / sizeof(kShowcaseLayers[0]);
|
||||||
|
inline constexpr const char* kShowcaseOrderDefault =
|
||||||
|
"albedo,lighting,materials,shadows+ao,ssr,vol+bloom";
|
||||||
|
|
||||||
bool Enabled();
|
bool Enabled();
|
||||||
|
|
||||||
// Host settings-overlay backdrop: while the SDK settings menu is open, run
|
// Host settings-overlay backdrop: while the SDK settings menu is open, run
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,9 @@ REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_radius);
|
||||||
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_static_size);
|
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_static_size);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_strength);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_strength);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_shadows);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_shadows);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_shafts);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_haze);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_showcase);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_sort_opaque);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_sort_opaque);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_splines);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_splines);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_ssao);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_ssao);
|
||||||
|
|
@ -127,6 +130,9 @@ REXCVAR_DECLARE(double, skate3_native_render_scene_refl_lod);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_intensity);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_intensity);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_luma_protect);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_luma_protect);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_radius);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_ssao_radius);
|
||||||
|
REXCVAR_DECLARE(double, skate3_native_render_scene_showcase_hold);
|
||||||
|
REXCVAR_DECLARE(double, skate3_native_render_scene_showcase_wipe);
|
||||||
|
REXCVAR_DECLARE(std::string, skate3_native_render_scene_showcase_order);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_ssr_intensity);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_ssr_intensity);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_ssr_thickness);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_ssr_thickness);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_world_v2_tan_sign);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_world_v2_tan_sign);
|
||||||
|
|
@ -6523,6 +6529,185 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Graphics build-up showcase -------------------------------------------
|
||||||
|
// Sequencer for the layer-by-layer showcase (cvar
|
||||||
|
// skate3_native_render_scene_showcase / the bind_skate3_showcase hotkey):
|
||||||
|
// one wipe strips the frame to clay geometry, then each following wipe
|
||||||
|
// reveals the next visual layer, a vertical split sweeping left-to-right
|
||||||
|
// with BOTH sides rendered live (per-pixel stage selection in the shaders,
|
||||||
|
// no frozen frames). The split state rides the per-frame b1 rows
|
||||||
|
// (scene.hlsl sh_v2.yzw) and g_r.showcase_rows (the SSR composite reads it
|
||||||
|
// from root constants); every consumer treats an all-zero row as "showcase
|
||||||
|
// off". Runs on the render thread once per frame at b1 staging. The layer
|
||||||
|
// mask encoding matches the shader contract in scene.hlsl ShowcaseMask;
|
||||||
|
// the reveal order and grouping come from the user-editable
|
||||||
|
// skate3_native_render_scene_showcase_order cvar (kShowcaseLayers tokens).
|
||||||
|
static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
||||||
|
// One reveal step: the b1 row encoding (256 + the cumulative layer mask;
|
||||||
|
// 0 = the full render) and its log label.
|
||||||
|
struct Step {
|
||||||
|
float value;
|
||||||
|
std::string label;
|
||||||
|
};
|
||||||
|
struct State {
|
||||||
|
bool active = false;
|
||||||
|
std::vector<Step> steps;
|
||||||
|
int logged = -1;
|
||||||
|
double wipe_s = 3.0;
|
||||||
|
double hold_s = 2.5;
|
||||||
|
PerfClock::time_point t0;
|
||||||
|
};
|
||||||
|
static State s;
|
||||||
|
float* rows = g_r.showcase_rows;
|
||||||
|
rows[0] = rows[1] = rows[2] = 0.0f;
|
||||||
|
const bool want = REXCVAR_GET(skate3_native_render_scene_showcase);
|
||||||
|
if (!s.active) {
|
||||||
|
if (!want) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Layer availability: the material looks need nothing; the shadow and
|
||||||
|
// post layers join only when their feature is enabled (the post gates
|
||||||
|
// live in the HDR tonemap, so they need the HDR chain).
|
||||||
|
const auto layer_available = [&](uint32_t bit) -> bool {
|
||||||
|
switch (bit) {
|
||||||
|
case 8u:
|
||||||
|
return REXCVAR_GET(skate3_native_render_scene_shadows);
|
||||||
|
case 16u:
|
||||||
|
return hdr_on && REXCVAR_GET(skate3_native_render_scene_ssao);
|
||||||
|
case 32u:
|
||||||
|
return hdr_on && REXCVAR_GET(skate3_native_render_scene_ssr);
|
||||||
|
case 64u:
|
||||||
|
return hdr_on && (REXCVAR_GET(skate3_native_render_scene_shafts) ||
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_haze));
|
||||||
|
case 128u:
|
||||||
|
return hdr_on && REXCVAR_GET(skate3_native_render_scene_bloom);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
uint32_t avail_mask = 0;
|
||||||
|
for (const auto& layer : kShowcaseLayers) {
|
||||||
|
if (layer_available(layer.bit)) {
|
||||||
|
avail_mask |= layer.bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Build the reveal steps from the user-ordered layer list (syntax in
|
||||||
|
// the skate3_native_render_scene_showcase_order cvar description):
|
||||||
|
// comma-separated steps, '+' joins layers into one wipe, '-' prefix =
|
||||||
|
// disabled. Unknown, unavailable and duplicate tokens drop out; steps
|
||||||
|
// left empty are skipped. The run always starts from clay.
|
||||||
|
s.steps.clear();
|
||||||
|
s.steps.push_back({256.0f, "clay geometry"});
|
||||||
|
uint32_t cum = 0;
|
||||||
|
const std::string order =
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_showcase_order);
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos <= order.size()) {
|
||||||
|
size_t end = order.find(',', pos);
|
||||||
|
if (end == std::string::npos) {
|
||||||
|
end = order.size();
|
||||||
|
}
|
||||||
|
const std::string group = order.substr(pos, end - pos);
|
||||||
|
pos = end + 1;
|
||||||
|
uint32_t add = 0;
|
||||||
|
std::string label;
|
||||||
|
size_t g = 0;
|
||||||
|
while (g <= group.size()) {
|
||||||
|
size_t ge = group.find('+', g);
|
||||||
|
if (ge == std::string::npos) {
|
||||||
|
ge = group.size();
|
||||||
|
}
|
||||||
|
std::string tok = group.substr(g, ge - g);
|
||||||
|
g = ge + 1;
|
||||||
|
while (!tok.empty() && (tok.front() == ' ' || tok.front() == '\t')) {
|
||||||
|
tok.erase(tok.begin());
|
||||||
|
}
|
||||||
|
while (!tok.empty() && (tok.back() == ' ' || tok.back() == '\t')) {
|
||||||
|
tok.pop_back();
|
||||||
|
}
|
||||||
|
for (char& ch : tok) {
|
||||||
|
if (ch >= 'A' && ch <= 'Z') {
|
||||||
|
ch = char(ch + 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tok.empty() || tok[0] == '-') {
|
||||||
|
continue; // disabled layer (kept in the string for its position)
|
||||||
|
}
|
||||||
|
for (const auto& layer : kShowcaseLayers) {
|
||||||
|
if (tok == layer.token) {
|
||||||
|
if ((avail_mask & layer.bit) != 0 && (cum & layer.bit) == 0 &&
|
||||||
|
(add & layer.bit) == 0) {
|
||||||
|
add |= layer.bit;
|
||||||
|
if (!label.empty()) {
|
||||||
|
label += " & ";
|
||||||
|
}
|
||||||
|
label += layer.label;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (add != 0) {
|
||||||
|
cum |= add;
|
||||||
|
s.steps.push_back({float(256u + cum), std::move(label)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A final full-render reveal when the list leaves layers unrevealed
|
||||||
|
// (the materials look subsumes the albedo/lighting bits, so those two
|
||||||
|
// stop counting once materials is in).
|
||||||
|
const bool covered =
|
||||||
|
(cum & 4u) != 0 && ((avail_mask & ~cum) & 0xF8u) == 0u;
|
||||||
|
if (!covered) {
|
||||||
|
s.steps.push_back({0.0f, "full render"});
|
||||||
|
}
|
||||||
|
// Durations snapshot at start so a live cvar edit cannot jump the
|
||||||
|
// elapsed-time -> step mapping mid-run.
|
||||||
|
s.wipe_s = std::max(0.2, REXCVAR_GET(skate3_native_render_scene_showcase_wipe));
|
||||||
|
s.hold_s = std::max(0.0, REXCVAR_GET(skate3_native_render_scene_showcase_hold));
|
||||||
|
s.t0 = PerfClock::now();
|
||||||
|
s.logged = -1;
|
||||||
|
s.active = true;
|
||||||
|
REXLOG_INFO(
|
||||||
|
"native-scene: showcase started ({} steps, wipe {:.1f}s, hold "
|
||||||
|
"{:.1f}s, hdr={}, order=\"{}\")",
|
||||||
|
s.steps.size(), s.wipe_s, s.hold_s, hdr_on ? 1 : 0, order);
|
||||||
|
}
|
||||||
|
if (!want) {
|
||||||
|
s.active = false;
|
||||||
|
REXLOG_INFO("native-scene: showcase cancelled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const double elapsed =
|
||||||
|
std::chrono::duration<double>(PerfClock::now() - s.t0).count();
|
||||||
|
const double per_step = s.wipe_s + s.hold_s;
|
||||||
|
const int idx = int(elapsed / per_step);
|
||||||
|
if (idx >= int(s.steps.size())) {
|
||||||
|
s.active = false;
|
||||||
|
REXCVAR_SET(skate3_native_render_scene_showcase, false);
|
||||||
|
REXLOG_INFO("native-scene: showcase finished");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const float cur = s.steps[idx].value;
|
||||||
|
if (idx != s.logged) {
|
||||||
|
REXLOG_INFO("native-scene: showcase step {}/{} - {}", idx + 1,
|
||||||
|
s.steps.size(), s.steps[idx].label);
|
||||||
|
s.logged = idx;
|
||||||
|
}
|
||||||
|
const float prev = idx == 0 ? 0.0f : s.steps[idx - 1].value;
|
||||||
|
const double phase = elapsed - double(idx) * per_step;
|
||||||
|
if (phase < s.wipe_s) {
|
||||||
|
float f = float(phase / s.wipe_s);
|
||||||
|
f = f * f * (3.0f - 2.0f * f); // ease the sweep in and out
|
||||||
|
rows[0] = cur;
|
||||||
|
rows[1] = prev;
|
||||||
|
// Overshoot slightly so the divider line exits the screen edge cleanly.
|
||||||
|
rows[2] = f * float(output_width) * 1.02f;
|
||||||
|
} else {
|
||||||
|
rows[0] = rows[1] = cur;
|
||||||
|
rows[2] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_data*/) {
|
bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_data*/) {
|
||||||
if (!SceneEnabled() ||
|
if (!SceneEnabled() ||
|
||||||
(context.backend != NativeGuestOutputBackend::kD3D12 &&
|
(context.backend != NativeGuestOutputBackend::kD3D12 &&
|
||||||
|
|
@ -7009,9 +7194,14 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
std::memcpy(cb + 40, scene.fog_color, 4 * sizeof(float));
|
std::memcpy(cb + 40, scene.fog_color, 4 * sizeof(float));
|
||||||
}
|
}
|
||||||
// World-shading v2 row (sh_v2, cb[60..63]): x = stored-tangent
|
// World-shading v2 row (sh_v2, cb[60..63]): x = stored-tangent
|
||||||
// polarity.
|
// polarity, yzw = the build-up showcase split state (stage left/right
|
||||||
|
// of the split + its position in output pixels; zeros = showcase off).
|
||||||
cb[60] =
|
cb[60] =
|
||||||
float(REXCVAR_GET(skate3_native_render_scene_world_v2_tan_sign));
|
float(REXCVAR_GET(skate3_native_render_scene_world_v2_tan_sign));
|
||||||
|
TickShowcase(context.guest_output_width, hdr_on);
|
||||||
|
cb[61] = g_r.showcase_rows[0];
|
||||||
|
cb[62] = g_r.showcase_rows[1];
|
||||||
|
cb[63] = g_r.showcase_rows[2];
|
||||||
// dynamicobject.fx frame rows (dyn_sun/dyn_amb/dyn_misc at cb[44..55]).
|
// dynamicobject.fx frame rows (dyn_sun/dyn_amb/dyn_misc at cb[44..55]).
|
||||||
if (scene.dynobj_valid) {
|
if (scene.dynobj_valid) {
|
||||||
cb[44] = scene.dynobj_rows[0]; // sun dir (PS c9)
|
cb[44] = scene.dynobj_rows[0]; // sun dir (PS c9)
|
||||||
|
|
|
||||||
|
|
@ -726,6 +726,11 @@ struct RendererState {
|
||||||
// staged by ApplyVolumetricPass; zeros disable every term.
|
// staged by ApplyVolumetricPass; zeros disable every term.
|
||||||
bool vol_tonemap_valid = false;
|
bool vol_tonemap_valid = false;
|
||||||
float vol_rows[16] = {};
|
float vol_rows[16] = {};
|
||||||
|
// Graphics build-up showcase split state for this frame, computed by the
|
||||||
|
// sequencer at b1 staging and mirrored here for the SSR composite:
|
||||||
|
// {stage left of the split, stage right, split position in output px}.
|
||||||
|
// All zeros = showcase off (stage 0 = the full render in every consumer).
|
||||||
|
float showcase_rows[3] = {};
|
||||||
std::unordered_map<uint32_t, MeshBuffers> meshes;
|
std::unordered_map<uint32_t, MeshBuffers> meshes;
|
||||||
// (The old D3D12 bookkeeping, the retired-resource vector, the CPU SRV
|
// (The old D3D12 bookkeeping, the retired-resource vector, the CPU SRV
|
||||||
// staging heap and its slot allocator/recycling lists, is gone: resource
|
// staging heap and its slot allocator/recycling lists, is gone: resource
|
||||||
|
|
|
||||||
|
|
@ -781,6 +781,17 @@ bool ApplySsrPass(const NativeGuestOutputRenderContext& context,
|
||||||
c[22] = float(REXCVAR_GET(skate3_native_render_scene_ssr_intensity));
|
c[22] = float(REXCVAR_GET(skate3_native_render_scene_ssr_intensity));
|
||||||
c[23] = float(REXCVAR_GET(skate3_native_render_scene_ssr_debug));
|
c[23] = float(REXCVAR_GET(skate3_native_render_scene_ssr_debug));
|
||||||
c[24] = 250.0f; // max ray distance (view units; the screen clip governs)
|
c[24] = 250.0f; // max ray distance (view units; the screen clip governs)
|
||||||
|
// Build-up showcase gate (p2, see ssr.hlsl ps_composite): the composite
|
||||||
|
// side whose layer mask has not revealed reflections outputs alpha 0.
|
||||||
|
// Rows carry 256 + mask (0 = showcase off); SSR is mask bit 32.
|
||||||
|
const auto ssr_visible = [](float row) {
|
||||||
|
return (row < 255.5f || ((uint32_t(row + 0.5f) - 256u) & 32u) != 0u)
|
||||||
|
? 1.0f
|
||||||
|
: 0.0f;
|
||||||
|
};
|
||||||
|
c[28] = g_r.showcase_rows[2];
|
||||||
|
c[29] = ssr_visible(g_r.showcase_rows[0]);
|
||||||
|
c[30] = ssr_visible(g_r.showcase_rows[1]);
|
||||||
|
|
||||||
cmd->SetBindingLayout(g_r.ssr_layout);
|
cmd->SetBindingLayout(g_r.ssr_layout);
|
||||||
cmd->SetPrimitiveTopology(nrhi::PrimitiveTopology::kTriangleList);
|
cmd->SetPrimitiveTopology(nrhi::PrimitiveTopology::kTriangleList);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue