scene: native photo-editor postfx (exact ucode ports) + grab readback window

This commit is contained in:
mchughalex 2026-07-13 18:25:25 +01:00
parent 020051bf32
commit 850c8d5b2d
5 changed files with 1655 additions and 12 deletions

View file

@ -13,6 +13,7 @@ set(SHADER_VARS
kOutlineShaderSource kOutlineShaderSource
kShader2dSource kShader2dSource
kShaderSplineSource kShaderSplineSource
kPhotoFxShaderSource
) )
set(SHADER_FILES set(SHADER_FILES
scene.hlsl scene.hlsl
@ -22,6 +23,7 @@ set(SHADER_FILES
outline.hlsl outline.hlsl
overlay2d.hlsl overlay2d.hlsl
spline.hlsl spline.hlsl
photo_fx.hlsl
) )
set(content "// Generated by cmake/EmbedShaders.cmake from src/native/shaders/*.hlsl.\n") set(content "// Generated by cmake/EmbedShaders.cmake from src/native/shaders/*.hlsl.\n")

View file

@ -0,0 +1,583 @@
// Photo-editor postfx chain: exact ports of the game's shaders, transcribed
// mechanically from their Xenos ucode (dumped via an F11 capture) and
// validated bit-exact against an offline ucode interpreter before HLSL
// conversion. Pass order
// mirrors the game's chain while a photo-mission photo editor is up:
// visualfx (full res) scene+accum color grade / vignette / depth->CoC
// dof_down (576x320) 4-tap alpha(CoC)-weighted downsample
// dof_mb (576x320) 9-tap motion-blur+DOF pass (motion from depth)
// dof (576x320) 9-tap variable-radius CoC blur
// uber (full res) temporal-jitter combine + grade LUT + grain
// fisheye (full res) lens warp + vignette gradient + tint -> screen
// Constants come LIVE from the game's PS/VS shadow banks captured at the
// SetPending_AluConstants hook per pass (rows c0..c31 + VS rows in c240..247);
// rows c250..c255 are the shaders' LITERAL tables, loaded by the game via
// PM4 LOAD_ALU_CONSTANT from the shader asset footers and baked here by the
// app layer (values read from a live capture).
//
// Register indices are kept IDENTICAL to the ucode. Do not simplify.
cbuffer Consts : register(b0) { float4 c[256]; };
// c[240..247] = the pass's VS c0..c7 (uvOffset/uvScale/oC1 rows).
// c[248].x = output width, .y = output height (depth pack / debug).
Texture2D t0 : register(t0);
Texture2D t1 : register(t1);
Texture2D t2 : register(t2);
Texture2D t3 : register(t3);
Texture3D t4 : register(t4);
Texture2D t6 : register(t6);
Texture2D t7 : register(t7);
SamplerState s_lin : register(s0); // linear clamp (color/gradient fetches)
SamplerState s_pt : register(s1); // point clamp (packed depth)
SamplerState s_wrap : register(s2); // linear wrap (grain)
struct VsOut {
float4 pos : SV_Position;
float4 r0 : TEXCOORD0;
float4 r1 : TEXCOORD1;
};
// Fullscreen triangle; per-pass interpolators mirror the game quad VSs
// (uv attr = screen uv; ucode: oC0 = (uv + c3.xy, uv), oC1 = uv.x*c0 +
// uv.y*c1 + c2; the offset/scale rows are the captured VS constants).
VsOut vs_offset(uint vid : SV_VertexID) {
VsOut o;
float2 t = float2((vid << 1) & 2, vid & 2);
o.pos = float4(t * float2(2, -2) + float2(-1, 1), 0, 1);
o.r0 = float4(t + c[243].xy, t);
o.r1 = t.x * c[240] + t.y * c[241] + c[242];
return o;
}
// uber: oC0 = raw uv4.
VsOut vs_raw(uint vid : SV_VertexID) {
VsOut o;
float2 t = float2((vid << 1) & 2, vid & 2);
o.pos = float4(t * float2(2, -2) + float2(-1, 1), 0, 1);
o.r0 = float4(t, t);
o.r1 = 0;
return o;
}
// fisheye: oC0.xy = uv*c3.xy + c2.xy (uvScale/uvTrans), oC0.zw = raw uv.
VsOut vs_scaled(uint vid : SV_VertexID) {
VsOut o;
float2 t = float2((vid << 1) & 2, vid & 2);
o.pos = float4(t * float2(2, -2) + float2(-1, 1), 0, 1);
o.r0 = float4(t * c[243].xy + c[242].xy, t);
o.r1 = 0;
return o;
}
// ---------------------------------------------------------------------------
// Depth pack: native D32 MSAA depth (sample 0) -> the console D24S8-as-8888
// texel layout the ported shaders reconstruct: sample.x = mid byte,
// sample.y = low byte, sample.z = stencil (0 natively), sample.w = high
// byte. dp3 with the baked weights (255/16776960, 255/256, 255/65536) then
// yields the exact 24-bit depth (visualfx c254.xyz, dof_mb c255.xyz).
#ifdef PFX_MSAA
Texture2DMS<float> depth_ms : register(t5);
#else
Texture2D<float> depth_1x : register(t5);
#endif
float4 ps_depthpack(VsOut i) : SV_Target {
#ifdef PFX_MSAA
float d = depth_ms.Load(int2(i.pos.xy), 0);
#else
float d = depth_1x.Load(int3(int2(i.pos.xy), 0));
#endif
float D = min(floor(d * 16777216.0), 16777215.0);
float hi = floor(D / 65536.0);
float mid = floor((D - hi * 65536.0) / 256.0);
float lo = D - hi * 65536.0 - mid * 256.0;
return float4(mid, lo, 0.0, hi) * (1.0 / 255.0);
}
// Plain bilinear blit: the quarter-res accumulation update (the game feeds
// visualfx tf3 from a motion-accumulation buffer; natively it is approximated
// per skate3_native_render_scene_photo_native_accum).
float4 ps_blit(VsOut i) : SV_Target {
return t0.SampleLevel(s_lin, i.r0.zw, 0);
}
// ---------------------------------------------------------------------------
// postfx_visualfxPS (bit-exact transcription). t0 = scene,
// t1 = packed depth, t3 = quarter-res accumulation buffer.
float4 ps_visualfx(VsOut i) : SV_Target {
float2 uv = i.r0.xy;
float4 r0 = i.r0;
float4 r1 = i.r1;
float4 r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0;
float4 oC0 = 0;
float nx, ny, nz, nw, dw, rx, old, r1x_old;
r2 = t1.SampleLevel(s_pt, uv, 0); // tfetch2D r2, r0.xy, tf1
r4.xyz = t0.SampleLevel(s_lin, uv, 0).xyz; // tfetch2D r4.xyz_, r0.xy, tf0
r3.xyz = t3.SampleLevel(s_lin, uv, 0).xyz; // tfetch2D r3.xyz_, r0.xy, tf3
// 7: mad r3.xyz_, r4.zxyy, c253.zzzz, r3.zxyy
nx = r4.z * c[253].z + r3.z;
ny = r4.x * c[253].z + r3.x;
nz = r4.y * c[253].z + r3.y;
r3.x = nx; r3.y = ny; r3.z = nz;
// 8: dp3 r0._y__, r2.ywxx, c254.xyzz (24-bit depth reconstruction)
r0.y = r2.y * c[254].x + r2.w * c[254].y + r2.x * c[254].z;
// 9: add r6.__zw, c2.yyyx, -c1.yyyx ; + subsc r3.___w, -c4.x, -r0.y
r6.z = c[2].y - c[1].y;
r6.w = c[2].x - c[1].x;
r3.w = r0.y - c[4].x;
// 10: mad r1.xyz_, r0.yyyy, c6.xyzz, r1.xyzz
r1.x = r0.y * c[6].x + r1.x;
r1.y = r0.y * c[6].y + r1.y;
r1.z = r0.y * c[6].z + r1.z;
// 11: mad r0._yz_, r1.zzzz, r0.zzww, r1.xxyy
ny = r1.z * r0.z + r1.x;
nz = r1.z * r0.w + r1.y;
r0.y = ny; r0.z = nz;
// 12: dp3 r0.___w, r3.xyzz, c255.xyzz ; + rcp r0.x___, r3.w
dw = r3.x * c[255].x + r3.y * c[255].y + r3.z * c[255].z;
rx = 1.0 / r3.w;
r0.w = dw;
r0.x = rx;
// 13: mad r1.xyz_, r0.wwww, c0.zyxx, -r3.xzyy
r1.x = r0.w * c[0].z + (-r3.x);
r1.y = r0.w * c[0].y + (-r3.z);
r1.z = r0.w * c[0].x + (-r3.y);
// 14: mad r4.xyz_, r1.xyzz, c0.wwww, r3.xzyy
r4.x = r1.x * c[0].w + r3.x;
r4.y = r1.y * c[0].w + r3.z;
r4.z = r1.z * c[0].w + r3.y;
// 15: mul r0.x__w, r0.xxxx, c253.xyyy
old = r0.x;
r0.x = old * c[253].x;
r0.w = old * c[253].y;
// 16: mul r0.x__w, r0.xwww, c3.xxxx ; + maxs r4.___w, c255.ww
nx = r0.x * c[3].x;
nw = r0.w * c[3].x;
r0.x = nx; r0.w = nw;
r4.w = c[255].w;
// 17: mad_sat r5.xy__, r0.wwww, c13.xyyy, c14.xyyy
r5.x = saturate(r0.w * c[13].x + c[14].x);
r5.y = saturate(r0.w * c[13].y + c[14].y);
#define DP4PERM(CR) ((CR).z * r4.x + (CR).x * r4.z + (CR).y * r4.y + (CR).w * r4.w)
// 18-24: the FX color matrix + gates
r3.x = DP4PERM(c[7]);
r3.y = DP4PERM(c[8]);
r2.x = saturate(r2.z);
r3.z = DP4PERM(c[9]);
r2.y = r2.x - c[255].w;
r3.w = DP4PERM(c[10]);
r1.y = c[5].x * r0.y;
r1.x = (r0.w >= c[1].y) ? 1.0 : 0.0;
r0.y = c[5].x * r0.z;
r6.x = DP4PERM(c[12]);
r2.z = r1.y * r1.y;
r6.y = DP4PERM(c[11]);
r2.w = r0.y * r0.y;
#undef DP4PERM
// 25: add_sat r1._y_w, r2.xxyy, r2.xxxx
r1.y = saturate(r2.x + r2.x);
r1.w = saturate(r2.y + r2.x);
// 26: mad r1.x_z_, r6.zwww, r1.xxxx, c1.yxxx
r1x_old = r1.x;
r1.x = r6.z * r1x_old + c[1].y;
r1.z = r6.w * r1x_old + c[1].x;
// 27: mul r3, r3, r5.xxxy ; + addsc r0.__z_, c253.w, r0.x
r3.x *= r5.x;
r3.y *= r5.x;
r3.z *= r5.x;
r3.w *= r5.y;
r0.z = c[253].w + r0.x;
// 28: mad r3._yz_, r6.xxyy, r5.yyyy, r3.zzyy
ny = r6.x * r5.y + r3.z;
nz = r6.y * r5.y + r3.y;
r3.y = ny; r3.z = nz;
// 29: add r0._y__, r1.xxxx, -r0.wwww ; + adds r0.___w, r2.zw
r0.y = r1.x + (-r0.w);
r0.w = r2.z + r2.w;
// 30: mul_sat r0.xy__, r0.yzzz, r1.zwww ; + adds r3.x___, r3.xw
nx = saturate(r0.y * r1.z);
ny = saturate(r0.z * r1.w);
r0.x = nx; r0.y = ny;
r3.x = r3.x + r3.w;
// 31: add r4.xyz_, r4.zxyy, -r3.xyzz ; + adds r0.__z_, r2.xx
nx = r4.z - r3.x;
ny = r4.x - r3.y;
nz = r4.y - r3.z;
r4.x = nx; r4.y = ny; r4.z = nz;
r0.z = r2.x + r2.x;
// 32: add r4.___w, -r0.yyyy, c255.wwww ; + mulsc r0._y__, c16.x, r1.y
r4.w = (-r0.y) + c[255].w;
r0.y = c[16].x * r1.y;
// 33: mul r1, r0.yyyw, r4.xzyw ; + subsc_sat r0.__z_, c255.w, r0.z
r1.x = r0.y * r4.x;
r1.y = r0.y * r4.z;
r1.z = r0.y * r4.y;
r1.w = r0.w * r4.w;
r0.z = saturate(c[255].w - r0.z);
// 34: mul_sat r0.__z_, r1.wwww, r0.zzzz ; + mulsc r0._y__, c255.z, r0.x
r0.z = saturate(r1.w * r0.z);
r0.y = c[255].z * r0.x;
// 35: add oC0.xyz_, r3.xzyy, r1.xyzz
oC0.x = r3.x + r1.x;
oC0.y = r3.z + r1.y;
oC0.z = r3.y + r1.z;
// 36: max r0.__z_, r0.zzzz, c254.wwww ; + mulsc r0.x___, c15.x, r0.y
r0.z = max(r0.z, c[254].w);
r0.x = c[15].x * r0.y;
// 37: max_sat oC0.___w, r0.zzzz, r0.xxxx
oC0.w = saturate(max(r0.z, r0.x));
return oC0;
}
// ---------------------------------------------------------------------------
// bloom_dof_motionblur_dof_dowsamplePS (bit-exact): 4-tap
// alpha-weighted downsample of the visualfx output into the DOF half buffer.
float4 ps_dof_down(VsOut i) : SV_Target {
float4 r0 = i.r0;
float4 r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0;
float4 c255 = c[255];
r1.y = r0.y;
r1.z = r0.x;
r1.x = r0.x + c255.x;
r1.w = r0.y + c255.y;
r4 = t0.SampleLevel(s_lin, float2(r1.x, r1.y), 0);
r5 = t0.SampleLevel(s_lin, float2(r1.z, r1.w), 0);
r1 = t0.SampleLevel(s_lin, float2(r1.x, r1.w), 0);
r2 = t0.SampleLevel(s_lin, float2(r0.x, r0.y), 0);
r3.x = r2.w + c255.w;
r0.x = r1.x * r1.w;
r0.y = r1.z * r1.w;
r0.z = r1.y * r1.w;
{
float ny = r5.x * r5.w + r0.x;
float nz = r5.y * r5.w + r0.z;
float nw = r5.z * r5.w + r0.y;
r0.y = ny; r0.z = nz; r0.w = nw;
}
r0.x = r3.x + r4.w;
r0.x = r0.x + r5.w;
{
float ny = r4.x * r4.w + r0.y;
float nz = r4.z * r4.w + r0.w;
float nw = r4.y * r4.w + r0.z;
r0.y = ny; r0.z = nz; r0.w = nw;
}
r1.x = r3.x * r2.x + r0.y;
r1.y = r3.x * r2.y + r0.w;
r1.z = r3.x * r2.z + r0.z;
r0.x = r0.x + r1.w;
r0.y = 1.0 / r0.x;
float4 oC0;
oC0.x = r1.x * r0.y;
oC0.y = r1.y * r0.y;
oC0.z = r1.z * r0.y;
oC0.w = c255.z * r0.x;
return oC0;
}
// ---------------------------------------------------------------------------
// bloom_dof_tap9dofMotionBlurPS (bit-exact): 9-tap motion-blur +
// DOF pass. t0 = DOF half buffer, t1 = packed depth (motion reconstructed
// from depth + camera rows in c1).
float4 ps_dof_mb(VsOut i) : SV_Target {
float4 r0 = i.r0;
float4 r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r11 = 0;
float ps = 0;
float4 c254 = c[254], c255 = c[255];
// /*6*/
r2.x = r0.x + c[7].x; r2.y = r0.y + c[7].y;
// /*7*/ tfetch2D r1.___w, r2.xy, tf0
r1.w = t0.SampleLevel(s_lin, r2.xy, 0).w;
// /*8*/ tfetch2D r2.xyw_, r0.xy, tf1 (dest xyw_ -> r2.x,r2.y,r2.z <- s.x,s.y,s.w)
{ float4 vel = t1.SampleLevel(s_pt, r0.xy, 0); r2.x = vel.x; r2.y = vel.y; r2.z = vel.w; }
// /*9*/ dp3 r2._y__, r2.yzxx, c255.xyzz + mulsc r2.x, c2.x, r1.w
{ float dp = r2.y * c255.x + r2.z * c255.y + r2.x * c255.z; r2.y = dp; }
r2.x = c[2].x * r1.w;
// /*10*/
r2.x = max(r2.x, c254.y);
// /*11*/ mad r1.xyz_, r2.yyyy, c1.xzyy, r1.xzyy (r1.xyz==0)
{ float nx = r2.y * c[1].x + r1.x, ny = r2.y * c[1].z + r1.z, nz = r2.y * c[1].y + r1.y; r1.x = nx; r1.y = ny; r1.z = nz; }
// /*12*/ mad r1.xy__, r1.yyyy, r0.zwww, r1.xzzz
{ float nx = r1.y * r0.z + r1.x, ny = r1.y * r0.w + r1.z; r1.x = nx; r1.y = ny; }
// /*13*/ mul r1._yz_, r1.xxyy, c0.xxxx
{ float ny = r1.x * c[0].x, nz = r1.y * c[0].x; r1.y = ny; r1.z = nz; }
// /*14*/ dp2add r0.___w, r1.yzzz, r1.yzzz, c255.wwww
r0.w = r1.y * r1.y + r1.z * r1.z + c255.w;
// /*15*/ add r1.x, r1.y, -r2.x + sqrt_sat r0.w, |r0.w|
r1.x = r1.y - r2.x;
r0.w = saturate(sqrt(abs(r0.w)));
// /*16*/ mul r1._yz_, r1.zzxx, r0.wwww + addsc r0.w, c254.z, r0.w
{ float ny = r1.z * r0.w, nz = r1.x * r0.w; r1.y = ny; r1.z = nz; }
r0.w = c254.z + r0.w;
// /*17*/
r1.x = r1.z + r2.x;
// /*18-27*/ tap centers
r2.x = r1.x * c[11].w + r0.x; r2.y = r1.y * c[11].w + r0.y;
r4.xy = r1.xy * c[9].w;
r6.xy = r1.xy * c[7].w;
r5.xy = r1.xy * c[5].w; r4.z = c[10].w * r1.x;
r3.xy = r1.xy * c[3].w; r6.z = c[8].w * r1.x;
r3.z = r1.x * c[4].w; r3.w = r1.y * c[4].w; r5.z = c[6].w * r1.x;
r3 += r0.xyxy; r5.w = c[6].w * r1.y;
r5 += r0.xyxy; r6.w = c[8].w * r1.y;
r6 += r0.xyxy; r4.w = c[10].w * r1.y;
r1 = r4 + r0.xyxy;
// /*28-37*/ taps
r4 = t0.SampleLevel(s_lin, r1.zw, 0);
r1 = t0.SampleLevel(s_lin, r1.xy, 0);
float4 r12 = t0.SampleLevel(s_lin, r6.zw, 0);
float4 r10 = t0.SampleLevel(s_lin, r6.xy, 0);
float4 r9 = t0.SampleLevel(s_lin, r5.zw, 0);
float4 r8 = t0.SampleLevel(s_lin, r5.xy, 0);
float4 r7 = t0.SampleLevel(s_lin, r3.zw, 0);
r5 = t0.SampleLevel(s_lin, r3.xy, 0);
r2 = t0.SampleLevel(s_lin, r2.xy, 0);
r0.x = t0.SampleLevel(s_lin, r0.xy, 0).w; // /*37*/ r0.w___ -> r0.x <- s.w
// /*38-49*/ coc weights (r0.w = base blur radius term)
r3.x = saturate(r0.w + c[9].z); r0.y = saturate(c[10].z + r0.w);
r3.y = saturate(r0.w + c[11].z); r0.z = saturate(c[3].z + r0.w);
r3.w = saturate(r3.y * r2.w + r0.w);
r6.x = r0.z * r5.w; r0.z = saturate(c[4].z + r0.w);
r6.y = r0.z * r7.w; r0.z = saturate(c[5].z + r0.w);
r6.z = r0.z * r8.w; r0.z = saturate(c[6].z + r0.w);
r6.w = r0.z * r9.w; r0.z = saturate(c[7].z + r0.w);
r11.x = r0.z * r10.w; r0.z = saturate(c[8].z + r0.w);
r11.z = r3.x * r1.w; ps = r0.z; // maxs r0._,r0.zz
r11.w = r0.y * r4.w; r11.y = r12.w * ps; ps = r12.w * ps;
r11 = saturate(r0.w + r11); ps = r0.x; // maxs r0.x,r0.xx
r6 = saturate(r0.w + r6); ps = r11.w; // maxs r0._,r11.ww
// /*50-62*/ weighted color accumulation
r5.w = (r6.w + r6.z + r6.x + r6.y) * c254.x; r3.x = r4.x * ps; ps = r4.x * ps;
{ float nx = r11.z * r1.x, ny = r11.z * r1.y, nz = r11.z * r1.z; r1.x = nx; r1.y = ny; r1.z = nz; } ps = r11.w;
r1.w = (r11.w + r11.z + r11.y + r11.x) * c254.x; r3.y = r4.y * ps; ps = r4.y * ps;
r0.y = r11.y * r12.x; r0.z = r11.y * r12.y; r0.w = r11.y * r12.z; ps = r11.w;
// /*54*/ mad r0._yzw, r11.xxxx, r10.xxzy, r0.yywz
{ float ny = r11.x * r10.x + r0.y, nz = r11.x * r10.z + r0.w, nw = r11.x * r10.y + r0.z; r0.y = ny; r0.z = nz; r0.w = nw; }
// /*55*/ mad r0._yzw, r6.wwww, r9.xxyz, r0.yywz
{ float ny = r6.w * r9.x + r0.y, nz = r6.w * r9.y + r0.w, nw = r6.w * r9.z + r0.z; r0.y = ny; r0.z = nz; r0.w = nw; }
// /*56*/ mad r0._yzw, r6.zzzz, r8.xxzy, r0.yywz
{ float ny = r6.z * r8.x + r0.y, nz = r6.z * r8.z + r0.w, nw = r6.z * r8.y + r0.z; r0.y = ny; r0.z = nz; r0.w = nw; }
// /*57*/ mad r0._yzw, r6.yyyy, r7.xxyz, r0.yywz
{ float ny = r6.y * r7.x + r0.y, nz = r6.y * r7.y + r0.w, nw = r6.y * r7.z + r0.z; r0.y = ny; r0.z = nz; r0.w = nw; }
// /*58*/ mad r5.xyz_, r6.xxxx, r5.xzyy, r0.ywzz
{ float nx = r6.x * r5.x + r0.y, ny = r6.x * r5.z + r0.w, nz = r6.x * r5.y + r0.z; r5.x = nx; r5.y = ny; r5.z = nz; }
// /*59*/ add r1, r5.xzyw, r1 + muls_prev r3.z, r4.z
r1 = float4(r5.x + r1.x, r5.z + r1.y, r5.y + r1.z, r5.w + r1.w);
r3.z = r4.z * ps; ps = r4.z * ps;
// /*60*/ add r1, r1.xzyw, r3.xzyw + maxs r0.y, c254.ww
r1 = float4(r1.x + r3.x, r1.z + r3.z, r1.y + r3.y, r1.w + r3.w);
r0.y = c254.w; ps = c254.w;
// /*61*/ rcp r0.z, r1.w
r0.z = 1.0 / r1.w;
// /*62*/ mad r1.xyz_, r3.wwww, r2.xyzz, r1.xzyy
{ float nx = r3.w * r2.x + r1.x, ny = r3.w * r2.y + r1.z, nz = r3.w * r2.z + r1.y; r1.x = nx; r1.y = ny; r1.z = nz; }
// /*63*/ mul oC0.xyz_, r1.xyzz, r0.zzzz + maxs oC0.w, r0.xy
float4 oC0;
oC0.x = r1.x * r0.z;
oC0.y = r1.y * r0.z;
oC0.z = r1.z * r0.z;
oC0.w = max(r0.x, r0.y);
return oC0;
}
// ---------------------------------------------------------------------------
// bloom_dof_tap9dofPS (bit-exact): 9-tap variable-radius CoC blur.
float4 ps_dof(VsOut i) : SV_Target {
float4 r0 = i.r0;
float4 r2 = 0, r3 = 0, r4 = 0, r5 = 0;
float4 r1 = t0.SampleLevel(s_lin, float2(r0.x, r0.y), 0);
r0.z = c[0].x * r1.w;
r0.z = max(r0.z, c[255].x);
float rz = r0.z;
r5.xy = rz * c[8].xy;
r4.xy = rz * c[6].xy;
r3.xy = rz * c[3].xy; r5.z = c[9].x * rz;
r2.xy = rz * c[1].xy; r4.z = c[7].x * rz;
r2.z = rz * c[2].x; r2.w = rz * c[2].y; r3.z = c[4].x * rz;
r2 += r0.xyxy; r3.w = c[4].y * rz;
r3 += r0.xyxy; r4.w = c[7].y * rz;
r4 += r0.xyxy; r5.w = c[9].y * rz;
r0 = r5 + r0.xyxy;
float4 r12 = t0.SampleLevel(s_lin, r0.zw, 0);
float4 r11 = t0.SampleLevel(s_lin, r0.xy, 0);
float4 r10 = t0.SampleLevel(s_lin, r4.zw, 0);
float4 r9 = t0.SampleLevel(s_lin, r4.xy, 0);
float4 r8 = t0.SampleLevel(s_lin, r3.zw, 0);
float4 r7 = t0.SampleLevel(s_lin, r3.xy, 0);
float4 r6 = t0.SampleLevel(s_lin, r2.zw, 0);
r5 = t0.SampleLevel(s_lin, r2.xy, 0);
r3.y = r8.w * c[4].z; r0.x = r7.w;
r3.x = r1.w * c[5].z; r3.z = c[3].z * r0.x;
r2.z = r10.w * c[7].z; r0.y = r9.w;
r2.x = r12.w * c[9].z; r0.x = r11.w;
r4.x = r2.x * r12.x; r4.y = r2.x * r12.y; r4.z = r2.x * r12.z;
r2.y = c[8].z * r0.x;
{
float nx = r2.y * r11.x, nz = r2.y * r11.y, nw = r2.y * r11.z;
r0.x = nx; r0.z = nz; r0.w = nw;
r2.w = c[6].z * r0.y;
}
{ float nx = r2.z * r10.x + r0.x, ny = r2.z * r10.z + r0.w, nz = r2.z * r10.y + r0.z; r0.x = nx; r0.y = ny; r0.z = nz; }
{ float nx = r2.w * r9.x + r0.x, ny = r2.w * r9.y + r0.z, nz = r2.w * r9.z + r0.y; r0.x = nx; r0.y = ny; r0.z = nz; }
{ float nx = r3.x * r1.x + r0.x, ny = r3.x * r1.z + r0.z, nz = r3.x * r1.y + r0.y; r0.x = nx; r0.y = ny; r0.z = nz; }
{ float nx = r3.y * r8.x + r0.x, ny = r3.y * r8.y + r0.z, nz = r3.y * r8.z + r0.y; r0.x = nx; r0.y = ny; r0.z = nz; }
{ float nx = r3.z * r7.x + r0.x, ny = r3.z * r7.z + r0.z, nz = r3.z * r7.y + r0.y; r0.x = nx; r0.y = ny; r0.z = nz; }
r4.w = r6.w * c[2].z; r5.w = c[1].z * r5.w;
{ float nx = r4.w * r6.x + r0.x, ny = r4.w * r6.y + r0.z, nz = r4.w * r6.z + r0.y; r0.x = nx; r0.y = ny; r0.z = nz; }
{ float nx = r5.w * r5.x + r0.x, ny = r5.w * r5.z + r0.z, nz = r5.w * r5.y + r0.y; r5.x = nx; r5.y = ny; r5.z = nz; }
r0 = float4(r5.w + r4.w, r5.x + r4.x, r5.z + r4.y, r5.y + r4.z);
r3.z = r0.x + r3.z;
r3.y = r3.z + r3.y;
r3.x = r3.y + r3.x;
r2.w = r3.x + r2.w;
r2.z = r2.w + r2.z;
r2.y = r2.z + r2.y;
r2.x = r2.y + r2.x;
r0.x = 1.0 / r2.x;
float4 oC0;
oC0.x = r0.y * r0.x;
oC0.y = r0.z * r0.x;
oC0.z = r0.w * r0.x;
oC0.w = r1.w;
return oC0;
}
// ---------------------------------------------------------------------------
// postfx_uberPS (bit-exact): temporal-jitter combine + 3D grade
// LUT + grain + border mask. t0 = graded sharp scene, t1 = packed depth,
// t4 = 3D LUT (identity volume natively unless the game's is resolvable),
// t6 = grain, t7 = DOF-blurred half buffer.
float4 ps_uber(VsOut i) : SV_Target {
float4 r0 = i.r0;
float4 r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0;
r2.x = r0.x * c[253].x + c[253].z;
r1.x = c[2].x + r0.y;
r4.y = frac(r1.x);
r2.y = r4.y * c[253].x + c[253].z;
r1.z = r2.x * c[0].y - r2.x;
r1.w = r2.y * c[0].y - r2.y;
r1.x = abs(r2.x) - c[1].w;
r1.y = abs(r2.y) - c[1].w;
r1.x = max(r1.x, c[253].w);
r1.y = max(r1.y, c[253].w);
{
float2 nr = float2(abs(r2.x) - c[0].z, abs(r2.y) - c[0].z);
r2.x = nr.x; r2.y = nr.y;
r2.z = r1.x + r1.y;
}
r2.x = r2.x * c[0].z;
r2.y = r2.y * c[0].z;
r2.z = r2.z * c[0].w;
r1.x = max(r2.x, c[253].w);
r1.y = max(r2.y, c[253].w);
r4.z = r1.z * r1.x;
r4.w = r1.w * r1.y;
r4.x = r0.x;
r3 = t7.SampleLevel(s_lin, r4.xy, 0);
r1 = t0.SampleLevel(s_lin, r4.xy, 0);
r5 = r3 - r1;
r3 = r5 * r3.w + r1;
{
float3 t19;
t19.x = r3.y * c[254].z + c[254].x;
t19.y = r3.x * c[254].w + c[254].y;
t19.z = r3.z * c[254].z + c[254].x;
r1.xyz = t19;
}
r5.y = r4.w + r4.y;
r5.x = r0.x + r0.x;
{
float2 t21 = float2(r5.x + r4.z, r5.y + r4.y);
r0.x = t21.x; r0.y = t21.y;
}
r0.x = r0.x * c[253].y;
r0.y = r0.y * c[253].y;
r0 = t0.SampleLevel(s_lin, r0.xy, 0);
r5 = t4.SampleLevel(s_lin, r1.xyz, 0);
r1 = t6.SampleLevel(s_wrap, r4.xy, 0);
r2.w = t1.SampleLevel(s_pt, r4.xy, 0).z;
r4 = r2.w * c[4];
r1 = r1 * c[253].x + c[253].z;
r1 = r1 * c[4] + c[255].x;
r5 = r5 - r3;
r5 = r5 * c[3].x + r3;
r1 = r5 * r1;
r3 = r3 - r1;
r1 = r4 * r3 + r1;
r3 = r1 - r0;
r0 = r3 * c[0].x + r0;
r1 = r1 + r0;
r0 = r1 * c[253].y;
r3.x = c[1].x * c[253].y;
r3.y = c[1].y * c[253].y;
r3.z = c[1].z * c[253].y;
r1.w = 0.0;
r1.x = r3.x * r1.x - r0.x;
r1.y = r3.y * r1.y - r0.y;
r1.z = r3.z * r1.z - r0.z;
r0 = r2.z * r1 + r0;
return r0 * c[5];
}
// ---------------------------------------------------------------------------
// postfx_basictex_fisheye_opaquePS (bit-exact): lens warp +
// vignette gradient + tint. t0 = uber output, t2 = the 512x2 vignette
// gradient texture (resolved from the game's own asset via fetch words).
float4 ps_fisheye(VsOut i) : SV_Target {
float4 r0 = i.r0;
float4 r1 = 0;
r1.y = max(c[0].y, c[0].y);
{
float r0x_old = r0.x;
float3 nr;
nr.x = r0x_old * c[255].w + c[255].y;
nr.y = r0.y * c[255].z + c[255].x;
nr.z = r0x_old * c[255].z + c[255].x;
r0.xyz = nr;
}
r1.x = r0.x * r0.x;
r1.z = r0.y * r0.y;
r1.w = r0.z * r0.z;
float ps = c[0].x;
{
float r0_x = r1.x + r1.z;
float r0_w = r1.w + r1.z;
r0.x = r0_x; r0.w = r0_w;
}
r1.x = c[255].z + ps;
r1.z = r0.w * c[1].x;
r1.w = sqrt(abs(r0.x));
r1.x = r1.w * (-c[1].z) + r1.x;
r0.x = r1.z * r0.w + c[1].y;
{
float r0x_new = r0.x;
float rX = r0.z * r0x_new + c[254].y;
float rY = r0.y * r0x_new + c[254].y;
r0.x = rX; r0.y = rY;
}
r0.xyz = t0.SampleLevel(s_lin, r0.xy, 0).xyz;
r1.xyz = t2.SampleLevel(s_lin, r1.xy, 0).xyz;
r1.xyz = saturate(r1.xyz + c[0].w);
r1.xyz = r1.xyz + c[254].x;
r1.xyz = r1.xyz * c[2].y + c[255].z;
float4 oC0;
oC0.xyz = r1.xyz * r0.xyz;
oC0.w = 1.0;
return oC0;
}

View file

@ -751,6 +751,20 @@ extern "C" REX_FUNC(sub_825623F0) {
__imp__sub_825623F0(ctx, base); __imp__sub_825623F0(ctx, base);
} }
// Sk8::FE::FrontEndState_Replay2::TakePhoto(): fires once when the player
// takes a photo (replay editor / photo mission Select). The game then
// renders the shot into the 1152x640 PostFX screenshot target, resolves it,
// and ScreenshotBackEnd::GrabScreenshot CPU-reads the resolved guest memory
// to JPEG-encode it, which is all zeros unless resolve readback is forced
// (the invisible-final-photo bug: an F11 capture showed the grab texture
// 0x04911000 memory fully zero). Arms the photo-grab readback window.
extern "C" REX_FUNC(sub_826147C8) {
if (skate3::native_render::Enabled()) {
skate3::native_scene::OnTakePhoto();
}
__imp__sub_826147C8(ctx, base);
}
// rw::movie::MovieDecoder::Decode(int, VideoRenderable**, // rw::movie::MovieDecoder::Decode(int, VideoRenderable**,
// SubtitleRenderable*): fires per decoded FMV frame while any movie plays // SubtitleRenderable*): fires per decoded FMV frame while any movie plays
// (boot intro logos and all other rw::movie playback). Heartbeat for the // (boot intro logos and all other rw::movie playback). Heartbeat for the

