//------------------------------------------------------------------------------
//  offscreen/main.jai
//
//  Render to an offscreen rendertarget texture, and use this texture
//  for rendering to the display.
//------------------------------------------------------------------------------
#import "Basic";
#import,dir "../../sokol/log"(USE_GL=USE_GL);
#import,dir "../../sokol/gfx"(USE_GL=USE_GL);
#import,dir "../../sokol/app"(USE_GL=USE_GL);
#import,dir "../../sokol/glue"(USE_GL=USE_GL);
#import,dir "../../sokol/shape"(USE_GL=USE_GL);
#load "../math.jai";
#load "./shader.jai";

OFFSCREEN_SAMPLE_COUNT :: 1;

state: struct {
    offscreen: struct {
        pass_action: sg_pass_action;
        attachments: sg_attachments;
        pip:         sg_pipeline;
        bind:        sg_bindings;
    };
    default: struct {
        pass_action: sg_pass_action;
        pip:         sg_pipeline;
        bind:        sg_bindings;
    };
    donut:    sshape_element_range_t;
    sphere:   sshape_element_range_t;
    rx, ry:   float;
    vertices: [4000]sshape_vertex_t;
    indices:  [24000]u16;
}

init :: () #c_call {
    push_context,defer_pop;

    sg_setup(*(sg_desc.{
        environment = xx,force sglue_environment(),
        logger = .{ func = slog_func },
    }));

    // default pass action: clear to blue-ish
    state.default.pass_action = .{
        colors[0] = .{ load_action = .CLEAR, clear_value = .{ 0.25, 0.45, 0.65, 1.0 } },
    };

    // offscreen pass action: clear to grey
    state.offscreen.pass_action = .{
        colors[0] = .{ load_action = .CLEAR, clear_value = .{ 0.25, 0.25, 0.25, 1.0 } },
    };

    // a render pass with one color- and one depth-attachment image
    img_desc := sg_image_desc.{
        render_target = true,
        width = 256,
        height = 256,
        pixel_format = .RGBA8,
        sample_count = OFFSCREEN_SAMPLE_COUNT,
    };
    color_img := sg_make_image(*img_desc);
    img_desc.pixel_format = .DEPTH;
    depth_img := sg_make_image(*img_desc);
    state.offscreen.attachments = sg_make_attachments(*(sg_attachments_desc.{
        colors[0] = .{ image = color_img },
        depth_stencil = .{
            image = depth_img,
        },
    }));

    // a donut shape which is rendered into the offscreen render target, and
    // a sphere shape which is rendered into the default framebuffer
    buf := sshape_buffer_t.{
        vertices = .{ buffer = .{ ptr = *state.vertices, size = size_of(type_of(state.vertices)) } },
        indices  = .{ buffer = .{ ptr = *state.indices,  size = size_of(type_of(state.indices)) } },
    };

    buf = sshape_build_torus(*buf, *(sshape_torus_t.{
        radius = 0.5,
        ring_radius = 0.3,
        sides = 20,
        rings = 30,
    }));
    state.donut = sshape_element_range(*buf);

    buf = sshape_build_sphere(*buf, *(sshape_sphere_t.{
        radius = 0.5,
        slices = 72,
        stacks = 40,
    }));
    state.sphere = sshape_element_range(*buf);

    vbuf := sg_make_buffer(xx,force *sshape_vertex_buffer_desc(*buf));
    ibuf := sg_make_buffer(xx,force *sshape_index_buffer_desc(*buf));

    // pipeline-state-object for offscreen-rendered donut, don't need texture coord here
    offscreen_pip_desc := sg_pipeline_desc.{
        shader = sg_make_shader(*offscreen_shader_desc(sg_query_backend())),
        index_type = .UINT16,
        cull_mode = .BACK,
        sample_count = OFFSCREEN_SAMPLE_COUNT,
        depth = .{
            pixel_format = .DEPTH,
            compare = .LESS_EQUAL,
            write_enabled = true,
        },
        colors[0] = .{ pixel_format = .RGBA8 },
    };
    offscreen_pip_desc.layout.buffers[0] = xx,force sshape_vertex_buffer_layout_state();
    offscreen_pip_desc.layout.attrs[ATTR_offscreen_position] = xx,force sshape_position_vertex_attr_state();
    offscreen_pip_desc.layout.attrs[ATTR_offscreen_normal] = xx,force sshape_normal_vertex_attr_state();
    state.offscreen.pip = sg_make_pipeline(*offscreen_pip_desc);

    // and another pipeline-state-object for the default pass
    default_pip_desc := sg_pipeline_desc.{
        shader = sg_make_shader(*default_shader_desc(sg_query_backend())),
        index_type = .UINT16,
        cull_mode = .BACK,
        depth = .{
            compare = .LESS_EQUAL,
            write_enabled = true,
        },
    };
    default_pip_desc.layout.buffers[0] = xx,force sshape_vertex_buffer_layout_state();
    default_pip_desc.layout.attrs[ATTR_default_position] = xx,force sshape_position_vertex_attr_state();
    default_pip_desc.layout.attrs[ATTR_default_normal] = xx,force sshape_normal_vertex_attr_state();
    default_pip_desc.layout.attrs[ATTR_default_texcoord0] = xx,force sshape_texcoord_vertex_attr_state();
    state.default.pip = sg_make_pipeline(*default_pip_desc);

    // a sampler object for sampling the render target as texture
    smp := sg_make_sampler(*(sg_sampler_desc.{
        min_filter = .LINEAR,
        mag_filter = .LINEAR,
        wrap_u = .REPEAT,
        wrap_v = .REPEAT,
    }));

    // the resource bindings for rendering a non-textured cube into offscreen render target
    state.offscreen.bind = .{
        vertex_buffers[0] = vbuf,
        index_buffer = ibuf,
    };

    // resource bindings to render a textured shape, using the offscreen render target as texture
    state.default.bind = .{
        vertex_buffers[0] = vbuf,
        index_buffer = ibuf,
    };
    state.default.bind.images[IMG_tex] = color_img;
    state.default.bind.samplers[SMP_smp] = smp;
}

