render: showcase decals/dyn layers, blackout bookends, and hot-rebuild fixes
This commit is contained in:
parent
0cdead0283
commit
33b7465d2e
9 changed files with 15566 additions and 15216 deletions
|
|
@ -148,13 +148,25 @@ VSOut vs_main(float3 p : POSITION, float2 uv : TEXCOORD0, float2 uv2 : TEXCOORD1
|
|||
// 1 albedo textures 2 baked lighting 4 materials & surface detail
|
||||
// 8 dynamic shadows 16 ambient occlusion 32 reflections (SSR)
|
||||
// 64 volumetrics 128 bloom
|
||||
// 256 decals & grime 512 dynamic entities
|
||||
// 1024 blackout: the run's opening/closing bookends (ps_main renders every
|
||||
// draw black; doubles as the recording cut marker), not an orderable layer.
|
||||
// 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.
|
||||
// them in any order or grouping. 256/512 are SUBTRACTIVE layers: their
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
// True when the given layer bit is revealed on this pixel's side of the
|
||||
// split (or the showcase is off entirely).
|
||||
bool ShowcaseLayerOn(float px_x, int bit) {
|
||||
int m = ShowcaseMask(px_x);
|
||||
return m < 0 || (m & bit) != 0;
|
||||
}
|
||||
#include "scene_shadows.hlsli"
|
||||
#include "scene_common.hlsli"
|
||||
#include "scene_char.hlsli"
|
||||
|
|
@ -292,12 +304,15 @@ float4 ShadePixel(VSOut i) {
|
|||
// against (CSM s + shadow color), kd, phong spec vs the shader's
|
||||
// fixed literal light, cube reflection on 5/6.
|
||||
float3 ov = float3(1.0, 1.0, 1.0);
|
||||
// Decals-&-grime build-up layer (bit 256): macro weathering and decal
|
||||
// art both render neutral until revealed.
|
||||
bool sc_decals = ShowcaseLayerOn(i.pos.x, 256);
|
||||
// Fam 13 (transparentenvironmentreflective) carries no macro term.
|
||||
if (overlay.z > 0.0 && fam < 12.5) {
|
||||
if (overlay.z > 0.0 && fam < 12.5 && sc_decals) {
|
||||
float3 mo = macro.Sample(smp, i.uv * overlay.x).rgb;
|
||||
ov = saturate((mo - 0.5) * overlay.y + 0.5);
|
||||
}
|
||||
if (fam > 2.5 && fam < 4.5 && overlay.w > 0.5) {
|
||||
if (fam > 2.5 && fam < 4.5 && overlay.w > 0.5 && sc_decals) {
|
||||
// overlay.w == 0 = art unresolved (white fallback alpha 1 would
|
||||
// whitewash the whole surface).
|
||||
float4 dk = overlay.w > 1.5 ? decal_art.Sample(smp, i.uv3)
|
||||
|
|
@ -553,7 +568,10 @@ float4 ShadePixel(VSOut i) {
|
|||
// macro overlay: the weathering applies over the paint too
|
||||
// (ApplyOverlay(cOverlay, ApplyDecal(...)); the old order left the paint
|
||||
// unweathered, the too-white ramp stencils).
|
||||
if (overlay.w > 0.0) {
|
||||
// Decals-&-grime build-up layer (bit 256): art and macro weathering both
|
||||
// render neutral until revealed.
|
||||
bool sc_decals = ShowcaseLayerOn(i.pos.x, 256);
|
||||
if (overlay.w > 0.0 && sc_decals) {
|
||||
// overlay.w == 2 marks environment.decal_tileable: the art tiles across
|
||||
// the surface (rock/cliff faces) and must WRAP; clamp stretched the
|
||||
// border texels into giant streaks. Single placements clamp (their
|
||||
|
|
@ -569,7 +587,8 @@ float4 ShadePixel(VSOut i) {
|
|||
// linear (squared) color space, so the gamma-space equivalent is
|
||||
// sqrt(m); a direct multiply doubles the darkening (harsh black
|
||||
// patchwork vs the emulated subtle weathering).
|
||||
if (overlay.z > 0.0 && misc.x < 1.5) { // water reuses overlay.z (ripple map flag)
|
||||
if (overlay.z > 0.0 && misc.x < 1.5 && // water reuses overlay.z (ripple map flag)
|
||||
sc_decals) {
|
||||
float4 m = macro.Sample(smp, i.uv * overlay.x);
|
||||
albedo.rgb *= lerp(float3(1.0, 1.0, 1.0), sqrt(m.rgb), overlay.y * m.a);
|
||||
}
|
||||
|
|
@ -701,8 +720,22 @@ float4 ShadePixel(VSOut i) {
|
|||
return float4(PassGamma(lit), 1.0);
|
||||
}
|
||||
float4 ps_main(VSOut i) : SV_Target {
|
||||
float4 c = ShadePixel(i);
|
||||
int mask = ShowcaseMask(i.pos.x);
|
||||
// Dynamic-entities build-up layer (bit 512): entity draws (tint.a < 0,
|
||||
// staged CPU-side from DrawItem::dyn_entity) drop out entirely until the
|
||||
// bit reveals on this pixel's side of the split; no color and no depth,
|
||||
// so the world behind them renders as if they were never published.
|
||||
if (mask >= 0 && (mask & 512) == 0 && tint.a < -0.5) {
|
||||
clip(-1.0);
|
||||
return float4(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
float4 c = ShadePixel(i);
|
||||
// Blackout stage (bit 1024, the showcase's opening/closing bookends and
|
||||
// the recording cut marker): every draw renders black, keeping its own
|
||||
// alpha so coverage, alpha test and blend routing stay intact.
|
||||
if (mask >= 0 && (mask & 1024) != 0) {
|
||||
return float4(0.0, 0.0, 0.0, c.a);
|
||||
}
|
||||
if (mask < 0 || (mask & 4) != 0) {
|
||||
return c; // showcase off, or the full material layer is revealed
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -11,7 +11,10 @@ cbuffer C : register(b0) {
|
|||
float4 vp1;
|
||||
float4 vp2;
|
||||
float4 vp3;
|
||||
float4 intensity; // i_intensity as staged (x = default gain, y = darken gain)
|
||||
float4 intensity; // x/y = i_intensity as staged (default/darken gain).
|
||||
// zw = showcase blackout gate, staged CPU-side over the
|
||||
// guest row's unused lanes: z = split x (output pixels),
|
||||
// w = left|right visibility bits (3 = fully visible).
|
||||
};
|
||||
Texture2D<float4> tex : register(t0);
|
||||
SamplerState smp : register(s0);
|
||||
|
|
@ -48,12 +51,25 @@ float3 SplineOut(float3 c) {
|
|||
return c;
|
||||
#endif
|
||||
}
|
||||
// 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.
|
||||
bool SplineVisible(float px_x) {
|
||||
int bits = (int)(intensity.w + 0.5);
|
||||
return ((px_x < intensity.z ? 1 : 2) & bits) != 0;
|
||||
}
|
||||
float4 ps_default(VSOut i) : SV_Target {
|
||||
if (!SplineVisible(i.pos.x)) {
|
||||
clip(-1.0);
|
||||
}
|
||||
float4 c = tex.Sample(smp, i.uv);
|
||||
float3 sq = c.rgb * c.rgb * (intensity.x * i.fade / max(c.a, 1.0 / 255.0));
|
||||
return float4(SplineOut(sqrt(abs(sq))), 1.0);
|
||||
}
|
||||
float4 ps_darken(VSOut i) : SV_Target {
|
||||
if (!SplineVisible(i.pos.x)) {
|
||||
clip(-1.0);
|
||||
}
|
||||
float4 c = tex.Sample(smp, i.uv);
|
||||
float3 rgb = sqrt(abs(c.rgb * c.rgb * intensity.y));
|
||||
float a = sqrt(abs(c.a * intensity.y * i.fade));
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/graphics/native_guest_renderer.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/ui/presenter.h>
|
||||
|
||||
#include "skate3_native_scene.h"
|
||||
|
|
@ -254,6 +255,14 @@ void SetMaxQuality(bool enable) {
|
|||
s.active = enable;
|
||||
}
|
||||
|
||||
REXCVAR_DEFINE_INT32(
|
||||
skate3_native_render_scene_maxq_cycle, 0, "Skate 3",
|
||||
"DEBUG: auto-toggle MAX QUALITY every N native-scene frames (0 = off). "
|
||||
"Stress-tests the hot pipeline/target/shadow-map rebuild path; pair "
|
||||
"with Vulkan validation layers when hunting stale bindings.")
|
||||
.range(0, 1000000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
void DrawMaxQualityToggle() {
|
||||
bool on = s_max_quality.active;
|
||||
if (ImGui::Checkbox("MAX QUALITY (photo mode)", &on)) {
|
||||
|
|
@ -1096,6 +1105,24 @@ void DrawCachesSection() {
|
|||
|
||||
} // namespace
|
||||
|
||||
void MaxQualityAutoCycle(uint64_t frame_number) {
|
||||
const int32_t period = REXCVAR_GET(skate3_native_render_scene_maxq_cycle);
|
||||
if (period <= 0) {
|
||||
return;
|
||||
}
|
||||
static uint64_t next_frame = 0;
|
||||
if (next_frame == 0) {
|
||||
next_frame = frame_number + uint64_t(period);
|
||||
return;
|
||||
}
|
||||
if (frame_number >= next_frame) {
|
||||
next_frame = frame_number + uint64_t(period);
|
||||
SetMaxQuality(!s_max_quality.active);
|
||||
REXLOG_INFO("native-scene: max-quality auto-cycle -> {}",
|
||||
s_max_quality.active ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
void NativeDebugDialog::Show() {
|
||||
visible_ = true;
|
||||
SetDrawActive(true);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@
|
|||
|
||||
namespace skate3 {
|
||||
|
||||
// Debug stress driver: when skate3_native_render_scene_maxq_cycle > 0,
|
||||
// toggles the max-quality photo mode every N native-scene frames. Called
|
||||
// once per frame from the scene renderer (works without the F12 dialog).
|
||||
void MaxQualityAutoCycle(uint64_t frame_number);
|
||||
|
||||
class NativeDebugDialog final : public rex::ui::ImGuiDialog {
|
||||
public:
|
||||
explicit NativeDebugDialog(rex::ui::ImGuiDrawer* drawer) : ImGuiDialog(drawer) {}
|
||||
|
|
|
|||
|
|
@ -294,10 +294,13 @@ REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_haze_density, 0.005,
|
|||
.range(0.0, 0.05)
|
||||
.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 "
|
||||
"Run the graphics build-up showcase: the screen snaps to "
|
||||
"black, flat clay geometry wipes in, the scene is rebuilt "
|
||||
"layer by layer (each layer revealed by a vertical split "
|
||||
"wiping across the screen with both sides rendered live), "
|
||||
"and the run closes by wiping back to black. The black "
|
||||
"bookends double as cut markers for screen recordings. "
|
||||
"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 "
|
||||
|
|
@ -310,10 +313,16 @@ REXCVAR_DEFINE_STRING(
|
|||
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\".")
|
||||
"position. Tokens: albedo, lighting, materials, decals, dyn, 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). decals (graffiti/paint art + macro weathering; renders on "
|
||||
"the full material look, so place it after materials) and dyn (dynamic "
|
||||
"entities: characters, props, cloth) HIDE their content until revealed; "
|
||||
"left out or disabled, that content renders from clay onward as usual. "
|
||||
"The CSM atlas is shared by both sides of the split, so a dyn step "
|
||||
"AFTER shadows shows entity shadows before their owners; order dyn "
|
||||
"first. Example: \"albedo,lighting,materials,decals,dyn,shadows+ao\".")
|
||||
.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 "
|
||||
|
|
@ -4100,6 +4109,7 @@ uint32_t CaptureDynamicState(uint8_t* base, uint32_t ctx, bool world_path,
|
|||
}
|
||||
item.shadow_caster =
|
||||
ortho_submit || (ctx != 0 && g_frame_ortho_ctx.count(ctx) != 0);
|
||||
item.dyn_entity = true;
|
||||
g_frame_dynitems.push_back(std::move(item));
|
||||
const size_t index = g_frame_dynitems.size() - 1;
|
||||
if (g_frame_dynitems[index].pending ||
|
||||
|
|
@ -4204,6 +4214,7 @@ uint32_t CaptureClothDraw(uint8_t* base, uint32_t r4, uint32_t r5, uint32_t r6,
|
|||
|
||||
// Distinct draw ranges within one ring buffer are distinct garments.
|
||||
*out_key = garment_key;
|
||||
item.dyn_entity = true;
|
||||
std::lock_guard<std::mutex> lock(g_palette_mutex);
|
||||
g_frame_dynitems.push_back(std::move(item));
|
||||
return uint32_t(g_frame_dynitems.size());
|
||||
|
|
|
|||
|
|
@ -279,6 +279,11 @@ struct DrawItem {
|
|||
// only their exact cached decodes: no guest-memory reads, no heals, no
|
||||
// miss enqueues.
|
||||
bool retained = false;
|
||||
// Captured through the dynamic-entity paths (characters, props, cloth,
|
||||
// quad-list garments) rather than the world sort lists. Set at capture so
|
||||
// every later copy (merges, rescues, retention re-publishes) inherits it;
|
||||
// the build-up showcase's dynamic-entities layer keys off it.
|
||||
bool dyn_entity = false;
|
||||
// Bone palette snapshot taken on the game thread: raw staged rows, 3
|
||||
// float4s per bone (column-vector affine [R | t], model -> world). The
|
||||
// guest staging bank is reused draw to draw, hence the copy.
|
||||
|
|
@ -443,6 +448,15 @@ struct SubmitRecord {
|
|||
// 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.
|
||||
// The decals and dyn layers are SUBTRACTIVE: their content is part of the
|
||||
// normal frame and the layer hides it until revealed. When such a layer is
|
||||
// left out of the order (or '-'disabled), the sequencer folds its bit into
|
||||
// every step so the run matches the non-showcase frame from clay onward.
|
||||
// The decal/grime composite only renders on the full material look, so the
|
||||
// decals layer reads best placed after materials.
|
||||
// Bit 1024 (blackout) is NOT an orderable layer: the sequencer brackets
|
||||
// every run with it (snap to black at start, wipe to black at the end) as
|
||||
// the recording cut marker.
|
||||
struct ShowcaseLayer {
|
||||
const char* token;
|
||||
uint32_t bit;
|
||||
|
|
@ -452,6 +466,8 @@ inline constexpr ShowcaseLayer kShowcaseLayers[] = {
|
|||
{"albedo", 1u, "albedo textures"},
|
||||
{"lighting", 2u, "baked lighting"},
|
||||
{"materials", 4u, "materials & surface detail"},
|
||||
{"decals", 256u, "decals & grime"},
|
||||
{"dyn", 512u, "dynamic entities"},
|
||||
{"shadows", 8u, "dynamic shadows"},
|
||||
{"ao", 16u, "ambient occlusion"},
|
||||
{"ssr", 32u, "reflections"},
|
||||
|
|
@ -460,8 +476,10 @@ inline constexpr ShowcaseLayer kShowcaseLayers[] = {
|
|||
};
|
||||
inline constexpr size_t kShowcaseLayerCount =
|
||||
sizeof(kShowcaseLayers) / sizeof(kShowcaseLayers[0]);
|
||||
// Bits of the subtractive layers (see above).
|
||||
inline constexpr uint32_t kShowcaseSubtractiveMask = 256u | 512u;
|
||||
inline constexpr const char* kShowcaseOrderDefault =
|
||||
"albedo,lighting,materials,shadows+ao,ssr,vol+bloom";
|
||||
"albedo,lighting,materials,decals,dyn,shadows+ao,ssr,vol+bloom";
|
||||
|
||||
bool Enabled();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// it through the RHI (D3D12 / Vulkan) into the guest output texture. Shared
|
||||
// cross-thread state lives in skate3_native_scene_state.h.
|
||||
|
||||
#include "skate3_native_debug_dialog.h"
|
||||
#include "skate3_native_scene.h"
|
||||
|
||||
#include "generated/skate3_init.h"
|
||||
|
|
@ -69,6 +70,7 @@ REXCVAR_DECLARE(bool, skate3_native_render_scene_boot_native);
|
|||
REXCVAR_DECLARE(bool, skate3_native_render_scene_cas_yield);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_char_shadow_exact);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_decals);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_dynamic_items);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_dynobj_v2);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_entity_fade);
|
||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_fmv_native);
|
||||
|
|
@ -3282,9 +3284,17 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
|||
g_r.world_shadow_in_srv = false;
|
||||
g_r.world_shadow_primed = false;
|
||||
}
|
||||
const uint32_t want_static_size = uint32_t(std::clamp(
|
||||
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) {
|
||||
if (device->backend() == nrhi::Backend::kD3D12) {
|
||||
// The 3-tile cascade row must fit D3D12's 16384 2D-texture width cap:
|
||||
// per-tile sizes above 5461 cannot allocate there (the 8192 setting
|
||||
// asks for 24576x8192), while desktop Vulkan allows 32768-wide.
|
||||
while (want_static_size * 3 > 16384) {
|
||||
want_static_size /= 2;
|
||||
}
|
||||
}
|
||||
if (g_r.static_sun != nullptr && g_r.static_sun_requested != 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).
|
||||
|
|
@ -3302,6 +3312,7 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
|||
// tiles side by side: inner (r/6, centimeter contact detail with
|
||||
// useful reach), mid (r/2) and far (full radius, large-caster
|
||||
// coverage); size is per tile.
|
||||
g_r.static_sun_requested = want_static_size;
|
||||
g_r.static_sun_size = want_static_size;
|
||||
nrhi::TextureDesc desc;
|
||||
desc.width = g_r.static_sun_size * 3;
|
||||
|
|
@ -3312,6 +3323,19 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
|
|||
desc.clear_color[0] = 1.0f; // depth: far = lit
|
||||
desc.clear_color[1] = 1.0f;
|
||||
g_r.static_sun = device->CreateTexture(desc);
|
||||
// Allocation-failure fallback (VRAM pressure, driver limits): a
|
||||
// coarser static sun map beats marking the whole renderer failed and
|
||||
// dropping the session to emulated output.
|
||||
while (g_r.static_sun == nullptr && g_r.static_sun_size > 1024) {
|
||||
g_r.static_sun_size /= 2;
|
||||
desc.width = g_r.static_sun_size * 3;
|
||||
desc.height = g_r.static_sun_size;
|
||||
REXLOG_WARN(
|
||||
"native-scene: static sun-shadow map allocation failed, retrying "
|
||||
"at {}x{}",
|
||||
g_r.static_sun_size * 3, g_r.static_sun_size);
|
||||
g_r.static_sun = device->CreateTexture(desc);
|
||||
}
|
||||
if (g_r.static_sun != nullptr) {
|
||||
nrhi::TextureViewDesc vd;
|
||||
vd.mip_levels = 1;
|
||||
|
|
@ -3571,6 +3595,23 @@ bool EnsureOutputSizedTargets(const NativeGuestOutputRenderContext& context) {
|
|||
g_r.targets_scene_fmt != want_scene_fmt ||
|
||||
g_r.targets_msaa != g_r.msaa) {
|
||||
if (g_r.depth) {
|
||||
// The AO/SSR/volumetric scene-depth SRVs alias this texture and
|
||||
// re-point on pointer identity; heap reuse can hand the NEW depth
|
||||
// texture the OLD one's address, so that comparison must never be
|
||||
// the only invalidation. Retire the views with the buffer they view:
|
||||
// a stale view kept sampling the retired depth image (still in
|
||||
// attachment layout), a GPU fault and device hang on Vulkan.
|
||||
nrhi::TextureView** depth_views[3] = {
|
||||
&g_r.ao_depth_srv, &g_r.ssr_depth_srv, &g_r.vol_depth_srv};
|
||||
nrhi::Texture** depth_views_of[3] = {
|
||||
&g_r.ao_depth_srv_of, &g_r.ssr_depth_srv_of, &g_r.vol_depth_srv_of};
|
||||
for (int v = 0; v < 3; ++v) {
|
||||
if (*depth_views[v] != nullptr) {
|
||||
g_r.device->DestroyDeferred(*depth_views[v]);
|
||||
*depth_views[v] = nullptr;
|
||||
}
|
||||
*depth_views_of[v] = nullptr;
|
||||
}
|
||||
g_r.device->DestroyDeferred(g_r.depth);
|
||||
g_r.depth = nullptr;
|
||||
}
|
||||
|
|
@ -6630,7 +6671,8 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
|||
}
|
||||
// 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).
|
||||
// live in the HDR tonemap, so they need the HDR chain). The subtractive
|
||||
// layers need their content published/composited at all.
|
||||
const auto layer_available = [&](uint32_t bit) -> bool {
|
||||
switch (bit) {
|
||||
case 8u:
|
||||
|
|
@ -6644,6 +6686,10 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
|||
REXCVAR_GET(skate3_native_render_scene_haze));
|
||||
case 128u:
|
||||
return hdr_on && REXCVAR_GET(skate3_native_render_scene_bloom);
|
||||
case 256u:
|
||||
return REXCVAR_GET(skate3_native_render_scene_decals);
|
||||
case 512u:
|
||||
return REXCVAR_GET(skate3_native_render_scene_dynamic_items);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
|
@ -6658,8 +6704,14 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
|||
// 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.
|
||||
// left empty are skipped. The run opens ON black (mask bit 1024; a
|
||||
// snap, not a wipe - see the step-0 timing below), wipes clay in from
|
||||
// it, and closes by wiping back out to black. The >= hold_s stretches
|
||||
// of pure black double as machine-findable cut markers for screen
|
||||
// recordings (local/scripts/cut_showcase_clips.py pairs them with
|
||||
// ffmpeg blackdetect).
|
||||
s.steps.clear();
|
||||
s.steps.push_back({float(256u + 1024u), "black (start marker)"});
|
||||
s.steps.push_back({256.0f, "clay geometry"});
|
||||
uint32_t cum = 0;
|
||||
const std::string order =
|
||||
|
|
@ -6715,14 +6767,28 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
|||
s.steps.push_back({float(256u + cum), std::move(label)});
|
||||
}
|
||||
}
|
||||
// Subtractive layers (decals/grime, dynamic entities) left out of the
|
||||
// order or '-'disabled are not build-up beats: fold their bits into
|
||||
// every step, clay included, so their content renders from the first
|
||||
// frame exactly as in the non-showcase frame. An UNAVAILABLE layer
|
||||
// needs no fold; its feature cvar already removes the content itself.
|
||||
const uint32_t always = kShowcaseSubtractiveMask & avail_mask & ~cum;
|
||||
if (always != 0u) {
|
||||
for (Step& st : s.steps) {
|
||||
st.value = float(256u + ((uint32_t(st.value) - 256u) | always));
|
||||
}
|
||||
}
|
||||
// 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).
|
||||
// stop counting once materials is in; the subtractive bits are either
|
||||
// revealed steps or folded in above, so they never force the step).
|
||||
const bool covered =
|
||||
(cum & 4u) != 0 && ((avail_mask & ~cum) & 0xF8u) == 0u;
|
||||
if (!covered) {
|
||||
s.steps.push_back({0.0f, "full render"});
|
||||
}
|
||||
// Closing blackout bookend (the end-of-run cut marker).
|
||||
s.steps.push_back({float(256u + 1024u), "black (end marker)"});
|
||||
// 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));
|
||||
|
|
@ -6743,7 +6809,20 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
|||
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);
|
||||
// Step 0 (the blackout start bookend) SNAPS to black instead of wiping
|
||||
// from the live frame and lasts hold_s only: the recording cut lands on
|
||||
// a hard black edge, and the build-up proper starts with clay wiping in
|
||||
// from black.
|
||||
int idx;
|
||||
double phase;
|
||||
if (elapsed < s.hold_s) {
|
||||
idx = 0;
|
||||
phase = s.wipe_s; // past the wipe window: hold, no split
|
||||
} else {
|
||||
const double e = elapsed - s.hold_s;
|
||||
idx = 1 + int(e / per_step);
|
||||
phase = e - double(idx - 1) * per_step;
|
||||
}
|
||||
if (idx >= int(s.steps.size())) {
|
||||
s.active = false;
|
||||
REXCVAR_SET(skate3_native_render_scene_showcase, false);
|
||||
|
|
@ -6757,7 +6836,6 @@ static void TickShowcase(uint32_t output_width, bool hdr_on) {
|
|||
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
|
||||
|
|
@ -6777,6 +6855,12 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
context.backend != NativeGuestOutputBackend::kVulkan)) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
// Debug stress driver (skate3_native_render_scene_maxq_cycle): cycles
|
||||
// the max-quality toggle to exercise the hot rebuild path.
|
||||
static uint64_t s_maxq_cycle_frame = 0;
|
||||
skate3::MaxQualityAutoCycle(++s_maxq_cycle_frame);
|
||||
}
|
||||
// While the game reports menus / loading (presence context 0x8001 == 0),
|
||||
// yield to the emulated output, EXCEPT the in-game pause menu (world
|
||||
// still publishing perspective scenes), which stays native when
|
||||
|
|
@ -8036,7 +8120,10 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
// alpha-clipped (their VS skinning branch is inert: zero weights).
|
||||
constants[33] = (bones_bound || item.ropa) ? 1.0f : 0.0f;
|
||||
constants[34] = item.unlit ? 1.0f : 0.0f;
|
||||
constants[35] = 0.0f;
|
||||
// tint.a < 0 marks dynamic-entity items for the showcase dyn layer
|
||||
// (ps_main clips them until the bit reveals); the solid-color
|
||||
// early-out only ever reads tint.a > 0.
|
||||
constants[35] = item.dyn_entity ? -1.0f : 0.0f;
|
||||
}
|
||||
constants[36] = scene.cam_pos[0];
|
||||
constants[37] = scene.cam_pos[1];
|
||||
|
|
@ -9362,6 +9449,17 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
|||
float spline_consts[20];
|
||||
std::memcpy(spline_consts, scene.view_proj, sizeof(float) * 16);
|
||||
std::memcpy(spline_consts + 16, s.consts + 149 * 4, sizeof(float) * 4);
|
||||
// intensity.zw = showcase blackout gate (the guest row's unused
|
||||
// lanes): z = split x, w = left|right visibility bits. The spline PS
|
||||
// draws outside the scene shaders, so the blackout stage must gate
|
||||
// it here or the neon lines leak over the black recording bookends.
|
||||
const auto side_visible = [](float v) {
|
||||
return v < 255.5f || ((uint32_t(v + 0.5f) - 256u) & 1024u) == 0u;
|
||||
};
|
||||
spline_consts[18] = g_r.showcase_rows[2];
|
||||
spline_consts[19] =
|
||||
float((side_visible(g_r.showcase_rows[0]) ? 1 : 0) |
|
||||
(side_visible(g_r.showcase_rows[1]) ? 2 : 0));
|
||||
cmd->SetRootConstants(0, 20, spline_consts, 0);
|
||||
cmd->SetTexture(1, spline_tex->srv);
|
||||
cmd->SetVertexBuffer(g_r.ui_ring, ui_region + ui_offset, bytes, 28);
|
||||
|
|
|
|||
|
|
@ -840,6 +840,11 @@ struct RendererState {
|
|||
bool static_sun_in_srv = false;
|
||||
bool static_sun_valid = false;
|
||||
uint32_t static_sun_size = 0;
|
||||
// The per-tile size this map was requested at (post backend-limit clamp,
|
||||
// pre allocation-failure fallback). The hot-size-change retire keys on
|
||||
// this, not static_sun_size: a map that allocated smaller than requested
|
||||
// must not be retired and re-tried every frame.
|
||||
uint32_t static_sun_requested = 0;
|
||||
float nsm_rows[12] = {};
|
||||
float nsm_depth_range = 1.0f; // meters per depth-map unit
|
||||
float nsm_radius = 1.0f; // far-tile ortho half-extent in meters
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue