fix: close STEP-081 iOS Metal particle rendering
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
tei
2026-09-18 07:58:51 +09:00
parent 8fd517bac2
commit f1c0f92b2b
14 changed files with 503 additions and 44 deletions

View File

@@ -35,10 +35,16 @@ public:
/// Apply the given stencil reference value, if different from the current value
void setStencilReference(int32_t referenceValue);
/// Bind a texture to the fragment location
/// Bind a texture to the vertex location.
void setVertexTexture(const MTLTexturePtr&, int32_t location);
/// Set the sampler for a vertex texture binding.
void setVertexSamplerState(const MTLSamplerStatePtr&, int32_t location);
/// Bind a texture to the fragment location.
void setFragmentTexture(const MTLTexturePtr&, int32_t location);
/// Set the sampler for a texture binding
/// Set the sampler for a fragment texture binding.
void setFragmentSamplerState(const MTLSamplerStatePtr&, int32_t location);
/// Set the render pipeline state
@@ -88,6 +94,8 @@ private:
std::array<std::optional<BindInfo>, maxBinds> vertexBinds;
std::array<std::optional<BindInfo>, maxBinds> fragmentBinds;
std::array<MTLTexturePtr, maxBinds> vertexTextureBindings;
std::array<MTLSamplerStatePtr, maxBinds> vertexSamplerStates;
std::array<MTLTexturePtr, maxBinds> fragmentTextureBindings;
std::array<MTLSamplerStatePtr, maxBinds> fragmentSamplerStates;

View File

@@ -65,21 +65,56 @@ FragmentStage vertex vertexMain(thread const VertexStage in [[stage_in]],
texture2d<float> nextField [[texture(3)]], texture2d<float> state [[texture(4)]],
sampler previousSampler [[sampler(0)]], sampler fieldSampler [[sampler(2)]],
sampler nextFieldSampler [[sampler(3)]], sampler stateSampler [[sampler(4)]]) {
float2 pos=float2(in.pos)/8192.0;
// a_pos is an unnormalized short2, matching the OpenGL contract.
float2 pos=float2(in.pos);
FragmentStage out={}; out.uv=pos; out.world=pos; out.valid=1.0;
if (u.pass.x > 4.5) out.position=u.matrix*float4(pos*8192.0,0,1);
else if (u.pass.x < 1.5) out.position=float4(pos*2.0-1.0,0,1);
else if (u.pass.x > 3.5) {
float endpoint=glMod(pos.x*8192.0,2.0)*2.0-1.0;
float2 index=float2(floor(pos.x*8192.0*.5),pos.y*8192.0);
if (u.pass.x > 5.5) {
float corner=glMod(pos.x,4.0);
float2 index=float2(floor(pos.x*.25),pos.y);
float2 stateUv=(index+.5)/u.mask.xy;
float4 old=previous.sample(previousSampler,stateUv), current=state.sample(stateSampler,stateUv);
out.uv=stateUv; out.world=current.rg;
bool stateOk=current.a>.5&&distance(old.rg,current.rg)>1e-9;
float2 uv=fieldUv(current.rg,u);
float4 a=field.sample(fieldSampler,uv), b=nextField.sample(nextFieldSampler,uv);
float2 advection;
bool ok=validFieldUv(uv)&&a.a>=.5&&b.a>=.5&&resolveAdvection(a,b,u,advection);
out.velocity=float2(advection.x,-advection.y);
out.valid=stateOk&&ok&&length(out.velocity)>0.00001?1.0:0.0;
float4 clip=u.matrix*float4(current.rg*8192.0,0,1);
float2 cornerOffset=corner<.5?float2(-1,-1):(corner<1.5?float2(1,-1):(corner<2.5?float2(-1,1):float2(1,1)));
float2 halfPixelNdc=float2(1.0/max(u.fieldScale.w,1.0),1.0/max(u.advection.w,1.0));
clip.xy+=cornerOffset*halfPixelNdc*clip.w;
out.position=out.valid<.5?float4(-2,-2,0,1):clip;
} else if (u.pass.x > 4.5) {
out.position=u.matrix*float4(pos*8192.0,0,1);
} else if (u.pass.x < 1.5) {
// Metal render targets are addressed from the top-left. Flip the
// fullscreen varying so the same logical State/Trail texel is sampled
// on the next ping-pong pass. Particle texel indices remain unflipped.
out.uv=float2(pos.x,1.0-pos.y);
out.position=float4(pos*2.0-1.0,0,1);
} else if (u.pass.x > 3.5) {
float endpoint=glMod(pos.x,2.0)*2.0-1.0;
float2 index=float2(floor(pos.x*.5),pos.y);
float2 stateUv=(index+.5)/u.mask.xy;
float4 old=previous.sample(previousSampler,stateUv), current=state.sample(stateSampler,stateUv);
out.uv=stateUv; out.world=current.rg;
bool stateOk=current.a>.5&&distance(old.rg,current.rg)>1e-9;
float2 uv=fieldUv(current.rg,u); float4 a=field.sample(fieldSampler,uv), b=nextField.sample(nextFieldSampler,uv); float2 advection;
bool ok=validFieldUv(uv)&&a.a>=.5&&b.a>=.5&&resolveAdvection(a,b,u,advection);
out.uv=stateUv; out.world=current.rg; out.velocity=float2(advection.x,-advection.y);
out.valid=ok&&length(out.velocity)>0.00001&&current.a>.5&&distance(old.rg,current.rg)>1e-9 ? 1.0:0.0;
out.position=out.valid<.5 ? float4(-2,-2,0,1) : u.matrix*float4((endpoint<0?old.rg:current.rg)*8192.0,0,1);
} else out.position=u.mask.z>.5 ? float4(pos*2.0-1.0,0,1) : u.matrix*float4(pos*8192.0,0,1);
out.velocity=float2(advection.x,-advection.y);
out.valid=stateOk&&ok&&length(out.velocity)>0.00001?1.0:0.0;
out.position=out.valid<.5?float4(-2,-2,0,1):u.matrix*float4((endpoint<0?old.rg:current.rg)*8192.0,0,1);
} else {
if (u.mask.z>.5) {
// Composite samples the viewport-sized Trail render target.
out.uv=float2(pos.x,1.0-pos.y);
out.position=float4(pos*2.0-1.0,0,1);
} else {
out.position=u.matrix*float4(pos*8192.0,0,1);
}
}
return out;
}
@@ -90,6 +125,7 @@ float4 fragment fragmentMain(FragmentStage in [[stage_in]], device const Particl
texture2d<float> previous [[texture(0)]], texture2d<float> mask [[texture(1)]], texture2d<float> field [[texture(2)]], texture2d<float> nextField [[texture(3)]], texture2d<float> state [[texture(4)]],
sampler previousSampler [[sampler(0)]], sampler maskSampler [[sampler(1)]], sampler fieldSampler [[sampler(2)]], sampler nextFieldSampler [[sampler(3)]], sampler stateSampler [[sampler(4)]]) {
float mode=u.pass.x;
if(mode>5.5){if(in.valid<.5)discard_fragment();float speed=length(in.velocity);return float4(1,1,clamp(speed/30.,0.,1.),1);}
if(mode>4.5){float2 uv=fieldUv(in.world,u); if(!validFieldUv(uv)) discard_fragment(); float4 a=field.sample(fieldSampler,uv),b=nextField.sample(nextFieldSampler,uv);if(a.a<.5||b.a<.5)discard_fragment();float value=u.weatherRamp.y>.5?length(mix((a.rg*2.-1.)*u.fieldScale.x,(b.rg*2.-1.)*u.fieldScale.y,u.marker.x)):mix((a.r*2.-1.)*u.fieldScale.x,(b.r*2.-1.)*u.fieldScale.y,u.marker.x);if(!isfinite(value))discard_fragment();float4 c=ramp(value,u);float alpha=c.a*u.visual.x;return float4(c.rgb*alpha,alpha);}
float4 old=previous.sample(previousSampler,in.uv);
if(mode<.5){