I solved my issue by creating a new gll program and attaching a compute shader to it.
unsigned int vs = CompileShader(vertShaderStr, GL_VERTEX_SHADER);
unsigned int fs = CompileShader(fragShaderStr, GL_FRAGMENT_SHADER);
unsigned int cs = CompileShader(compShaderStr, GL_COMPUTE_SHADER);
glAttachShader(mainProgram, vs);
glAttachShader(mainProgram, fs);
glAttachShader(computeProgram, cs);
glLinkProgram(computeProgram);
glValidateProgram(computeProgram);
glLinkProgram(mainProgram);
glValidateProgram(mainProgram);
glUseProgram(computeProgram);
Then, in the Render loop I switch programs and run the compute shader.
glUseProgram(computeProgram);
    glDispatchCompute(resolutionX, resolutionY, 1);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(mainProgram);
    /* Drawing the whole screen using the shader */
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    /* Poll for and process events */
    glfwPollEvents();
    
    updateBuffer();
    Update();
    /* Swap front and back buffers */
    glfwSwapBuffers(window);
I pass the data from compute shader to fragment shader via shader storage buffer.
void setupBuffer() {
    glGenBuffers(1, &ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glNamedBufferStorage(ssbo, sizeof(float) * (resolutionX * resolutionY + 
    SH_EXTRA_FLOATS), &data, GL_MAP_WRITE_BIT | GL_MAP_READ_BIT | GL_DYNAMIC_STORAGE_BIT); //sizeof(data) only works for statically sized C/C++ arrays.
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo);
}
void updateBuffer() {
    float d[] = { data.min, data.max };
    glNamedBufferSubData(ssbo, 0, 2 * sizeof(float), &d);
}
In the compute shader, I can access the buffer like this:
layout(std430, binding = 1) buffer bufferIn
    {
        float min;
        float max;
        float data[];
    };
layout(std430, binding = 1) buffer destBuffer
{
    float min;
    float max;
    float data[];
} outBuffer;
void main() {
    screenResolution;
    int index = int(gl_WorkGroupID.x + screenResolution.x * gl_WorkGroupID.y);
    dvec2 coords = adjustCoords();
    dvec4 position = rotatedPosition(coords);
    for (i = 0; i < maxIter; i++) {
        position = pow2(position);
        double length = lengthSQ(position);
        if (length > treashold) {
            float log_zn = log(float(length)) / 2.0;
            float nu = log(log_zn / log(2.0)) / log2;
            float iterAdj = 1.0 - nu + float(i);
            float scale = iterAdj / float(maxIter);
            if (scale < 0)
                data[index] = -2;
            data[index] = scale;
            if (scale > max) max = scale;
            if (scale < min && scale > 0) min = scale;
            return;
        }
    }
    data[index] = -1;
};
And finally, in the fragment shader, I can read the buffer like this:
layout(std430, binding = 1) buffer bufferIn
{
    float min;
    float max;
    float data[];
};
if (data[index] == -1) {
    color = notEscapedColor;
    return;
}
float value = (data[index] - min) / (max - min);
if (value < 0) value = 0;
if (value > 1) value = 1;
Here is the code in its entirety.