
    

local elapsed_time = gh_utils.get_elapsed_time()
local dt = elapsed_time - last_time
last_time = elapsed_time


frames = frames+1
fps_time = fps_time + dt
if (fps_time >= 1.0) then
  fps_time = 0
  fps = frames
  frames = 0
end  




local platform_windows = 1 
local platform_osx = 2 
local platform_linux = 3 

if (gh_utils.get_platform() == platform_windows) then
  gh_window.keyboard_update_buffer(0)
end

  
left_ctrl_key = gh_input.keyboard_is_key_down(KC_LEFT_CTRL)
left_shift_key = gh_input.keyboard_is_key_down(KC_LEFT_SHIFT)

if (attractor_auto_move == 0) then
  if (left_shift_key == 1) then
    attractor_force_sign = -1
  else  
    attractor_force_sign = 1
  end
end

---[[
local kdt = elapsed_time - keyboard_last_time

if (kdt > 0.2) then
  if (gh_input.keyboard_is_key_down(KC_SPACE) == 1) then
    if  (update_particles == 1) then 
      update_particles = 0
    else
      update_particles = 1
    end
    keyboard_last_time = elapsed_time 
  end
end
--]]





local cursorX, cursorY = gh_input.mouse_get_position()

local destPosX = (cursorX / winW - 0.5) * 2.0
local destPosY = ((winH - cursorY) / winH - 0.5) * 2.0
destPosX = destPosX * winW/2.0
destPosY = destPosY * winH/2.0




if (attractor_auto_move == 1) then

  left_ctrl_key = 1

  attractor_radius_x = attractor_radius_x + attractor_radius_dt_inc * dt
  attractor_radius_y = attractor_radius_y + attractor_radius_dt_inc * dt

  --if (attractor_radius_x > 250.0) then
  if (elapsed_time - attractor_auto_move_time > attractor_auto_move_reset_time) then
    attractor_auto_move_time = elapsed_time

    ResetParticles()
    
    attractor_radius_x = Random(20, winW/4.0)
    attractor_radius_y = Random(20, winH/4.0)
    attractor_radius_dt_inc = 10
    attractor_force_sign = 1
    force_damping = Random(150, 300)
    velocity_damping = Random(0.980, 1.0)
  end

  destPosX = attractor_radius_x * math.cos(elapsed_time * 3.0)
  destPosY = attractor_radius_y * math.sin(elapsed_time * 1.0)
  --]]

  --[[
  attractor_radius = winH/4.0
  attractor_force_sign = 1
  force_damping = 200.0 * math.cos(elapsed_time * 10.0) 
  velocity_damping = 0.980
  
  destPosX = attractor_radius_x * math.cos(elapsed_time * 0.40)
  destPosY = attractor_radius_y * math.sin(elapsed_time * 1.20)
  --]]
 
end





local work_group_size = {x=128, y=1, z=1}
local num_groups = {x=particle_count / work_group_size.x, y=1, z=1}




-- dt = 0.016

--[[
gh_camera.set_position(camera, 0, 0, 50) 
gh_camera.set_lookat(camera, 0, 0, 0, 1)
gh_camera.bind(camera) 
--]]


gh_camera.bind(camera_ortho) 



gh_renderer.set_depth_test_state(1)
gh_renderer.clear_color_depth_buffers(bgcolor.r, bgcolor.g, bgcolor.b, 1.0, 1.0)



---[[
if ((update_particles == 1) and (compute_prog > 0)) then
 
  gh_gpu_buffer.bind_base(ssbo_positions, 0)
  gh_gpu_buffer.bind_base(ssbo_velocities, 1)
  
  
  gh_gpu_program.bind(compute_prog)
  gh_gpu_program.uniform1f(compute_prog, "deltaT", dt * speed_factor)

  if (left_ctrl_key == 1) then
    attractor_pos.x = destPosX
    attractor_pos.y = destPosY
    gh_gpu_program.uniform3f(compute_prog, "destPos", attractor_pos.x, attractor_pos.y, 0)
  end

  -- Viewport dimensions for border clamp
  gh_gpu_program.uniform2f(compute_prog, "vpDim", winW/2, winH/2)
  gh_gpu_program.uniform1i(compute_prog, "borderClamp", 1)

  gh_gpu_program.uniform1f(compute_prog, "force_damping", force_damping * attractor_force_sign)
  gh_gpu_program.uniform1f(compute_prog, "velocity_damping", velocity_damping)
  gh_gpu_program.uniform1f(compute_prog, "force_scale", force_scale)

  

  if (GL_ARB_compute_variable_group_size_ok == 1) then
    gh_gpu_program.run_compute_group_size(compute_prog, num_groups.x, num_groups.y, num_groups.z, work_group_size.x, work_group_size.y, work_group_size.z)
  else
    gh_gpu_program.run_compute(compute_prog, num_groups.x, num_groups.y, num_groups.z)
  end

  -- Set memory barrier on per vertex base to make sure we get what was written by the compute shaders
  --gh_renderer.memory_barrier("GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT")
  --gh_renderer.memory_barrier("GL_SHADER_STORAGE_BARRIER_BIT")
  gh_renderer.memory_barrier("GL_SHADER_STORAGE_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT")
  
  gh_gpu_buffer.unbind(ssbo_positions)
  gh_gpu_buffer.unbind(ssbo_velocities)
  
end
--]] 






if (GL_ARB_compute_shader_ok == 1) then
  gh_renderer.set_depth_test_state(0)

  gh_renderer.set_blending_state(1)
  --gh_renderer.set_blending_factors(GXL3D_RENDERER_BLEND_FACTOR_SRC_ALPHA, GXL3D_RENDERER_BLEND_FACTOR_ONE)
  gh_renderer.set_blending_factors(blending_factors.s, blending_factors.d)

  gh_texture.bind(tex01, 0)

  gh_gpu_program.bind(particle_prog)
  gh_gpu_program.uniform1f(particle_prog, "point_size", point_size)
  gh_gpu_program.uniform4f(particle_prog, "color", particle_color.r, particle_color.g, particle_color.b, particle_color.a)

  gh_vertex_pool.render(vp, 0, particle_count)

  gh_renderer.set_blending_state(0)
  gh_renderer.set_depth_test_state(1)
end




if (show_attractor == 1) then
  gh_gpu_program.bind(color_prog)
  gh_gpu_program.uniform4f(color_prog, "color", 1, 0, 0, 1)
  gh_object.set_position(attractor_disc, attractor_pos.x, attractor_pos.y, 0)
  gh_object.render(attractor_disc)
end



--[[
gh_gpu_program.bind(color_prog)
for i=0, num_attractors-1 do
  local pos = attractors_positions[i+1]
  gh_object.set_position(light_sphere, pos.px, pos.py, pos.pz)
  gh_object.render(light_sphere)
end
--]]




--[[
  libfont_clear()

  local y_offset = 40
  
  libfont_print(20, y_offset, 1, 0.5, 0, 1, string.format("Compute Shader Demo (%d particles)", particle_count))
  y_offset = y_offset + 20
  
  y_offset = y_offset + 20
  libfont_print(20, y_offset, 1, 1, 0, 1, string.format("%.0f FPS / %.3f ms", fps, dt * 1000.0))
  y_offset = y_offset + 20
  
  y_offset = y_offset + 20
  libfont_print(20, y_offset, 1, 1, 0, 1, "GL_RENDERER")
  y_offset = y_offset + 20
  libfont_print(20, y_offset, 1, 1, 1, 1, "> " .. gl_renderer)
  y_offset = y_offset + 20
  
  libfont_print(20, y_offset, 1, 1, 0, 1, "GL_VERSION")
  y_offset = y_offset + 20
  libfont_print(20, y_offset, 1, 1, 1, 1, "> " .. gl_version)
  y_offset = y_offset + 20


  y_offset = y_offset + 20
  libfont_print(20, y_offset, 1, 1, 1, 1, string.format("destPosX: %.1f - destPosY: %.1f", destPosX, destPosY))
  y_offset = y_offset + 20

  libfont_print(20, y_offset, 1, 1, 1, 1, string.format("compute_prog: %d", compute_prog))
  y_offset = y_offset + 20

  libfont_print(20, y_offset, 1, 1, 1, 1, string.format("workingGroups: %d", workingGroups))
  y_offset = y_offset + 20

  
  
  libfont_render()
  --]]
  
  
  




---[[
  imgui_frame_begin()


  
  gh_imgui.set_color(IMGUI_WINDOW_BG_COLOR, 0.2, 0.4, 0.9, 0.0)
  gh_imgui.set_color(IMGUI_BORDER_COLOR, 0.2, 0.4, 0.9, 0.8)
  
  gh_imgui.set_color(IMGUI_TITLE_BG_COLOR, 0.2, 0.4, 0.9, 0.6)
  gh_imgui.set_color(IMGUI_TITLE_BG_ACTIVE_COLOR, 0.2, 0.4, 1.0, 1.0)
  
  gh_imgui.set_color(IMGUI_FRAME_BG_COLOR, 0.6, 0.6, 0.6, 0.5)
  gh_imgui.set_color(IMGUI_FRAME_BG_HOVERED_COLOR, 0.7, 0.7, 0.7, 0.6)
  
  gh_imgui.set_color(IMGUI_SLIDER_GRAB_COLOR, 1.0, 0.8, 0.1, 0.8)
  gh_imgui.set_color(IMGUI_SLIDER_GRAB_ACTIVE_COLOR, 1.0, 0.9, 0.4, 1.0)
  
  gh_imgui.set_color(IMGUI_BUTTON_COLOR, 1.0, 0.5, 0.0, 1.0)
  gh_imgui.set_color(IMGUI_BUTTON_HOVERED_COLOR, 1.0, 0.7, 0.0, 1.0)

  gh_imgui.set_color(IMGUI_SCROLLBAR_BG_COLOR, 0.4, 0.4, 0.4, 0.8)
  gh_imgui.set_color(IMGUI_SCROLLBAR_GRAB_HOVERED_COLOR, 0.2, 0.5, 1.0, 0.8)
  gh_imgui.set_color(IMGUI_SCROLLBAR_GRAB_COLOR, 0.2, 0.4, 0.9, 0.8)
  gh_imgui.set_color(IMGUI_SCROLLBAR_GRAB_ACTIVE_COLOR, 0.2, 0.6, 1.0, 0.8)

  gh_imgui.set_color(IMGUI_HEADER_COLOR, 0.2, 0.4, 0.9, 0.6)
  gh_imgui.set_color(IMGUI_HEADER_HOVERED_COLOR, 0.2, 0.4, 1.0, 0.8)

  gh_imgui.set_color(IMGUI_TEXT_COLOR, 1.0, 1.0, 1.0, 1.0)


  --local is_open = imgui_window_begin_pos_size_once("Control panel", 300, winH, 0, 0)
  local is_open = imgui_window_begin_pos_size_always("Control panel", 300, winH, 0, 0)
  if (is_open == 1) then

 
    local window_w = gh_imgui.get_content_region_available_width()

    local widget_width = window_w * 1.0
    
    

    gh_imgui.text("OpenGL 4.3+ Compute Shader Demo")

    if (GL_ARB_compute_shader_ok == 1) then
      gh_imgui.text("GL_ARB_compute_shader: OK")

      if (GL_ARB_compute_variable_group_size_ok == 1) then
        gh_imgui.text("GL_ARB_compute_variable_group_size: OK")
      else
        gh_imgui.text("GL_ARB_compute_variable_group_size NOT SUPPORTED")
      end
  
    else
      gh_imgui.text_rgba("GL_ARB_compute_shader: NOT SUPPORTED", 1.0, 0.4, 0.0, 1.0)
    end



    gh_imgui.text(string.format("%d particles", particle_count))
    gh_imgui.text(string.format("%.0f FPS / %.3f ms", fps, dt * 1000.0))
    
    imgui_vertical_space_v2(2)

    if (host_app_name == "GeeXLab") then
      gh_imgui.text(string.format("Mouse position: < %.1f ; %.1f >", destPosX, destPosY))
      gh_imgui.text(string.format("Attractor position: < %.1f ; %.1f >", attractor_pos.x, attractor_pos.y))
    end
  

    gh_imgui.text(string.format("work_group_size: { %d, %d, %d }", work_group_size.x, work_group_size.y, work_group_size.z))
    gh_imgui.text(string.format("num_groups: { %d, %d, %d }", num_groups.x, num_groups.y, num_groups.z))
    
 
 
    if (host_app_name == "GeeXLab") then
      imgui_vertical_space_v2(4)

      gh_imgui.set_color(IMGUI_TEXT_COLOR, 0.0, 0.0, 0.0, 1.0)
      if (gh_imgui.button("Reset particles", 120, 24) == 1) then
        ResetParticles()
        attractor_radius = 10
      end
      gh_imgui.set_color(IMGUI_TEXT_COLOR, 1.0, 1.0, 1.0, 1.0)
    end

    imgui_vertical_space_v2(2)
    
    
    --imgui_vertical_space_v2(4)



    local min_value = 1.0
    local max_value = 20.0
    local power = 1.0


    if (host_app_name == "GeeXLab") then
      imgui_vertical_space_v2(2)
      gh_imgui.separator()
      if (gh_imgui.collapsing_header("Particle update", ImGuiTreeNodeFlags_DefaultOpen) == 1) then
        
        attractor_auto_move = gh_imgui.checkbox("Attractor auto-move", attractor_auto_move)

        if (attractor_auto_move == 0) then
          update_particles = gh_imgui.checkbox("Update particles (SPACE key)", update_particles)

          imgui_vertical_space_v2(2)

          min_value = 1.0
          max_value = 10.0
          gh_imgui.text("Time speed factor")
          speed_factor = gh_imgui.slider_1f("##speed_factor", speed_factor,   min_value, max_value,   power)

          min_value = 980.0
          max_value = 1000.0
          gh_imgui.text("Velocity damping")
          velocity_damping = gh_imgui.slider_1f("##velocity_damping", velocity_damping*1000.0,   min_value, max_value,   power) / 1000.0
          --velocity_damping = gh_imgui.slider_1f("##velocity_damping", velocity_damping,   min_value, max_value,   power)

          min_value = 0.0
          max_value = 500.0
          gh_imgui.text("Force damping")
          force_damping = gh_imgui.slider_1f("##force_damping", force_damping,   min_value, max_value,   power)

          min_value = 0.01
          max_value = 1.0
          gh_imgui.text("Force scale")
          force_scale = gh_imgui.slider_1f("##force_scale", force_scale,   min_value, max_value,   power)
        end
    end
  end





  imgui_vertical_space_v2(2)
  gh_imgui.separator()
  if (gh_imgui.collapsing_header("Rendering", 0) == 1) then

    show_attractor = gh_imgui.checkbox("Show attractor", show_attractor)

    imgui_vertical_space_v2(2)

    min_value = 1.0
    max_value = 20.0
    power = 1.0
    gh_imgui.text("Particle size")
    point_size = gh_imgui.slider_1f("##point_size", point_size,   min_value, max_value,   power)


    min_value = 1
    max_value = 9
    
    local blending_factor_str = blending_factor_to_str(blending_factors.s)
    gh_imgui.text("SRC blending factor:" .. blending_factor_str)
    blending_factors.s = gh_imgui.slider_1i("##blending_factors_s", blending_factors.s,   min_value, max_value)
    
    blending_factor_str = blending_factor_to_str(blending_factors.d)
    gh_imgui.text("DST blending factor: " .. blending_factor_str)
    blending_factors.d = gh_imgui.slider_1i("##blending_factors_d", blending_factors.d,   min_value, max_value)


    gh_imgui.text("Particle color")
    local flags = ImGuiColorEditFlags_AlphaBar
    particle_color.r, particle_color.g, particle_color.b, particle_color.a = gh_imgui.color_edit_rgba_v2("##particle_color", particle_color.r, particle_color.g, particle_color.b, particle_color.a, flags)


    imgui_vertical_space_v2(2)

    gh_imgui.text("Background color")
    bgcolor.r, bgcolor.g, bgcolor.b, bgcolor.a = gh_imgui.color_edit_rgba("##bg_color", bgcolor.r, bgcolor.g, bgcolor.b, 1.0)


  end
      












  
    imgui_vertical_space_v2(2)
    gh_imgui.separator()


    if (gh_imgui.collapsing_header("About", ImGuiTreeNodeFlags_DefaultOpen) == 1) then

      imgui_vertical_space_v2(2)
      gh_imgui.set_color(IMGUI_TEXT_COLOR, 0.0, 0.0, 0.0, 1.0)
      local button_label = "More info @ HackLAB"
      if (host_app_name == "GPU Caps Viewer") then
        button_label = "Full interactive demo"
      end
      if (gh_imgui.button(button_label, 160, 20) == 1) then
        gh_utils.open_url("https://geeks3d.com/hacklab/20200117/demo-opengl-4-3-compute-shaders-particles-ssbo/")
      end
      gh_imgui.set_color(IMGUI_TEXT_COLOR, 1.0, 1.0, 1.0, 1.0)
      imgui_vertical_space_v2(2)

      

      gh_imgui.text(geexlab_version)
      gh_imgui.text("GL_RENDERER: " .. gl_renderer)
      gh_imgui.text("GL_VERSION: " .. gl_version)
  
      if (host_app_name == "GeeXLab") then
        imgui_vertical_space_v2(2)
        gh_imgui.text_rgba("Controls:", 1.0, 1.0, 0.25, 1.0)
        gh_imgui.text("- SPACE: toggle update")
        gh_imgui.text("- LEFT CTRL: enable mouse attractor")
        gh_imgui.text("- LEFT SHIFT: enable repulsion")
      end
    end





    win_hovered = 0
    if (gh_imgui.is_any_window_hovered() == 1) then
      win_hovered = 1
    end

    item_hovered = 0
    if (gh_imgui.is_any_item_hovered() == 1) then
      item_hovered = 1
    end
  


  end 

  imgui_window_end()

  imgui_frame_end()
--]]


  