frame :: () #c_call {
    push_context,defer_pop;

    t := cast(float) (sapp_frame_duration() * 60.0);
    state.rx += 1.0 * t;
    state.ry += 2.0 * t;

    // the offscreen pass, rendering an rotating, untextured donut into a render target image
    vs_params := Vs_Params.{
        mvp = compute_mvp(rx = state.rx, ry = state.ry, aspect = 1.0, eye_dist = 2.5),
    };
    sg_begin_pass(*(sg_pass.{ action = state.offscreen.pass_action, attachments = state.offscreen.attachments }));
    sg_apply_pipeline(state.offscreen.pip);
    sg_apply_bindings(*state.offscreen.bind);
    sg_apply_uniforms(UB_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
    sg_draw(state.donut.base_element, state.donut.num_elements, 1);
    sg_end_pass();

    // and the default-pass, rendering a rotating textured sphere which uses the
    // previously rendered offscreen render-target as texture
    vs_params = Vs_Params.{
        mvp = compute_mvp(
            rx = -state.rx * 0.25,
            ry = state.ry * 0.25,
            aspect = sapp_widthf() / sapp_heightf(),
            eye_dist = 2.0,
        ),
    };
    sg_begin_pass(*(sg_pass.{ action = state.default.pass_action, swapchain = xx,force sglue_swapchain() }));
    sg_apply_pipeline(state.default.pip);
    sg_apply_bindings(*state.default.bind);
    sg_apply_uniforms(UB_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
    sg_draw(state.sphere.base_element, state.sphere.num_elements, 1);
    sg_end_pass();

    sg_commit();
}

compute_mvp :: (rx: float, ry: float, aspect: float, eye_dist: float) -> Matrix4 {
    proj := persp_mat4(fov = 45.0, aspect = aspect, near = 0.01, far = 10.0);
    view := lookat_mat4(eye = .{ 0.0, 0.0, eye_dist }, center = .{}, up = .{ 0.0, 1.0, 0.0 });
    view_proj := multiply_mat4(proj, view);
    rxm := rotate_mat4(rx, .{ 1.0, 0.0, 0.0 });
    rym := rotate_mat4(ry, .{ 0.0, 1.0, 0.0 });
    model := multiply_mat4(rym, rxm);
    mvp := multiply_mat4(view_proj, model);
    return mvp;
}

cleanup :: () #c_call {
    sg_shutdown();
}

main :: () {
    sapp_run(*(sapp_desc.{
        init_cb = init,
        frame_cb = frame,
        cleanup_cb = cleanup,
        width = 800,
        height = 600,
        sample_count = 4,
        window_title = "offscreen",
        icon = .{ sokol_default = true },
        logger = .{ func = slog_func },
    }));
}
