0
\$\begingroup\$

I am casually trying to learn a bit about writing simple fragment shaders, in particular using Metal API. I have been experimenting in KodeLife. As a simple example, I am trying to remake the Double Pendulum Map from this site.

The physics details are probably not important. Essentially, I am solving an ODE (ordinary differential equation) for each frame which tracks the movement of a double pendulum (for each possible initial configuration, in parallel). Each pixel color updates based on how the pendulum for that initial value evolves over time. Here is some quick code I wrote to achieve this:

#include <metal_stdlib>
using namespace metal;

struct FS_INPUT
{
    float3 v_normal;
    float2 v_texcoord;
};

struct FS_UNIFORM
{
    float time;
    float2 resolution;
    float2 mouse;
    float3 spectrum;
};

class DoublePendulum{
public:
    DoublePendulum(float2 _th, float2 _om, float _dt) {
        current_th = _th;
        current_om  = _om;
        dt = _dt;
        g = 9.82;
        L1 = 1;
        L2 = 1;
        m1 = 1;
        m2 = 2;

    }
    
    float2 omega_prime(float2 oTh, float2 oOm){
        float2 nOm;
        
        float th12 = oTh.x - oTh.y;
        float cos12 = cos(th12);
        float sin12 = sin(th12);
        float c1 = 2*m1+m2-m2*cos(2*th12);
        
        nOm.x = (-g*(2*m1+m2)*sin(oTh.x) - m2*g*sin(oTh.x-2*oTh.y) - 2*sin12*m2*(oOm.y*oOm.y*L2 + oOm.x*oOm.x*L1*cos12))/(L1*c1);
        nOm.y = (2*sin12*(oOm.x*oOm.x*L1*(m1+m2) + g*(m1+m2)*cos(oTh.x) + oOm.y*oOm.y*L2*m2*cos12))/(L2*c1);
        
        return nOm;
    }
    
    void nextStep(){
        pre_th = current_th;
        pre_om = current_om;
        
        current_om = pre_om + omega_prime(pre_th, pre_om)*dt;
        current_th = pre_th + current_om*dt;
    }
    
    float2 currentPosition(){
        return current_th;
    }
        
private:
    float2 current_th, current_om;
    float2 pre_th, pre_om;
    float dt;
    float m1,m2, L1, L2, g;
};

float4 colorMap(float2 uv){
    return abs(float4(cos(uv*2),1.0, 1.0));
}



fragment
float4 fs_main(
    FS_INPUT In [[stage_in]],
    constant FS_UNIFORM& uniform [[buffer(16)]],
    texture2d<float> prevFrame [[texture(0)]],
    texture2d<float> prevPass [[texture(1)]],
    texture2d<float> texture0 [[texture(2)]],
    texture2d<float> texture1 [[texture(3)]],
    texture2d<float> texture2 [[texture(4)]],
    texture2d<float> texture3 [[texture(5)]],
    sampler prevFrameSmplr [[sampler(0)]],
    sampler prevPassSmplr [[sampler(1)]],
    sampler texture0Smplr [[sampler(2)]],
    sampler texture1Smplr [[sampler(3)]],
    sampler texture2Smplr [[sampler(4)]],
    sampler texture3Smplr [[sampler(5)]])
{
    float2 uv = -1. + 2. * In.v_texcoord;
    float t = uniform.time;
    float dt = 1./60.;
    
    DoublePendulum dp(3.12*uv, float2(0.0, 0.0), dt);
    
    dp.nextStep();
    
    
    float4 col = colorMap(dp.currentPosition()/3.12);
    return col;
}

The above code doesn't evolve in time, as when the shader code is called in each frame, the simulation just starts from the beginning and just does one single time step evolution. The previous position is not remembered in the next frame. If I replace

dp.nextStep();

with (say)

for(int i=0; i<50*t; i++){
     dp.nextStep();
    }

then it does what it should. But for each frame, the simulation begins from the start and evolves till time 50*t. Thus as time progresses, each frame is much slower to compute.

Question: Is it somehow possible to retain the values stored in the dp object between frames or pass the data to the next frame? Is this possible to do without passing the data back/forth to the CPU?

I see that KodeLife has prepared texture2d and sampler stuff in the fragment shader with names like "prevFrame" and "prevPass" etc. Can I use those to pass the data to the next frame somehow? What is the simplest standard design to solve this kind of issue?

PS. please move this to another stackexchange site, if this is not the appropriate place for this question.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ As you've surmised, this is accomplished by outputting the rendered result to a texture, then sampling from that texture next frame to reconstruct your starting state for the following iteration. You can do this in a ping-pong fashion where you have two textures that trade read/write roles on alternating frames. Can you show us how you've tried to make use of this and where you're stuck? \$\endgroup\$
    – DMGregory
    Nov 14 '21 at 12:49
  • \$\begingroup\$ @DMGregory Thanks for the comment. I figured out how to sample from "preFrame", but I don't need the previous frame to compute the next, I need the float2 in dp.currentPosition(). I was thinking about writing this into texture0 (as float4) and sample it in next frame. Something like: float4 currentPos = float4(dp.currentPosition(),0,0); ushort2 gid = ushort2(In.v_texcoord*uniform.resolution); texture0.write(currentPos,gid); I get an error saying: "Texture writing supports only texture with access::write or access::read_write as the access qualifier". 1/2 \$\endgroup\$
    – Heidar
    Nov 14 '21 at 17:52
  • 1
    \$\begingroup\$ Have you considered returning the value you want to write from the fragment shader, so it gets drawn into a destination frame buffer (which is your output texture)? \$\endgroup\$
    – DMGregory
    Nov 14 '21 at 17:57
  • 1
    \$\begingroup\$ I don't know Metal, or I'd have posted an answer. But there's two main ways this would be done in other frameworks. Option A is to have two shader passes. In the first pass you read from your "prevFrame" texture and render the new positions to your "nextFrame" texture. In your second pass, you read from "nextFrame" and compute the displayed colour to output to the screen. Option B is to use "multiple render target" rendering, to combine the two passes and output to both the position output and the display colour targets at once. \$\endgroup\$
    – DMGregory
    Nov 14 '21 at 18:21
  • 1
    \$\begingroup\$ Be sure to post an Answer below once you have a version that works for you \$\endgroup\$
    – DMGregory
    Nov 14 '21 at 18:42

You must log in to answer this question.

Browse other questions tagged .