render: compile the showcase gates out of the standard shader variants

This commit is contained in:
mchughalex 2026-07-21 09:50:17 +01:00
parent 137d97c190
commit 7fa9585285
8 changed files with 13520 additions and 32 deletions

View file

@ -364,8 +364,17 @@ float4 ps_tonemap(VSOut i) : SV_Target {
// 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).
// Compiled in only for the SHOWCASE=1 variant; the default build folds
// the mask to off and every gate below dead-strips.
#ifndef SHOWCASE
#define SHOWCASE 0
#endif
#if SHOWCASE
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;
#else
const int sc_mask = -1;
#endif
float ao = ao_plane.SampleLevel(smp_linear, i.uv, 0);
if (sc_mask >= 0 && (sc_mask & 16) == 0) {
ao = 1.0;
@ -446,6 +455,7 @@ float4 ps_tonemap(VSOut i) : SV_Target {
// overpass) saturated to a solid black void.
float3 outc =
ToneMapScene((x + haze) * (1.0 - 0.55 * saturate(shaft)) + bloom);
#if SHOWCASE
// 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) {
@ -454,5 +464,6 @@ float4 ps_tonemap(VSOut i) : SV_Target {
outc = lerp(outc, float3(1.0, 1.0, 1.0),
saturate(1.0 - d / half_w) * 0.85);
}
#endif
return float4(outc, 1.0);
}

View file

@ -157,9 +157,20 @@ VSOut vs_main(float3 p : POSITION, float2 uv : TEXCOORD0, float2 uv2 : TEXCOORD1
// content belongs to the normal frame and stays hidden until the bit
// reveals (the sequencer folds their bits into every step when the layer
// is not part of the run). Returns -1 when the showcase is off.
// The showcase code compiles in only for the SHOWCASE=1 shader variants,
// which the pipeline family swaps in for the duration of a run. The default
// build folds the mask to the off value, so every showcase branch
// dead-strips and the program matches the pre-showcase compile exactly.
#ifndef SHOWCASE
#define SHOWCASE 0
#endif
int ShowcaseMask(float px_x) {
#if SHOWCASE
float v = px_x < sh_v2.w ? sh_v2.y : sh_v2.z;
return v < 255.5 ? -1 : (int)(v + 0.5) - 256;
#else
return -1;
#endif
}
// True when the given layer bit is revealed on this pixel's side of the
// split (or the showcase is off entirely).

File diff suppressed because it is too large Load diff

View file

@ -54,9 +54,18 @@ float3 SplineOut(float3 c) {
// Showcase blackout gate: the spline pass draws outside the scene shaders,
// so ps_main's blackout stage (scene.hlsl, mask bit 1024) cannot cover it;
// without this the neon lines leak over the black recording bookends.
// Compiled in only for the SHOWCASE=1 variant swapped in during a run; the
// default build folds to visible and the gate dead-strips.
#ifndef SHOWCASE
#define SHOWCASE 0
#endif
bool SplineVisible(float px_x) {
#if SHOWCASE
int bits = (int)(intensity.w + 0.5);
return ((px_x < intensity.z ? 1 : 2) & bits) != 0;
#else
return true;
#endif
}
float4 ps_default(VSOut i) : SV_Target {
if (!SplineVisible(i.pos.x)) {

View file

@ -285,11 +285,17 @@ float4 ps_march(VSOut i) : SV_Target {
}
float4 ps_composite(VSOut i) : SV_Target {
#ifndef SHOWCASE
#define SHOWCASE 0
#endif
#if SHOWCASE
// Showcase gate: alpha 0 leaves the scene plane untouched on a side
// whose build-up stage has not revealed reflections yet.
// whose build-up stage has not revealed reflections yet. Compiled in
// only for the SHOWCASE=1 variant swapped in during a run.
if ((i.pos.x < p2.x ? p2.y : p2.z) < 0.5f) {
return float4(0.0f, 0.0f, 0.0f, 0.0f);
}
#endif
float4 g = tex1c.SampleLevel(smp_point, i.uv, 0);
float wrefl = abs(g.a);
float dbg = p0.w;

View file

@ -2656,15 +2656,31 @@ bool EnsureScenePsoFamily(const NativeGuestOutputRenderContext& context) {
}
// HDR=1 switches the pixel shader's output encode to pre-tonemap linear
// (see scene.hlsl ToneOut/PassGamma).
const nrhi::ShaderMacro hdr_defs[] = {{"HDR", "1"}, {nullptr, nullptr}};
// (see scene.hlsl ToneOut/PassGamma); SHOWCASE=1 compiles the build-up
// split/mask gates in (swapped in only while a run is live). The variant
// string is the canonical ";"-joined macro signature the offline SPIR-V
// matrix uses.
nrhi::ShaderMacro ps_defs[3];
uint32_t ps_def_count = 0;
if (g_r.hdr_active) {
ps_defs[ps_def_count++] = {"HDR", "1"};
}
if (g_r.showcase_shaders) {
ps_defs[ps_def_count++] = {"SHOWCASE", "1"};
}
ps_defs[ps_def_count] = {nullptr, nullptr};
char ps_variant[24];
std::snprintf(ps_variant, sizeof(ps_variant), "%s%s%s",
g_r.hdr_active ? "HDR=1" : "",
g_r.hdr_active && g_r.showcase_shaders ? ";" : "",
g_r.showcase_shaders ? "SHOWCASE=1" : "");
nrhi::Shader* vs = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kVertex, "scene.hlsl", kShaderSource,
"vs_main", nullptr, ""));
nrhi::Shader* ps = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "scene.hlsl", kShaderSource,
"ps_main", g_r.hdr_active ? hdr_defs : nullptr,
g_r.hdr_active ? "HDR=1" : ""));
"ps_main", ps_def_count != 0 ? ps_defs : nullptr,
ps_variant));
if (vs == nullptr || ps == nullptr) {
REXLOG_ERROR("native-scene: shader compile failed");
g_r.failed = true;
@ -2995,18 +3011,34 @@ bool EnsureSplinePsos(const NativeGuestOutputRenderContext& context) {
*p = nullptr;
}
}
const nrhi::ShaderMacro hdr_defs[] = {{"HDR", "1"}, {nullptr, nullptr}};
const nrhi::ShaderMacro* sp_defs = g_r.hdr_active ? hdr_defs : nullptr;
const char* sp_variant = g_r.hdr_active ? "HDR=1" : "";
// SHOWCASE=1 compiles the blackout gate in (see spline.hlsl
// SplineVisible); like the scene family, the variant follows the
// showcase swap so normal sessions carry no gate at all.
nrhi::ShaderMacro sp_defs[3];
uint32_t sp_def_count = 0;
if (g_r.hdr_active) {
sp_defs[sp_def_count++] = {"HDR", "1"};
}
if (g_r.showcase_shaders) {
sp_defs[sp_def_count++] = {"SHOWCASE", "1"};
}
sp_defs[sp_def_count] = {nullptr, nullptr};
char sp_variant[24];
std::snprintf(sp_variant, sizeof(sp_variant), "%s%s%s",
g_r.hdr_active ? "HDR=1" : "",
g_r.hdr_active && g_r.showcase_shaders ? ";" : "",
g_r.showcase_shaders ? "SHOWCASE=1" : "");
nrhi::Shader* svs = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kVertex, "spline.hlsl",
kShaderSplineSource, "vs_main", nullptr, ""));
nrhi::Shader* spd = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "spline.hlsl",
kShaderSplineSource, "ps_default", sp_defs, sp_variant));
kShaderSplineSource, "ps_default",
sp_def_count != 0 ? sp_defs : nullptr, sp_variant));
nrhi::Shader* spk = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "spline.hlsl",
kShaderSplineSource, "ps_darken", sp_defs, sp_variant));
kShaderSplineSource, "ps_darken",
sp_def_count != 0 ? sp_defs : nullptr, sp_variant));
if (svs == nullptr || spd == nullptr || spk == nullptr) {
REXLOG_ERROR("native-scene: spline shader compile failed");
g_r.failed = true;
@ -3981,7 +4013,8 @@ bool EnsurePipeline(const NativeGuestOutputRenderContext& context) {
if (!g_r.pso || g_r.rtv_format != context.guest_output->format() ||
g_r.hdr_active != hdr_want ||
(hdr_want && g_r.hdr_scene_format != hdr_fmt_want) ||
g_r.msaa != msaa_want) {
g_r.msaa != msaa_want ||
g_r.showcase_shaders != g_r.showcase_shaders_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
@ -3997,14 +4030,24 @@ bool EnsurePipeline(const NativeGuestOutputRenderContext& context) {
g_r.hdr_active = hdr_want;
g_r.hdr_scene_format = hdr_fmt_want;
g_r.msaa = msaa_want;
g_r.showcase_shaders = g_r.showcase_shaders_want;
if (!EnsureScenePsoFamily(context) || !EnsureResolvePso(context) ||
!EnsureBlurPsos(context) || !EnsureOutlineEdgePso(context) ||
!Ensure2dPso(context) || !EnsureSplinePsos(context) ||
!EnsureShadowPsos(context)) {
if (g_r.showcase_shaders) {
// A showcase-variant build failure must not pin the sticky failure
// latch: drop the swap request so the F5 retry rebuilds the
// standard shaders.
g_r.showcase_shaders_want = false;
g_r.showcase_shaders = false;
REXCVAR_SET(skate3_native_render_scene_showcase, false);
}
return false;
}
REXLOG_INFO("native-scene: pipelines created (MSAA x{}, {})", g_r.msaa,
g_r.hdr_active ? "HDR" : "classic");
REXLOG_INFO("native-scene: pipelines created (MSAA x{}, {}{})", g_r.msaa,
g_r.hdr_active ? "HDR" : "classic",
g_r.showcase_shaders ? ", showcase variants" : "");
g_r.rtv_format = context.guest_output->format();
}
@ -6946,6 +6989,18 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
const bool want = REXCVAR_GET(skate3_native_render_scene_showcase);
if (!s.active) {
if (!want) {
// No run pending: release the showcase shader swap (the next
// EnsurePipeline rebuild returns the family to the standard
// variants with the showcase code compiled out).
g_r.showcase_shaders_want = false;
return;
}
// The run's shaders carry the split/mask gates only in their
// SHOWCASE=1 variants: request the swap and hold the start until the
// next EnsurePipeline rebuild has them live (rows stay zero, so the
// waiting frames render normally).
g_r.showcase_shaders_want = true;
if (!g_r.showcase_shaders) {
return;
}
// Layer availability: the material looks need nothing; the shadow and
@ -7082,6 +7137,7 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
}
if (!want) {
s.active = false;
g_r.showcase_shaders_want = false;
REXLOG_INFO("native-scene: showcase cancelled");
return;
}
@ -7104,6 +7160,7 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
}
if (idx >= int(s.steps.size())) {
s.active = false;
g_r.showcase_shaders_want = false;
REXCVAR_SET(skate3_native_render_scene_showcase, false);
REXLOG_INFO("native-scene: showcase finished");
return;

View file

@ -636,6 +636,17 @@ struct RendererState {
nrhi::Pipeline* pso_bloom_up = nullptr; // additive ONE/ONE tent upsample
nrhi::Pipeline* pso_tonemap = nullptr;
nrhi::Format hdr_pso_out_format = nrhi::Format::kUnknown;
// Showcase shader swap: while a build-up run is live the pipeline family
// rebuilds with the SHOWCASE=1 shader variants (the split/mask gates
// compiled in); every other session runs shaders with the showcase code
// compiled out entirely, so normal rendering carries none of it. _want is
// set by TickShowcase (a run needs the variants BEFORE its first frame),
// applied by the EnsurePipeline rebuild block; the per-pass built flags
// let the lazily-built tonemap/SSR pipelines follow the swap.
bool showcase_shaders_want = false;
bool showcase_shaders = false;
bool hdr_showcase = false;
bool ssr_showcase = false;
// 1x float scene plane: the MSAA resolve destination (or the scene target
// itself when MSAA is off); the AO composite, bloom extract and tonemap
// all consume/write it.

View file

@ -460,7 +460,8 @@ bool EnsureSsrPipeline(const NativeGuestOutputRenderContext& context) {
return false;
}
if (g_r.pso_ssr_march != nullptr && g_r.ssr_msaa == g_r.msaa &&
g_r.ssr_scene_fmt == g_r.hdr_scene_format) {
g_r.ssr_scene_fmt == g_r.hdr_scene_format &&
g_r.ssr_showcase == g_r.showcase_shaders) {
return true;
}
nrhi::Device* device = context.device;
@ -519,9 +520,13 @@ bool EnsureSsrPipeline(const NativeGuestOutputRenderContext& context) {
nrhi::Shader* ps_march = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "ssr.hlsl", kSsrShaderSource,
"ps_march", nullptr, ""));
nrhi::Shader* ps_comp = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "ssr.hlsl", kSsrShaderSource,
"ps_composite", nullptr, ""));
// The composite follows the showcase shader swap (its SHOWCASE=1 variant
// carries the per-side reveal gate).
const nrhi::ShaderMacro sc_defs[] = {{"SHOWCASE", "1"}, {nullptr, nullptr}};
nrhi::Shader* ps_comp = device->CreateShader(MakeShaderDesc(
nrhi::ShaderStage::kPixel, "ssr.hlsl", kSsrShaderSource, "ps_composite",
g_r.showcase_shaders ? sc_defs : nullptr,
g_r.showcase_shaders ? "SHOWCASE=1" : ""));
const bool shaders_ok = scene_vs != nullptr && ps_gbuf != nullptr &&
vs != nullptr && ps_lin != nullptr &&
ps_march != nullptr && ps_comp != nullptr;
@ -591,6 +596,7 @@ bool EnsureSsrPipeline(const NativeGuestOutputRenderContext& context) {
}
g_r.ssr_msaa = g_r.msaa;
g_r.ssr_scene_fmt = g_r.hdr_scene_format;
g_r.ssr_showcase = g_r.showcase_shaders;
REXLOG_INFO("native-scene: ssr pipeline created (MSAA x{})", g_r.msaa);
return true;
}
@ -1315,7 +1321,8 @@ bool EnsureHdrPipeline(const NativeGuestOutputRenderContext& context) {
return false;
}
if (g_r.pso_tonemap != nullptr &&
g_r.hdr_pso_out_format == context.guest_output->format()) {
g_r.hdr_pso_out_format == context.guest_output->format() &&
g_r.hdr_showcase == g_r.showcase_shaders) {
return true;
}
nrhi::Device* device = context.device;
@ -1384,9 +1391,13 @@ bool EnsureHdrPipeline(const NativeGuestOutputRenderContext& context) {
nrhi::Shader* ps_up = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "hdr.hlsl", kHdrShaderSource,
"ps_bloom_up", nullptr, ""));
nrhi::Shader* ps_tone = device->CreateShader(
MakeShaderDesc(nrhi::ShaderStage::kPixel, "hdr.hlsl", kHdrShaderSource,
"ps_tonemap", nullptr, ""));
// The tonemap follows the showcase shader swap (its SHOWCASE=1 variant
// carries the post-layer gates and the split divider).
const nrhi::ShaderMacro sc_defs[] = {{"SHOWCASE", "1"}, {nullptr, nullptr}};
nrhi::Shader* ps_tone = device->CreateShader(MakeShaderDesc(
nrhi::ShaderStage::kPixel, "hdr.hlsl", kHdrShaderSource, "ps_tonemap",
g_r.showcase_shaders ? sc_defs : nullptr,
g_r.showcase_shaders ? "SHOWCASE=1" : ""));
const bool shaders_ok = vs != nullptr && ps_first != nullptr &&
ps_down != nullptr && ps_up != nullptr &&
ps_tone != nullptr;
@ -1428,6 +1439,7 @@ bool EnsureHdrPipeline(const NativeGuestOutputRenderContext& context) {
return fail("pso");
}
g_r.hdr_pso_out_format = context.guest_output->format();
g_r.hdr_showcase = g_r.showcase_shaders;
REXLOG_INFO("native-scene: HDR post pipeline created");
return true;
}