File diff suppressed because it is too large Load diff

View file

@ -309,6 +309,20 @@ struct FrameScene {
// blue in every capture), refreshed from the guest edge-detect draw when // blue in every capture), refreshed from the guest edge-detect draw when
// it runs. Consumed by the outline composite when any item is selected. // it runs. Consumed by the outline composite when any item is selected.
float outline_color[4] = {0.21569f, 0.64706f, 1.0f, 1.0f}; float outline_color[4] = {0.21569f, 0.64706f, 1.0f, 1.0f};
// Photo-editor postfx chain (photo_fx.hlsl, exact ucode ports): the live
// PS/VS constant rows of the game's own postfx passes, captured per frame
// at the SetPending_AluConstants hook while a photo-mission photo editor
// is up, plus the fetch words of the two static input textures the chain
// samples (the 512x2 vignette gradient and the grain texture). Pass order:
// visualfx, dof downsample, tap9dofMotionBlur, tap9dof, uber, fisheye.
struct PhotoFx {
bool valid = false;
float ps[6][32][4] = {};
float vs[6][8][4] = {};
uint32_t vignette_fetch[6] = {};
uint32_t grain_fetch[6] = {};
};
PhotoFx photo_fx;
std::vector<DrawItem> items; std::vector<DrawItem> items;
}; };
@ -364,6 +378,11 @@ void OnVsConstantUpload(uint8_t* base, uint64_t mask, uint32_t bank, uint32_t pt
// / contrast effects are the game's own postfx chain, which only the // / contrast effects are the game's own postfx chain, which only the
// emulated path executes. // emulated path executes.
void OnPhotoReplayUpdate(); void OnPhotoReplayUpdate();
// Called from the Sk8::FE::FrontEndState_Replay2::TakePhoto hook: a photo
// was just taken (replay editor / photo mission). Arms the photo-grab
// forced-readback window for the frames that render and CPU-grab the
// screenshot target.
void OnTakePhoto();
void OnMovieDecode(); void OnMovieDecode();
void OnMovieFrame(uint8_t* base, uint32_t renderer); void OnMovieFrame(uint8_t* base, uint32_t renderer);