text:
struct MPOperator{Y<:Tensor{T,4} where T}
    Ws :: Vector{Y}
end

@inline tensortype(::MPOperator{Y}) where {Y} = Y
@inline Base.eltype(::MPOperator{Y}) where {Y} = eltype(Y)
@inline Base.length(mpo::MPOperator) = length(mpo.Ws)
#@inline checkbounds(mpo, l) = 0 < l <= L || throw(BoundsError(mpo.vectors), l)

@inline bondspace(mpo::MPOperator, l::Int) = dual(space(mpo.Ws[l], 3))
@inline bonddim(mpo::MPOperator, l::Int) = size(mpo.Ws[l], 3)
@inline bonddim(mpo::MPOperator) =
    [size(mpo.Ws[1], 1); [bonddim(mpo, l) for l in 1:length(mpo)]]

@inline leftspace(mpo::MPOperator) = space(mpo.Ws[1], 1)
@inline rightspace(mpo::MPOperator) = bondspace(mpo, length(mpo))

@inline sitespace(mpo::MPOperator, l::Int) = space(mpo.Ws[l], 2)

@inline function Base.push!(mpo::MPOperator{Y},
                            W::Y) where{Y}
    lx = length(mpo)

    isdual(space(W, 2), space(W, 4))
    if lx > 0
        isequal(bondspace(mpo, lx), space(W,1)) || throw("SpaceMismatch()")
    else
        size(W,1) == 1 || throw("SpaceMismatch()")
    end
    push!(mpo.Ws, W)
    mpo
end

const U1MPO{T} = MPOperator{SymTensor{U1,T,4}}
const MPO{T} = MPOperator{Array{T,4}}

MPOperator{Y}() where{Y} = MPOperator{Y}(Vector{Y}())

function convert(::Type{MPOperator{Y1}},
                 mpo::MPOperator{Y2}) where {Y1, Y2}
    MPOperator{Y1}(convert(Vector{Y1}, mpo.Ws))
end

"function to explicitly make the xxz mpo (for test and example)"
function xxz_u1mpo(T::DataType, lx::Int, d::Int, delta::Float64=1.0)

    V0 = U1Space(0=>1)
    V = U1Space(-1=>1, 0=>3, +1=>1)
    d = U1Space(0=>1, 1=>1)

    space = (V, d, dual(V), dual(d))

    Wbeg = fill(zero(T), U1(0), (V0, space[2:4]...))
    W    = fill(zero(T), U1(0), space)
    Wend = fill(zero(T), U1(0), (space[1:2]...,V0, space[4]))

    # making the generic MPO tensor
    mat0 = [
        1.   0.   0.;
        delta*-.5  0.   0.;
        0.  -0.5  1
    ]
    mat1 = [
        1.   0.   0.;
        delta*.5   0.   0.;
        0.   0.5  1
    ]
    W[Sector{U1}(0,0,0,0)] =  reshape(mat0, 3,1,3,1)
    W[Sector{U1}(0,1,0,1)] =  reshape(mat1, 3,1,3,1)

    W[Sector{U1}(0,1,1,0)] = reshape([0. 0. 1.], 3,1,1,1)
    W[Sector{U1}(0,0,-1,1)] = reshape([0. 0. 1.], 3,1,1,1)
    W[Sector{U1}(1,0,0,1)] = 0.5*reshape([1. 0. 0.], 1,1,3,1)
    W[Sector{U1}(-1,1,0,0)] = 0.5*reshape([1. 0. 0.], 1,1,3,1)

    # making the begging tensor
    Wbeg[Sector{U1}(0,0,0,0)] =  reshape(mat0[3,:], 1,1,3,1)
    Wbeg[Sector{U1}(0,1,0,1)] =  reshape(mat1[3,:], 1,1,3,1)

    Wbeg[Sector{U1}(0,1,1,0)] =  ones(T,1,1,1,1)
    Wbeg[Sector{U1}(0,0,-1,1)] =  ones(T,1,1,1,1)

    # making the end tensor
    Wend[Sector{U1}(0,0,0,0)] =  reshape(mat0[:,1], 3,1,1,1)
    Wend[Sector{U1}(0,1,0,1)] =  reshape(mat1[:,1], 3,1,1,1)

    Wend[Sector{U1}(-1,1,0,0)] =  0.5*ones(T,1,1,1,1)
    Wend[Sector{U1}(1,0,0,1)] =  0.5*ones(T,1,1,1,1)

    mpo = U1MPO{T}()
    push!(mpo, Wbeg)
    for site=2:lx-1
        push!(mpo, W)
    end
    push!(mpo, Wend)
    mpo
end

### conversions
###############

MPOperator{Y}(mpo::MPOperator{Y}) where {Y} = mpo
function MPOperator{Y}(mpo::MPOperator) where {Y}
    MPOperator{Y}([Y(W) for W in mpo])
end

# function MatrixProductOperator(mpo::MPOperator{T}) where {T<:Number}
#     MatrixProductOperator{T}(mpo.lx, mpo.d, mpo.dims,
#                              [array(mat) for mat in mpo.tensors])
# end

function mpo2hamiltonian(mpo::MPOperator)
    lx = length(mpo)
    ds = [dim(sitespace(mpo, l)) for l in 1:lx]
    prod(ds) > 10000 && error("model is too large for explicit Hamiltonian!")
    H = mpo.Ws[1]
    for two in mpo.Ws[2:lx-1]
        H = fuselegs(fuselegs(contract(H, (1, 2, -1, 5),
                                       two, (-1, 3, 4, 6)),
                              5, 2),
                     2, 2)
    end
    H = fuselegs(fuselegs(contract(H, (-2, 1, -1, 3),
                                       mpo.Ws[lx], (-1, 2, -2, 4)),
                              3, 2),
                 1, 2)
    H
end

function reducempo!(mpo::MPOperator)
    if dim(rightspace(mpo)) != 1
        println(rightspace(mpo))
        return _reduceinfinitempo!(mpo)
    end
    lx = length(mpo)
    W = mpo.Ws[1]
    for l=1:lx-1
        u,s,v = svdtrunc(fuselegs(permutelegs(W,
                                              [1,2,4,3]),
                                  1, 3))
        mpo.Ws[l] = permutelegs(splitleg(u*s, 1,
                                         W.space[[1,2,4]]),
                                [1,2,4,3])
        W = contract(v, (1, -1), mpo.Ws[l+1], (-1, 2, 3, 4))
    end

    for l=lx:-1:2
        u,s,v = svdtrunc(fuselegs(W, 2, 3, true))
        mpo.Ws[l] = splitleg(s*v, 2, W.space[2:4])

        W = contract(mpo.Ws[l-1], (1,2,-1,4), u, (-1, 3))
    end
    mpo.Ws[1] = W
    mpo
end

function _reduceinfinitempo!(mpo::MPOperator)
    A = mpo.Ws[1]
    for l=1:mpo.lx-1
        U, S, Vt = svdtrunc(fuselegs(permutelegs(A,
                                                 [1,2,4,3]),
                                     +1, 1, 3))
        mpo.dims[l+1] = size(S, 1)
        mpo.tensors[l] = permutelegs(unfuseleg(U*S, 1,
                                               A.legs[[1,2,4]]),
                                     [1,2,4,3])
        A = contract(Vt, (1, -1), mpo.tensors[l+1], (-1, 2, 3, 4))
    end
    U, S, Vt = svdtrunc(fuselegs(permutelegs(A,
                                             [1,2,4,3]),
                                 +1, 1, 3))
    mpo.dims[mpo.lx+1] = size(S, 1)
    mpo.dims[1] = size(S, 1)
    mpo.tensors[mpo.lx] = permutelegs(unfuseleg(U*S, 1,
                                            A.legs[[1,2,4]]),
                                  [1,2,4,3])
    A = contract(Vt, (1, -1), mpo.tensors[1], (-1, 2, 3, 4))

    U, S, Vt = svdtrunc(fuselegs(A, -1, 2, 3))
    mpo.dims[mpo.lx+1] = size(S, 1)
    mpo.dims[1] = size(S, 1)
    mpo.tensors[1] = unfuseleg(S*Vt, 2, A.legs[2:4])

    A = contract(mpo.tensors[mpo.lx], (1,2,-1,4), U, (-1, 3))

    for l=mpo.lx:-1:2
        U, S, Vt = svdtrunc(fuselegs(A, -1, 2, 3))
        mpo.dims[l] = size(S, 1)
        mpo.tensors[l] = unfuseleg(S*Vt, 2, A.legs[2:4])

        A = contract(mpo.tensors[l-1], (1,2,-1,4), U, (-1, 3))
    end
    mpo.tensors[1] = A#mpo.tensors[mpo.lx] = A

    mpo
end

max_stars_repo_name: amiragha/mps

max_issues_repo_name: amiragha/mps

max_forks_repo_name: amiragha/mps


text:
using StructuredProximalOperators
using LinearAlgebra
using Random
using Manifolds

function get_Ṡ(::FixedRankMatrices{m,n,k,ℝ}, x, ξ) where {m,n,k}
    return Vector([ξ.M[i, i] for i in 1:k])
end

function get_U̇(::FixedRankMatrices{m,n,k,ℝ}, x, ξ) where {m,n,k}
    F = zeros(k, k)
    for i in 1:k, j in 1:k
        if x.S[i] != x.S[j]
            F[i, j] = 1 / (x.S[j]^2 - x.S[i]^2)
        end
    end

    return x.U * (F .* (ξ.M * Diagonal(x.S) + Diagonal(x.S) * ξ.M')) +
           ξ.U * Diagonal(x.S .^ (-1))
end
function get_V̇t(::FixedRankMatrices{m,n,k,ℝ}, x, ξ) where {m,n,k}
    F = zeros(k, k)
    for i in 1:k, j in 1:k
        if x.S[i] != x.S[j]
            F[i, j] = 1 / (x.S[j]^2 - x.S[i]^2)
        end
    end

    return (-F .* (Diagonal(x.S) * ξ.M + ξ.M' * Diagonal(x.S))) * x.Vt +
           Diagonal(x.S .^ (-1)) * ξ.Vt
end

function main()
    m, n = 5, 6
    k = 3
    M = FixedRankMatrices{m,n,k,ℝ}()

    Random.seed!(152)
    x = randomMPoint(M)
    Random.seed!(4158)
    ξ = randomTVector(M, x)

    x_emb = embed(M, x)
    ξ_emb = embed(M, x, ξ)

    γ(t) = retract(M, x, t * ξ)
    function errors_retraction(t)
        return norm(embed(M, γ(t)) - x_emb - t * ξ_emb)
    end
    function errors_proj_retraction(t)
        return norm(project(M, x, embed(M, γ(t)) - x_emb - t * ξ_emb))
    end

    #
    ## Symmetric term
    #
    S = x.S
    Ṡ = get_Ṡ(M, x, ξ)
    function errorsS(t)
        F = svd(embed(M, γ(t)))
        return norm(F.S[1:k] - (S + t * Ṡ))
    end

    #
    ## U term
    #
    U = x.U
    U̇ = get_U̇(M, x, ξ)
    function errorsU(t)
        F = svd(embed(M, γ(t)))
        return norm(F.U[:, 1:k] - (U + t * U̇))
    end

    #
    ## Vt term
    #
    Vt = x.Vt
    V̇t = get_V̇t(M, x, ξ)
    function errorsVt(t)
        F = svd(embed(M, γ(t)))
        return norm(F.Vt[1:k, :] - (Vt + t * V̇t))
    end

    comparison = compare_curves(
        errors_retraction,
        errors_proj_retraction,
        errorsU,
        errorsS,
        errorsVt,
    )
    return display_curvescomparison(comparison)
end

main()

ext: jl


text:
struct BrentMin{T}
    division::T
    detect_flatness::Bool
end
BrentMin(; division=(3-sqrt(5))/2, detect_flatness=false) = BrentMin(division, detect_flatness)

function solve(problem::OptimizationProblem, approach::BrentMin, options::OptimizationOptions)
    _solve(problem, approach, options)
end

function _solve(prob, bm::BrentMin, options)
	a, b = bounds(prob)
    T = typeof(a)
    t = 1e-8
    c = bm.division
    v = w = x = a + c*(b - a)

    e = d = 0*x

    fv = fw = fx = value(prob, x)
    p = q = r = 0*x

    for i = 1:options.maxiter
        m = (a + b)/2
        tol = eps(T)*abs(x) + t
        if abs(x - m) > 2*tol - (b - a)/2 # stopping crit
            # fit parabola
            if abs(e) > tol
                r = (x - w)*(fx - fv)
                q = (x - v)*(fx - fw)
                p = (x - v)*q - (x - w)*r
                q = 2*(q - r)
                if q > 0
                    p = -p
                else
                    q = -q
                end
                r = e
                e = d
            end

            if abs(p) < abs(q*r/2) && p < q*(a - x) && p < q*(b - x)
                # the do the parapolic interpolation
                d = p/q
                u = x + d
                if u - a < 2*tol || b - u < 2*tol
                    if x < m # this should just use sign
                        d = tol
                    else
                        d = -tol
                       end
                end
            else
                # do golden section
                if x < m
                    e = b - x
                else
                    e = a - x
                end
                d = c * e
            end

            if abs(d) >= tol
                u = x + d
            else
            	u = x + sign(d)*tol
            end

            fu = value(prob, u)
            if fu <= fx
                if u < x
                    b = x
                else
                    a = x
                end
                v = w
                fv = fw
                w = x
                fw = fx
                x = u
                fx = fu
            else
                if u < x
                    a = u
                else
                    b = u
                end
                if fu <= fw || w == x
                    v = w
                    fv = fw
                    w = u
                    fw = fu
                elseif fu <= fv || v == x || v == w
                    v = u
                    fv = fu
                end
            end
        else
            break
        end
    end
    return x, fx
end

#=
prob = OptimizationProblem(ScalarObjective(;f=x->sign(x)), (-2.0,2.0))
@time solve(prob, 0.0, BrentMin(), OptimizationOptions())

=#


text:
using Distributed
using JLD2

@time @everywhere include("../BioClim/src/required.jl")
cd("/Users/kiristern/Documents/GitHub/SDM")
## Get & prepare data
@time @everywhere begin
    # Load data from CSV files
     df = CSV.read("/Users/kiristern/Documents/GitHub/SDM/data/pred_prey/Puma_concolor.csv", header=true)
    # Prepare data (select columns, arrange values)
    df = prepare_gbif_data(pred_prey)
    # Separate species
    taxa_occ = [df[df.species .== u,:] for u in unique(df.species)]
end

# convert year "string" to "integer"
#df.longitude = parse.(Float64, df.longitude)

# Get the worldclim data by their layer number
cd("/Users/kiristern/Documents/GitHub/BioClim/")

#prediction pred
@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[1]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[1]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction1 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction1[taxa_occ[1]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction1.grid)
    prediction1.grid[i] < threshold && (prediction1.grid[i] = NaN)
end

#prediction prey1
@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[2]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[2]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction2 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction2[taxa_occ[2]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction2.grid)
    prediction2.grid[i] < threshold && (prediction2.grid[i] = NaN)
end

@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[3]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[3]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction3 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction3[taxa_occ[3]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction3.grid)
    prediction3.grid[i] < threshold && (prediction3.grid[i] = NaN)
end

@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[4]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[4]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction4 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction4[taxa_occ[4]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction4.grid)
    prediction4.grid[i] < threshold && (prediction4.grid[i] = NaN)
end

@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[5]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[5]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction5 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction5[taxa_occ[5]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction5.grid)
    prediction5.grid[i] < threshold && (prediction5.grid[i] = NaN)
end

@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[6]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[6]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction6 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction6[taxa_occ[6]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction6.grid)
    prediction6.grid[i] < threshold && (prediction6.grid[i] = NaN)
end

@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[7]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[7]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction7 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction7[taxa_occ[7]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction7.grid)
    prediction7.grid[i] < threshold && (prediction7.grid[i] = NaN)
end

@info "Extract and crop bioclim variables"
@time wc_vars = [clip(worldclim(i), taxa_occ[8]) for i in 1:19];
# Make the prediction for each layer
@info "Predictions for each layer"
@time predictions = [bioclim(wc_vars[i], taxa_occ[8]) for i in 1:length(wc_vars)];
# Make the final prediction by taking the minimum
@info "Minimum-consensus aggregation"
@time prediction8 = reduce(minimum, predictions);
# Get the threshold for NaN given a percentile
@info "Threshold estimation"
@time threshold = first(quantile(prediction8[taxa_occ[8]], [0.05]))
@info "5% threshold:\t$(round(threshold; digits=3))"
# Filter the predictions based on the threshold
@info "Final prediction filtering"
@time for i in eachindex(prediction8.grid)
    prediction8.grid[i] < threshold && (prediction8.grid[i] = NaN)
end

predictions = [prediction1, prediction2, prediction3, prediction4, prediction5, prediction6, prediction7, prediction8]

# ## Get the worldclim data
# @time wc_vars = map(x -> worldclim(x)[lon_range, lat_range], 1:19);
#
# ## Make predictions for all species
# @time predictions = map(x -> species_bclim(x, wc_vars), taxa_occ);

## Export predictions
@save "data/predictions_procyon_lotor.jld2" predictions

# Test import
@load "data/predictions.jld2" predictions

max_stars_repo_name: kiristern/SDM

max_issues_repo_name: kiristern/SDM

max_forks_repo_name: kiristern/SDM


text:
# Defines the types Centre, LazyCentre, and associated
# functions. These represent a sequence of elements at
# the middle of each row and are zero-indexed. That is,
# if c is a Centre, then c[i] is the value of the middle
# element(s) of row i.

# LazyCentre calculates only the elements actually
# requested, Centre calculates all the requested elements
# up front.

# Center and LazyCenter are accepted aliases.
"""
    AbstractCentre <: AbstractVector

Supertype of all types representing the central elements
of Pascal's triangle.
"""
abstract type AbstractCentre{V <: Real} <: AbstractVector{V} end
Base.IndexStyle(::AbstractCentre) = IndexLinear()

"""
    Centre(data)
    Centre(c::Centre)
    Centre{V}(maxrow)
    Centre(maxrow)
    Centre(l::LazyCentre)

Represents a finite sequence of central elements of Pascal's
triangle. That is, the middle value(s) in each row, starting
from row 0 and up to `maxrow`.

`Center` is an alias for `Centre`.
"""
struct Centre{V <: Real} <: AbstractCentre{V}
    data::Vector{V}
end
Centre(c::Centre) = Centre(copy(c.data))
function Centre{V}(maxrow::Integer) where {V <: Real}
    maxrow ≥ 0 || throw(DomainError("maxrow must be non-negative, maxrow: $maxrow"))
    data = ones(V, maxrow+1)
    if maxrow ≥ 2
        e = Entry(2, 1, V(2))
        for i ∈ 3:2:maxrow-1
            data[i] = e.val
            down!(e)
            data[i+1] = e.val
            e.n += 1
            e.k += 1
            e.val *= 2
        end
        if iseven(maxrow)
            data[maxrow+1] = e.val
        end
    end
    return Centre{V}(data)
end
Centre(maxrow::Integer) = Centre{BigInt}(maxrow)

value(c::Centre) = c.data
Base.values(c::Centre) = value(c)

Base.axes(c::Centre) = (ZeroRange(length(c.data)-1),)
Base.axes(c::Centre, d::Integer) = d == 1 ? axes(c)[1] : nothing
Base.LinearIndices(c::Centre) = ZeroRange(length(c.data)-1)
Base.size(c::Centre) = (length(c.data),)
function Base.getindex(c::Centre, i::Int)
    0 ≤ i < length(c.data) || throw(BoundsError(c, i))
    return c.data[i+1]
end
Base.getindex(c::Centre, ::Colon) = value(c)
Base.getindex(c::Centre, I) = [c[i] for i ∈ I]
Base.firstindex(c::Centre) = 0
Base.lastindex(c::Centre) = length(c.data)-1
function toarray(c::Centre, leftbias=true)
    rm = leftbias ? RoundDown : RoundUp
    ns = axes(c,1)
    ks = div.(ns, 2, rm)
    return Entry.(ns, ks, c.data)
end

function tostring(c::Centre; oneline=false)
    sep = oneline ? ", " : "\n"
    v = values(c)
    s = ""
    if length(v) < THRESHOLD
        s *= join(v, sep)
    else
        s *= join(v[1:HALF], sep)
        s *= oneline ? ", …, " : "\n⋮\n"
        s *= join(v[end-HALF+1:end], sep)
    end
    return s
end
function Base.show(io::IO, ::MIME"text/plain", c::Centre)
    print(io, "$(typeof(c).name){$(eltype(c).name)}")
    print(io, " up to row $(length(c.data)-1)\n")
    print(io, tostring(c))
end
function Base.show(io::IO, c::Centre)
    print(io, tostring(c, oneline=true))
end

function isvalid(c::Centre)
    ns = big(0):length(c.data)-1
    ks = ns .÷ 2
    return value(c) == binomial.(ns, ks)
end

"""
    LazyCentre(data)
    LazyCentre(l::LazyCentre)
    LazyCentre{V}()
    LazyCentre()
    LazyCentre(c::Centre)

Represents the central elements of Pascal's triangle but
lazily evaluated. `data` can be supplied as a dictionary
mapping a row number to a value. `V` is a data type for
the values.

`LazyCenter` is an alias for `LazyCentre`.
"""
struct LazyCentre{V <: Real} <: AbstractCentre{V}
    data::Dict{Int,V}
end
LazyCentre(c::LazyCentre) = LazyCentre(copy(c.data))
LazyCentre{V}() where {V <: Real} = LazyCentre{V}(Dict{Int,V}(0 => 1, 1 => 1))
LazyCentre() = LazyCentre{BigInt}()
LazyCentre(c::Centre) = LazyCentre(Dict(axes(c,1) .=> values(c)))

const PRECALC_NUMBER = 5
function Base.getindex(c::LazyCentre, i::Int)
    0 ≤ i || throw(BoundsError(c, i))
    if haskey(c.data, i)
        return c.data[i]
    end
    # Calculating adjacent entries is ≈ 10x faster than calculating
    # isolated entries using binomial, so when calculating a new
    # value we do several either side of it.
    e = Entry{valtype(c.data)}(i, i÷2)
    uprange = (i-1):-1:max(2,i-PRECALC_NUMBER)
    a = Entry(e)
    for j ∈ uprange
        up!(a)
        if isodd(a.n)
            a.k -= 1
        end
        if !haskey(c.data, j)
            c.data[j] = a.val
        end
    end
    downrange = (i+1):(i+PRECALC_NUMBER)
    a = Entry(e)
    for j ∈ downrange
        down!(a)
        if isodd(a.n)
            a.k += 1
        end
        if !haskey(c.data, j)
            c.data[j] = a.val
        end
    end
    return e.val
end
Base.getindex(c::LazyCentre, I) = [c[i] for i ∈ I]
Base.firstindex(c::LazyCentre) = 0
Base.IteratorSize(::LazyCentre) = Base.IsInfinite()
Base.eltype(::LazyCentre{V}) where {V <: Real} = V

Base.:(==)(a::LazyCentre, b::LazyCentre) = a.data == b.data

function isvalid(c::LazyCentre)
    return all(c.data) do (n, val)
        binomial(n, n÷2) == val
    end
end

function toarray(c::LazyCentre, leftbias=true)
    rm = leftbias ? RoundDown : RoundUp
    ns = keys(c.data)
    ks = div.(ns, 2, rm)
    return Entry.(ns, ks, values(c.data))
end

function Base.show(io::IO, ::MIME"text/plain", c::LazyCentre)
    print(io, "$(typeof(c).name){$(eltype(c).name)}")
    print(io, "($(length(c.data)) elements precalculated, ")
    print(io, "up to row $(maximum(keys(c.data))))")
end
function Base.show(io::IO, c::LazyCentre)
    print(io, "$(typeof(c).name)")
    print(io, "($(length(c.data)))")
end

function Centre(c::LazyCentre)
    data = eltype(c)[]
    i = 0
    while haskey(c.data, i)
        push!(data, c.data[i])
        i += 1
    end
    return Centre(data)
end

const Center = Centre
const LazyCenter = LazyCentre


text:
const COORD_DIVIDER = 1000
const OCC_B_FACTOR_DIVIDER = 100
const MAX_SHORT = 32767
const MIN_SHORT = -32768
const NULL_BYTE = '\0'
const CHAIN_LEN = 4
const BASE_URL = "http://mmtf.rcsb.org/v1.0/full"
# UNKNOWN_SPACE_GROUP = ""
# UNKNOWN_UNIT_CELL = ""
# UNKOWN_GROUP_NAME = "UNL"
# UNKOWN_GROUP_TYPE = "other"
# UNKNOWN_SL = "X"


text:
module ImmutableArrays

include("generate_arrays.jl")
generate_arrays(4)

export Vector1,   Vector2,   Vector3,   Vector4,
       Matrix1x1, Matrix1x2, Matrix1x3, Matrix1x4,
       Matrix2x1, Matrix2x2, Matrix2x3, Matrix2x4,
       Matrix3x1, Matrix3x2, Matrix3x3, Matrix3x4,
       Matrix4x1, Matrix4x2, Matrix4x3, Matrix4x4

end


text:
# what is this even?
function localMinima( image::Array{T,2} ) where {T<:Real}

    minima_r = [];
    minima_c = [];

    prevGH = zeros( Bool, size(image,1) )
    for col in 2:size(image,2)

        column = view( image, :, col )

        gradH = image[ :, col ] .- image[ :, col - 1 ];

        prevGV = false;
        for row in 2:size(image,1)

            gradV = column[row] - column[row-1]

            if ( prevGV && gradV > 0 && prevGH[row] && gradH[row] > 0 )
                push!( minima_r, row-1 )
                push!( minima_c, col-1 )
            end

            prevGV = gradV < 0;
        end

        prevGH .= gradH .< 0
    end

    return( minima_r, minima_c )
end


text:
### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ c2efdfae-502c-11eb-24f3-ef331e609508
md"""
# Set

Set is a collection without an order and doesn't allow repeatition of an element. And similar to Mathematical set we can perform set operations on these data structures
"""

# ╔═╡ 08c92a08-502d-11eb-056b-794d9230ea3f
begin
	A = Set([1,2,3])
	B = Set([2,3,4])
	A ∪ B
end

# ╔═╡ 1b8e7af8-502d-11eb-269c-d1f3e80a2b0e

# ╔═╡ d61a4b96-502c-11eb-0265-ef3964d04fd1
md"""
# Map (Dictionaries)

Associative Data Structure that associates a Key with a Value. Keys in a map is a set, i.e each key exist only once.

"""

# ╔═╡ 27bdcffc-502d-11eb-25ba-fbc0720d76fb
dict1 = ["a" => 1, "b"=>2, "c"=>3]

# ╔═╡ dc9ec154-502c-11eb-30ac-61ee85f7dc15
md"""
# Hashing allows O(1) lookups

- In arrays, list we need to look through every element hence in lookups are in linear time.
- In stacks and queues, we can lookup the start or end almost immediately
- priority queues allows to access priority element fast but linear time for other elements

Hash functions coverts a value to a hash value that is used as index in an array. How ?
- One easiest approach is to divide the last few digits by certain number and use remainder as index to store the elements in as an array. And access elements by indexing in constant time.
-

Collision (same hash value for multiple item) can occur. There are two main ways to handle collision
1. Change hash function completely, or change the number for division to get enough slots for all values
    - using bigger number need a larger space to maintain a constant time lookup. Changing numbers requires moving data to a new arrays
2. Change the structure of arrays to buckets: rather than store one value in the slot store collection of values in the buckets
    - need to iterate through a list or link list. on average has a constant time look up but in worst case it can become linear time O(n).
    - Can use second hash function inside the bucket to further split the data

"""

# ╔═╡ e0e1b918-502c-11eb-1506-819b3261e35e
md"""
# Hash Maps

using Keys to create Hash values.

Strings can be converted using ASCII values for first few laters as hash values.

"""

# ╔═╡ e69324b6-502c-11eb-0307-13492268de72

# ╔═╡ Cell order:
# ╠═c2efdfae-502c-11eb-24f3-ef331e609508
# ╠═08c92a08-502d-11eb-056b-794d9230ea3f
# ╠═1b8e7af8-502d-11eb-269c-d1f3e80a2b0e
# ╠═d61a4b96-502c-11eb-0265-ef3964d04fd1
# ╠═27bdcffc-502d-11eb-25ba-fbc0720d76fb
# ╠═dc9ec154-502c-11eb-30ac-61ee85f7dc15
# ╠═e0e1b918-502c-11eb-1506-819b3261e35e
# ╠═e69324b6-502c-11eb-0307-13492268de72


text:
# TODO: Group Wasmtime.WasmEngine and Wasmer.WasmEngine

mutable struct WasmEngine <: AbstractWasmEngine
    wasm_engine_ptr::Ptr{wasm_engine_t}

    WasmEngine(wasm_engine_ptr) =
        finalizer(new(wasm_engine_ptr)) do wasm_engine
            wasm_engine_delete(wasm_engine_ptr)
        end
end
function WasmEngine()
    wasm_engine_ptr = wasm_engine_new()
    WasmEngine(wasm_engine_ptr)
end

Base.unsafe_convert(::Type{Ptr{wasm_engine_t}}, wasm_engine::WasmEngine) =
    wasm_engine.wasm_engine_ptr
Base.show(io::IO, ::WasmEngine) = print(io, "WasmEngine()")


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

struct SubnetsApi <: SwaggerApi
    client::Swagger.Client
end

"""

Creates or updates a subnet in the specified virtual network.
Param: resourceGroupName::String (required)
Param: virtualNetworkName::String (required)
Param: subnetName::String (required)
Param: subnetParameters::Subnet (required)
Param: api_version::String (required)
Param: subscriptionId::String (required)
Return: Subnet
"""
function _swaggerinternal_subnetsCreateOrUpdate(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, subnetName::String, subnetParameters, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = Swagger.Ctx(_api.client, "PUT", Subnet, "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}/subnets/{subnetName}", ["azure_auth"], subnetParameters)
    Swagger.set_param(_ctx.path, "resourceGroupName", resourceGroupName)  # type String
    Swagger.set_param(_ctx.path, "virtualNetworkName", virtualNetworkName)  # type String
    Swagger.set_param(_ctx.path, "subnetName", subnetName)  # type String
    Swagger.set_param(_ctx.path, "subscriptionId", subscriptionId)  # type String
    Swagger.set_param(_ctx.query, "api-version", api_version)  # type String
    Swagger.set_header_accept(_ctx, ["application/json"])
    Swagger.set_header_content_type(_ctx, (_mediaType === nothing) ? ["application/json"] : [_mediaType])
    return _ctx
end

function subnetsCreateOrUpdate(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, subnetName::String, subnetParameters, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsCreateOrUpdate(_api, resourceGroupName, virtualNetworkName, subnetName, subnetParameters, api_version, subscriptionId; _mediaType=_mediaType)
    Swagger.exec(_ctx)
end

function subnetsCreateOrUpdate(_api::SubnetsApi, response_stream::Channel, resourceGroupName::String, virtualNetworkName::String, subnetName::String, subnetParameters, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsCreateOrUpdate(_api, resourceGroupName, virtualNetworkName, subnetName, subnetParameters, api_version, subscriptionId; _mediaType=_mediaType)
    Swagger.exec(_ctx, response_stream)
end

"""

Deletes the specified subnet.
Param: resourceGroupName::String (required)
Param: virtualNetworkName::String (required)
Param: subnetName::String (required)
Param: api_version::String (required)
Param: subscriptionId::String (required)
Return: Nothing
"""
function _swaggerinternal_subnetsDelete(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, subnetName::String, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = Swagger.Ctx(_api.client, "DELETE", Nothing, "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}/subnets/{subnetName}", ["azure_auth"])
    Swagger.set_param(_ctx.path, "resourceGroupName", resourceGroupName)  # type String
    Swagger.set_param(_ctx.path, "virtualNetworkName", virtualNetworkName)  # type String
    Swagger.set_param(_ctx.path, "subnetName", subnetName)  # type String
    Swagger.set_param(_ctx.path, "subscriptionId", subscriptionId)  # type String
    Swagger.set_param(_ctx.query, "api-version", api_version)  # type String
    Swagger.set_header_accept(_ctx, ["application/json"])
    Swagger.set_header_content_type(_ctx, (_mediaType === nothing) ? ["application/json"] : [_mediaType])
    return _ctx
end

function subnetsDelete(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, subnetName::String, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsDelete(_api, resourceGroupName, virtualNetworkName, subnetName, api_version, subscriptionId; _mediaType=_mediaType)
    Swagger.exec(_ctx)
end

function subnetsDelete(_api::SubnetsApi, response_stream::Channel, resourceGroupName::String, virtualNetworkName::String, subnetName::String, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsDelete(_api, resourceGroupName, virtualNetworkName, subnetName, api_version, subscriptionId; _mediaType=_mediaType)
    Swagger.exec(_ctx, response_stream)
end

"""

Gets the specified subnet by virtual network and resource group.
Param: resourceGroupName::String (required)
Param: virtualNetworkName::String (required)
Param: subnetName::String (required)
Param: api_version::String (required)
Param: subscriptionId::String (required)
Param: expand::String
Return: Subnet
"""
function _swaggerinternal_subnetsGet(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, subnetName::String, api_version::String, subscriptionId::String; expand=nothing, _mediaType=nothing)
    _ctx = Swagger.Ctx(_api.client, "GET", Subnet, "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}/subnets/{subnetName}", ["azure_auth"])
    Swagger.set_param(_ctx.path, "resourceGroupName", resourceGroupName)  # type String
    Swagger.set_param(_ctx.path, "virtualNetworkName", virtualNetworkName)  # type String
    Swagger.set_param(_ctx.path, "subnetName", subnetName)  # type String
    Swagger.set_param(_ctx.path, "subscriptionId", subscriptionId)  # type String
    Swagger.set_param(_ctx.query, "api-version", api_version)  # type String
    Swagger.set_param(_ctx.query, "\$expand", expand)  # type String
    Swagger.set_header_accept(_ctx, ["application/json"])
    Swagger.set_header_content_type(_ctx, (_mediaType === nothing) ? ["application/json"] : [_mediaType])
    return _ctx
end

function subnetsGet(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, subnetName::String, api_version::String, subscriptionId::String; expand=nothing, _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsGet(_api, resourceGroupName, virtualNetworkName, subnetName, api_version, subscriptionId; expand=expand, _mediaType=_mediaType)
    Swagger.exec(_ctx)
end

function subnetsGet(_api::SubnetsApi, response_stream::Channel, resourceGroupName::String, virtualNetworkName::String, subnetName::String, api_version::String, subscriptionId::String; expand=nothing, _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsGet(_api, resourceGroupName, virtualNetworkName, subnetName, api_version, subscriptionId; expand=expand, _mediaType=_mediaType)
    Swagger.exec(_ctx, response_stream)
end

"""

Gets all subnets in a virtual network.
Param: resourceGroupName::String (required)
Param: virtualNetworkName::String (required)
Param: api_version::String (required)
Param: subscriptionId::String (required)
Return: SubnetListResult
"""
function _swaggerinternal_subnetsList(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = Swagger.Ctx(_api.client, "GET", SubnetListResult, "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{virtualNetworkName}/subnets", ["azure_auth"])
    Swagger.set_param(_ctx.path, "resourceGroupName", resourceGroupName)  # type String
    Swagger.set_param(_ctx.path, "virtualNetworkName", virtualNetworkName)  # type String
    Swagger.set_param(_ctx.path, "subscriptionId", subscriptionId)  # type String
    Swagger.set_param(_ctx.query, "api-version", api_version)  # type String
    Swagger.set_header_accept(_ctx, ["application/json"])
    Swagger.set_header_content_type(_ctx, (_mediaType === nothing) ? ["application/json"] : [_mediaType])
    return _ctx
end

function subnetsList(_api::SubnetsApi, resourceGroupName::String, virtualNetworkName::String, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsList(_api, resourceGroupName, virtualNetworkName, api_version, subscriptionId; _mediaType=_mediaType)
    Swagger.exec(_ctx)
end

function subnetsList(_api::SubnetsApi, response_stream::Channel, resourceGroupName::String, virtualNetworkName::String, api_version::String, subscriptionId::String; _mediaType=nothing)
    _ctx = _swaggerinternal_subnetsList(_api, resourceGroupName, virtualNetworkName, api_version, subscriptionId; _mediaType=_mediaType)
    Swagger.exec(_ctx, response_stream)
end

export subnetsCreateOrUpdate, subnetsDelete, subnetsGet, subnetsList


text:
m13.1 <- ulam(
    alist(
        S ~ dbinom( N , p ) ,
        logit(p) <- a[tank] ,
        a[tank] ~ dnorm( 0 , 1.5 )
    ), data=dat , chains=4 , log_lik=TRUE )

## R code 13.3
m13.2 <- ulam(
    alist(
        S ~ dbinom( N , p ) ,
        logit(p) <- a[tank] ,
        a[tank] ~ dnorm( a_bar , sigma ) ,
        a_bar ~ dnorm( 0 , 1.5 ) ,
        sigma ~ dexp( 1 )
    ), data=dat , chains=4 , log_lik=TRUE )

## R code 13.13
dat <- list( Si=dsim$Si , Ni=dsim$Ni , pond=dsim$pond )
m13.3 <- ulam(
    alist(
        Si ~ dbinom( Ni , p ),
        logit(p) <- a_pond[pond],
        a_pond[pond] ~ dnorm( a_bar , sigma ),
        a_bar ~ dnorm( 0 , 1.5 ),
        sigma ~ dexp( 1 )
    ), data=dat , chains=4 )

## R code 13.21
library(rethinking)
data(chimpanzees)
d <- chimpanzees
d$treatment <- 1 + d$prosoc_left + 2*d$condition

dat_list <- list(
    pulled_left = d$pulled_left,
    actor = d$actor,
    block_id = d$block,
    treatment = as.integer(d$treatment) )

set.seed(13)
m13.4 <- ulam(
    alist(
        pulled_left ~ dbinom( 1 , p ) ,
        logit(p) <- a[actor] + g[block_id] + b[treatment] ,
        b[treatment] ~ dnorm( 0 , 0.5 ),
        # adaptive priors
        a[actor] ~ dnorm( a_bar , sigma_a ),
        g[block_id] ~ dnorm( 0 , sigma_g ),
        # hyper-priors
        a_bar ~ dnorm( 0 , 1.5 ),
        sigma_a ~ dexp(1),
        sigma_g ~ dexp(1)
    ) , data=dat_list , chains=4 , cores=4 , log_lik=TRUE )

## R code 13.23
m13.5 <- ulam(
    alist(
        pulled_left ~ dbinom( 1 , p ) ,
        logit(p) <- a[actor] + b[treatment] ,
        b[treatment] ~ dnorm( 0 , 0.5 ),
        a[actor] ~ dnorm( a_bar , sigma_a ),
        a_bar ~ dnorm( 0 , 1.5 ),
        sigma_a ~ dexp(1)
    ) , data=dat_list , chains=4 , cores=4 , log_lik=TRUE )

## R code 13.25
m13.6 <- ulam(
    alist(
        pulled_left ~ dbinom( 1 , p ) ,
        logit(p) <- a[actor] + g[block_id] + b[treatment] ,
        b[treatment] ~ dnorm( 0 , sigma_b ),
        a[actor] ~ dnorm( a_bar , sigma_a ),
        g[block_id] ~ dnorm( 0 , sigma_g ),
        a_bar ~ dnorm( 0 , 1.5 ),
        sigma_a ~ dexp(1),
        sigma_g ~ dexp(1),
        sigma_b ~ dexp(1)
    ) , data=dat_list , chains=4 , cores=4 , log_lik=TRUE )

## R code 13.26
m13x <- ulam(
    alist(
        v ~ normal(0,3),
        x ~ normal(0,exp(v))
    ), data=list(N=1) , chains=4 )
precis(m13x)

## R code 13.27
m13y <- ulam(
    alist(
        v ~ normal(0,3),
        z ~ normal(0,1),
        gq> real[1]:x <<- z*exp(v)
    ), data=list(N=1) , chains=4 )
precis(m13y)

## R code 13.28
m13.4b <- ulam( m13.4 , chains=4 , cores=4 , control=list(adapt_delta=0.99) )
divergent(m13.4b)

## R code 13.29
m13.4nc <- ulam(
    alist(
        pulled_left ~ dbinom( 1 , p ) ,
        logit(p) <- a_bar + z[actor]*sigma_a + # actor intercepts
                    x[block_id]*sigma_g +      # block intercepts
                    b[treatment] ,
        b[treatment] ~ dnorm( 0 , 0.5 ),
        z[actor] ~ dnorm( 0 , 1 ),
        x[block_id] ~ dnorm( 0 , 1 ),
        a_bar ~ dnorm( 0 , 1.5 ),
        sigma_a ~ dexp(1),
        sigma_g ~ dexp(1)
    ) , data=dat_list , chains=4 , cores=4 )


text:
include("../input_data/read_data.jl")
include("../basics/init.jl")
include("../input_data/data_manipulation.jl")
#include("../src/functions.jl")

possible_tasks = Array{Task, 1}()
tasks_by_position = Dict{Int, Array}()
prec = Dict{Int, Array}()
task_times = zeros(Int, P, C)

for p = 1:P
    l=[]
    for t=1:CP
        target_bay = bj[cpij[t,1]+1]
        if p==P
            #create array with all tasks
            push!(possible_tasks, Task(cpij[t,1]+1, target_bay, cpij[t,2]+1, cpij[t,3]))
            #create matrix with all task times
            task_times[cpij[t,1]+1, cpij[t,2]+1] = cpij[t,3]
        end
        #create dict with the tasks sorted by position
        if cpij[t,1]+1==p
            push!(l,Task(cpij[t,1]+1, target_bay, cpij[t,2]+1, cpij[t,3]))
        end
    end
    tasks_by_position[p]=l

    #create dict with the position precedences
    s=[]
    for j = 1:P
        if prejj[p,j]==1
            push!(s,j)
        end
    end
    prec[p]=s

end

H = horizon_plan(C,P,J,Q,tt,d,tasks_by_position)
CTS = Constants(C,P,J,Q,H,tt,d)

max_stars_repo_licenses: Zlib MIT

max_issues_repo_licenses: Zlib MIT

max_forks_repo_licenses: Zlib MIT


text:
module SCRIPT

using GLFW, ModernGL, SharedArrays, StaticArrays, Quaternions

using ..WindowManager
using ..GraphicsManager
using ..DefaultModelData
using ..CameraManager
using ..FrustumManager
using ..ChunkManager
using ..MeshManager
using ..TextureManager
using ..ShaderManager
using ..ScriptManager
using ..TimeManager
using ..LogManager
using ..Math

######################################

struct IndirectCommand
         count::GLuint
     primCount::GLuint
    firstIndex::GLuint
    baseVertex::GLuint
  baseInstance::GLuint

  IndirectCommand(count=0, primCount=0) = new(count,primCount,0,0,0)
end

######################################

println("Init Vars.")

const GPU = GraphicsManager

TITLE = "Blocks Game"
STARTTIME = time()
PREVTIME = STARTTIME
FRAMES = 0
MAX_FRAMES = 0
FPS = 0
MAX_FPS = 0
ITERATION = 0

REF_PREVTIME = Ref(0.0)

WIDTH = 1280 #800
HEIGHT = 800 #600
RATIO = WIDTH/(HEIGHT*1f0)
SIZE = WIDTH * HEIGHT
FOV = 60.0f0
CLIP_NEAR = 0.001f0
CLIP_FAR = 10000.0f0
BLOCK_COUNT = 0

CAM_LOCK = false
WIREFRAME = false
TEXTUREMODE = true
LIGHTMODE = true
FRUSTUM_CULLING = true
HIDE_UNSEEN_CUBES = true
RENDER_METHOD = 8
SCENE = 2

FRUSTUM = nothing
WINDOW = nothing

CHUNK_SIZE = 128
CHUNK1D_SIZE = CHUNK_SIZE
CHUNK2D_SIZE = CHUNK1D_SIZE * CHUNK1D_SIZE
CHUNK3D_SIZE = CHUNK2D_SIZE * CHUNK1D_SIZE

GPU_FRUSTUM = true
CAMERA = CameraManager.CAMERA
GPU_CHUNKS = ChunkManager.GPU_CHUNKS
GPU_CHUNKS_INIT = true
CAMERA_UPDATED = true
MSAA = false
UPDATE_FRUSTUM = false
WINDOW_FOCUS = true

MYCHUNK = nothing

PROGRAMS = Dict{Symbol,GLint}(
:CHUNKS=>0,:NORMAL=>0, :SCREEN=>0, :COMPUTE=>0,
:COMPUTE_CHUNKS=>0, :CHANGE_CHUNKS=>0, :INSTANCES=>0, :INDIRECT=>0,
:BG=>0, :FG=>0, :DEPTH=>0, :RASTER=>0, :DEPTH_MIP=>0
)

chunkData = nothing
chunkData_upload = nothing
chunk_instances = nothing
planeData = nothing
screenData = nothing
boxData = nothing
indirectData = nothing

current_program = 0
MVP = nothing

texture_screen = 0
texture_blocks = 0
texture_heightMap = 0
texture_depth = 0
texture_msaa = 0

shadow_sampler = 0

render_init = false
render_ready = false
uploaded = false

fileredCount = 0

#--------------------------------------------

chunk_instances = SharedArray(Float32[])
setChunkInstances(data) = global chunk_instances = data
getChunkInstances() = chunk_instances

plane_vertices = SharedArray(Float32[])
setPlaneVertices(data) = global plane_vertices = data
getPlaneVertices() = plane_vertices

box_vertices = SharedArray(Float32[])
setBoxVertices(data) = global box_vertices = data
getBoxVertices() = box_vertices

#--------------------------------------------

chunk_pos = [
  Float32[0,0,0],
  Float32[1,0,0],Float32[-1,0,0],Float32[0,0,1],Float32[0,0,-1],Float32[-1,0,-1],Float32[1,0,1],Float32[-1,0,1],Float32[1,0,-1],
  Float32[2,0,0],Float32[-2,0,0],Float32[0,0,2],Float32[0,0,-2],Float32[-2,0,-2],Float32[2,0,2],Float32[-2,0,2],Float32[2,0,-2],
  Float32[2,0,1],Float32[2,0,-1],Float32[-2,0,1],Float32[-2,0,-1],Float32[1,0,2],Float32[-1,0,2],Float32[1,0,-2],Float32[-1,0,-2]
] * CHUNK_SIZE

CHUNKS = Chunk[Chunk(;id=i,pos=chunk_pos[i]) for i=1:1] #length(chunk_pos) #Array{ChunkD,1}(undef, length(centers))

CHUNK_COUNT = length(CHUNKS)
DEPTH_SIZE = max(WIDTH, HEIGHT) #256
DEPTH_SIZE_LOG2 = round(Integer,log2(DEPTH_SIZE)) #8
LOD_LEVELS = DEPTH_SIZE_LOG2 + 1

DISPATCH_RESETER = nothing
CHUNK_ALL_BUFFERS = []
CHUNK_BUFFERS = []
CHUNK_OCCLUDED_BUFFERS = []
DISPATCH_BUFFERS = []
CHUNK_COUNTERS = []
CHUNK_INDIRECT_DRAW_BUFFERS = []
CHUNK_COUNTERS=[]
DISPATCH_COUNTERS=[]
CHUNK_OBJECTS=[]
TRANSFORM_FEEDBACK_BUFFERS=[]

frameBuffers = zeros(Integer,LOD_LEVELS)
depthrenderbuffer = 0

CHUNK_DATA = nothing
ALL_CHUNK_SIZE = CHUNK3D_SIZE*(1+8+16+24+32+40)
CHUNK_BUFFERS_SIZE = sizeof(Float32)*3*2*ALL_CHUNK_SIZE

single_indirect = true
single_storage = true

fbo_msaa = 0
rbo_msaa = 0
fbo_intermediate = 0

global_vars=Dict{Symbol,Any}()

SLEEP=0 #1f0/200

RENDER_CHUNKS_COUNT=0

#frameBufferMax = length(CHUNKS)
#frameBufferCounter=0

ITIME = 0

INDIRECT_DRAW_BUFFER_SIZE=sizeof(GLuint[0,1,0,0,0])

VISIBLE_CHUNKS=Array{Chunk,1}(undef, length(CHUNKS))
VISIBLE_CHUNKS_COUNT = 0

#atrb=[("iInstancePos",Float32,3,0),("iInstanceFlags",Float32,3,0)]
#buffsize=sizeof(Float32)*3*2*CHUNK3D_SIZE

const BUFFER = zeros(Float32,128^3*100)

println("Set Event Functions.")

""" TODO """
function main(args::Dict{Symbol,Any})
	#$(this.args),
  WINDOW = args[:WINDOW]
  println("Script: $(basename(@__FILE__)), $(args), time: $(mtime(@__FILE__))")
  WindowManager.resize(WINDOW, (800,600))
  glViewport(0, 0, WINDOW.size[1], WINDOW.size[2])
end

######################################

""" TODO """
function OnWindowFocus(focused::Bool)
end

""" TODO """
function OnWindowResize(size::Array{Int32,1})
end

""" TODO """
function OnKey(key::Number, scancode::Number, action::Number, mods::Number, unicode::Number)
end

""" TODO """
function OnMouseKey(key::Number, action::Number, mods::Number)
end

""" TODO """
function OnMousePos(pos::AbstractVector)
end

""" TODO """
function OnMouseMove(pos::AbstractVector)
end

""" TODO """
function OnCamRotate()
end

""" TODO """
function OnMove(key::Symbol, m::Number)
end

""" TODO """
function OnShaderReload(path)
end

""" TODO """
function OnPreRender()
end

""" TODO """
function OnPostRender()
end

""" TODO """
function OnRender()
  game_loop()
end

function OnPostDraw()
end

""" TODO """
function OnPreDraw()
end

""" TODO """
function OnRenderPause()
end

""" TODO """
function OnUpdate()
  UpdateTimers()
  if !OnUpdatedKeys() return end
end

""" TODO """
function OnStart()
end

""" TODO """
function OnReload()
	#global WINDOW=this.objref.WINDOW
end

""" TODO """
function OnInit()
  println("OnInit")
  #global BUFFER = zeros(Float32,128^3*6*40)

  global FRUSTUM = Frustum()

  presetCamera()

  #if myid() == 1
    global chunkData = MeshData()
    global chunkData_upload = MeshData()
    global planeData = MeshData()
    global screenData = MeshData()
    global boxData = MeshData()
    global indirectData = MeshData()
    presetTextures()
    chooseRenderMethod()
  #end

  #if myid() != 1 || length(procs()) <= 1
  #  global MYCHUNK = Chunk()
  #  createChunk(MYCHUNK)
  #end

  game_start()
end

""" TODO """
function OnUpdatedKeys()
  keyValue, keyPressed = getKey()
  if keyPressed
    resetKeys()

    if keyValue == 82 #r
      return false
    end
  end
  true
end

#---------------------------------------------

println("Set Functions.")

""" TODO """
function clearScreen()
end

""" TODO """
function presetCamera()
  setPosition(CAMERA,[0f0,0,0])
  setProjection(CAMERA, projection_perspective(FOV, RATIO, CLIP_NEAR, CLIP_FAR))
  Update(CAMERA)
  global MVP = CAMERA.MVP
  SetFrustum(FRUSTUM, FOV+10, RATIO, CLIP_NEAR, CLIP_FAR)
  SetCamera(FRUSTUM, Vec3f(CAMERA.position), Vec3f(CAMERA.position+CameraManager.forward(CAMERA)), Vec3f(0,1,0))
end

""" TODO """
function presetTextures()
  global texture_blocks = uploadTexture("blocks.png")
  global texture_heightMap = uploadTexture((1024,1024)) #uploadTextureGray("heightmap.png")
  global texture_screen = uploadTexture((WIDTH,HEIGHT))
end

""" TODO """
useProgram(program) = begin global current_program = program; glUseProgram(program) end

""" TODO """
function use_program(program, f::Function)
  if program != current_program
    @GLCHECK glUseProgram(program)
  end
  @GLCHECK f()
  if program != current_program
    @GLCHECK glUseProgram(0)
  end
end

""" TODO """
setMatrix(program, name, m) = begin cm = SMatrix{4,4,Float32}(m); glUniformMatrix4fv(glGetUniformLocation(program, name), 1, false, cm) end #const smatrix

""" TODO """
function setMVP(program, mvp)
  use_program(program, () -> begin
    @GLCHECK setMatrix(program, "iMVP", mvp)
  end)
end

function setMode(name::String, value, mode=""; program=current_program)
  if program < 0 return end
  l = glGetUniformLocation(program, name)
  if l>-1
    elems=length(value)
    isArray = isa(value, AbstractArray{Float32,1}) || isa(value, AbstractArray{Float64,1})
    isMatrix = isa(value, AbstractArray{Float32,2}) || isa(value, AbstractArray{Float64,2})
    if isa(value, Integer) glUniform1i(l, value)
    elseif isa(value, AbstractFloat) glUniform1f(l, value)
    elseif isArray && elems==1 glUniform1fv(l, 1, Float32[value...])
    elseif isArray && elems==2 glUniform2fv(l, 1, Float32[value...])
    elseif isArray && elems==3 glUniform3fv(l, 1, Float32[value...])
    elseif isArray && elems==4 glUniform4fv(l, 1, Float32[value...])
    elseif isMatrix && elems==4 glUniformMatrix2fv(l, 1, false, Float32[value...])
    elseif isMatrix && elems==9 glUniformMatrix3fv(l, 1, false, Float32[value...])
    elseif isMatrix && elems==16 glUniformMatrix4fv(l, 1, false, Float32[value...])
    else warn("MODE($mode): $name with $(string(typeof(value))) for $elems elements is not implemented yet.")
    end
  end
  if mode != "" println("MODE(",stringColor(mode;color=:yellow),"): ",stringColor(value;color=:yellow)) end
end

"""
sets a mode in a shader.
"""
function setMode(program::Number, name::String, value, mode="")
  use_program(program, () -> begin
    setMode(name, value, mode; program=program)
  end)
end

""" TODO """
function setFrustumMode()
  SetCamera(FRUSTUM, Vec3f(CAMERA.position), Vec3f(CAMERA.position+CameraManager.forward(CAMERA)), Vec3f(0,1,0))
  if !GPU_FRUSTUM
    updateChunk(MYCHUNK)
    #= global =# uploaded = :UPDATE
  end
end

""" TODO """
function updateChunk(this::Chunk)
  ##= global =# chunkData, chunkData_upload, planeData, boxData
  #println("update Chunk")

  if !GPU_FRUSTUM && FRUSTUM_CULLING
    #println("checkInFrustum")
    checkInFrustum(this, FRUSTUM)
  else
    #println("showAll")
    showAll(this)
  end

  #println("setChunkInstances")
  setChunkInstances(getData(this))
  setPlaneVertices(getVertices(FRUSTUM))
  setBoxVertices(getBox(FRUSTUM))

  global BLOCK_COUNT = this.fileredCount
  #global VERT_COUNT = this.verticesCount

end

function createChunk(this::Chunk)
  println("Create chunk & create blocks...")

  ChunkManager.reset(this; size=CHUNK_SIZE)

  #println("Set Chunk scenery")

  if SCENE == 0 createSingle(this)
  elseif SCENE == 1 createExample(this)
  else createLandscape(this)
  end

  update(this; unseen=HIDE_UNSEEN_CUBES)

  updateChunk(this)
  #= global =# uploaded = :CREATE
end

""" TODO """
function chooseRenderMethod(method=RENDER_METHOD)
  global RENDER_METHOD
  if method != RENDER_METHOD RENDER_METHOD = method end

  name =
  method == 1 ? "INSTANCES of POINTS + GEOMETRY SHADER" :
  method == 2 ? "INSTANCES of TRIANGLES" :
  method == 3 ? "INSTANCES of TRIANGLES + INDICIES" :
  method == 4 ? "TRIANGLES + INDICIES" :
  method == 5 ? "TRIANGLES" :
  method == 6 ? "TRIANGLES" :
  method == 7 ? "COMPUTED" :
  method == 8 ? "COMPUTED + INSTANCES" :
                "NOT DEFINED"

  println("CHUNK SIZE: ",stringColor(string(CHUNK_SIZE,"³");color=:yellow))
  println("SCENE: ",stringColor(SCENE;color=:yellow))
  println("METHOD(",stringColor(method;color=:yellow),"): ",stringColor(method," = ", name; color=:yellow))

  if name == "NOT DEFINED" return end

  #createChunk(MYCHUNK)

  global uploaded = :YES
  global render_ready = false
end

function uploadData()
  if !GPU_CHUNKS_INIT return end

  #=
  m=uploaded

  #chunk_instances = sdata(chunk_instances)
  #plane_vertices = sdata(plane_vertices)
  #box_vertices = sdata(box_vertices)
  =#
  global fileredCount = length(chunk_instances)
  #=
  if m == :UPDATE
    println("Upload data...")
    upload(chunkData, :instances, chunk_instances)
    if FRUSTUM_CULLING
      upload(planeData, :vertices, plane_vertices)
      upload(boxData, :vertices, box_vertices)
    end
    return
  end

  method = RENDER_METHOD

  println("Link data...")
  if method == 1 linkData(chunkData, :points=>chunk_instances)
  elseif method == 2 linkData(chunkData, :vertices=>(DATA_CUBE,3), :instances=>chunk_instances)
  elseif method == 3 linkData(chunkData, :vertices=>(DATA_CUBE_VERTEX,3), :indicies=>(DATA_CUBE_INDEX,1,GL_ELEMENT_ARRAY_BUFFER, true), :instances=>chunk_instances)
  elseif method == 4 linkData(chunkData, :vertices=>(DATA_CUBE_VERTEX,3), :indicies=>(DATA_CUBE_INDEX,1,GL_ELEMENT_ARRAY_BUFFER, true))
  elseif method > 4 && method < 7 linkData(chunkData, :vertices=>(DATA_CUBE,3))
  end
  =#

  if GPU_CHUNKS_INIT
    #GL_DYNAMIC_COPY

    ARRAY_SIZE=6*128^3 #max: 128000000
    #DISPATH_SIZE = CHUNK2D_SIZE

    global DISPATCH_RESETER = createBuffers(GLuint[1,1,1],1; typ=GL_DISPATCH_INDIRECT_BUFFER, usage=GL_STREAM_READ)[1]

    #linkData(chunkData, :vertices=>(DATA_PLANE_VERTEX,3), :indicies=>(DATA_PLANE_INDEX,1,GL_ELEMENT_ARRAY_BUFFER, true), :instances=>chunk_instances)

    setFrustumMode()
    plane_vertices = getVertices(FRUSTUM)
    box_vertices = getBox(FRUSTUM)

    linkData(planeData,  :vertices=>(plane_vertices,3))
    linkData(boxData,  :vertices=>(box_vertices,3))

    if RENDER_METHOD >= 7
      linkData(screenData, :vertices=>(DATA_PLANE2D_VERTEX_STRIP,2)) #:indicies=>(DATA_PLANE_INDEX,1,GL_ELEMENT_ARRAY_BUFFER, true))
    end

    if RENDER_METHOD == 8

      #linkData(chunkData,
      #:points=>zeros(Float32,ARRAY_SIZE),
      #:chunks_default=>chunk_instances,
      #:chunks_changed=>zeros(Float32,ARRAY_SIZE),
      #:points_default=>zeros(Float32,ARRAY_SIZE),
      #:indirect_dispatch=>(GLuint[1,1,1],1,GL_DISPATCH_INDIRECT_BUFFER, GL_STREAM_READ)
      #:counter=>(GLuint[0],1,GL_ATOMIC_COUNTER_BUFFER, GL_STREAM_READ),
      #:dispatchCounter=>(GLuint[0],1,GL_ATOMIC_COUNTER_BUFFER, GL_STREAM_READ),
      #:dispatch_reset=>(GLuint[1,1,1],1,GL_DISPATCH_INDIRECT_BUFFER, GL_STREAM_READ),
      #:indirect=>(GLuint[0,1,0,0,0],1,GL_DRAW_INDIRECT_BUFFER)
      #)

      linkData(indirectData)

      global CHUNK_COUNTERS = createBuffers(GLuint[0],2; typ=GL_ATOMIC_COUNTER_BUFFER, usage=GL_STREAM_READ)
      global DISPATCH_COUNTERS = createBuffers(GLuint[0],CHUNK_COUNT; typ=GL_ATOMIC_COUNTER_BUFFER, usage=GL_STREAM_READ)

      global CHUNK_DATA = createBuffers(zeros(Float32,6),1)[1]
      global CHUNK_OBJECTS = createArrayObjects(CHUNK_COUNT)

      default_data = zeros(Float32,ARRAY_SIZE);

      if single_storage
        MAX_SHADER_STORAGE_BLOCK_SIZE = glGetIntegerval(GL_MAX_SHADER_STORAGE_BLOCK_SIZE)
        MAX_GEOMETRY_OUTPUT_VERTICES = glGetIntegerval(GL_MAX_GEOMETRY_OUTPUT_VERTICES)
        MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = glGetIntegerval(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS )
        MAX_GEOMETRY_SHADER_INVOCATIONS = glGetIntegerval(GL_MAX_GEOMETRY_SHADER_INVOCATIONS)

        info("MAX_SHADER_STORAGE_BLOCK_SIZE: $MAX_SHADER_STORAGE_BLOCK_SIZE")
        info("MAX_GEOMETRY_OUTPUT_VERTICES: $MAX_GEOMETRY_OUTPUT_VERTICES")
        info("MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS: $MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS")
        info("MAX_GEOMETRY_SHADER_INVOCATIONS: $MAX_GEOMETRY_SHADER_INVOCATIONS")

        max_chunks=25 #40 #sizeof(Float32)*6*128^3*42 = 2113929216 bytes ~ 2.11 GB
        global CHUNK_ALL_BUFFERS = [createBuffer(default_data,max_chunks)]
        global CHUNK_BUFFERS = [createBuffer(default_data,max_chunks)]
        #global CHUNK_OCCLUDED_BUFFERS = [createBuffer(default_data,max_chunks)]
      else
        global CHUNK_ALL_BUFFERS = createBuffers(default_data,CHUNK_COUNT)
        global CHUNK_BUFFERS = createBuffers(default_data,CHUNK_COUNT)
      end

      #global TRANSFORM_FEEDBACK_BUFFERS = [createBuffer(default_data,1)]
      global DISPATCH_BUFFERS = createBuffers(GLuint[1,1,1],CHUNK_COUNT; typ=GL_DISPATCH_INDIRECT_BUFFER, usage=GL_STREAM_READ)

      if single_indirect
        global CHUNK_INDIRECT_DRAW_BUFFERS = [createBuffer(GLuint[CHUNK3D_SIZE,1,0,0,0],CHUNK_COUNT; typ=GL_DRAW_INDIRECT_BUFFER)]
      else
        global CHUNK_INDIRECT_DRAW_BUFFERS = createBuffers(GLuint[CHUNK3D_SIZE,1,0,0,0],CHUNK_COUNT; typ=GL_DRAW_INDIRECT_BUFFER)
      end

      global depthrenderbuffer = GPU.create(:RENDERBUFFER)
      global texture_depth = createTexture((WIDTH,HEIGHT);level=LOD_LEVELS) #DEPTH_SIZE

      glActiveTexture(GL_TEXTURE0)
      glBindTexture(GL_TEXTURE_2D, texture_depth)

      # Sampler object that is used during occlusion culling.
      # We want GL_LINEAR shadow mode (PCF), but no filtering between miplevels as we manually specify the miplevel in the compute shader.
      global shadow_sampler = GPU.create(:SAMPLER)
      glSamplerParameteri(shadow_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST)
      glSamplerParameteri(shadow_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
      glSamplerParameteri(shadow_sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
      glSamplerParameteri(shadow_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
      glSamplerParameteri(shadow_sampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)
      glSamplerParameteri(shadow_sampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL)

      # msaa

      global fbo_msaa = GPU.create(:FRAMEBUFFER)
      glBindFramebuffer(GL_FRAMEBUFFER, fbo_msaa)

      global texture_msaa = GPU.create(:TEXTURE)
      glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture_msaa);
      glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, WIDTH, HEIGHT, GL_TRUE);
      glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, texture_msaa, 0)

      global rbo_msaa = GPU.create(:RENDERBUFFER)
      glBindRenderbuffer(GL_RENDERBUFFER, rbo_msaa);
      glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, WIDTH, HEIGHT);
      glBindRenderbuffer(GL_RENDERBUFFER, 0);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo_msaa)

      status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
      if status != GL_FRAMEBUFFER_COMPLETE error("Framebuffer is incomplete!") end
      glBindFramebuffer(GL_FRAMEBUFFER, 0)

      # intermediate

      global fbo_intermediate = GPU.create(:FRAMEBUFFER)
      glBindFramebuffer(GL_FRAMEBUFFER, fbo_intermediate)
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_screen, 0)

      status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
      if status != GL_FRAMEBUFFER_COMPLETE error("Framebuffer is incomplete!") end
      glBindFramebuffer(GL_FRAMEBUFFER, 0)

      # DEPTH, LOD

      global frameBuffers=GPU.create(:FRAMEBUFFER, LOD_LEVELS)

      for lod=1:LOD_LEVELS
        #frameBuffers[lod] = glGenFramebuffer()
        glBindFramebuffer(GL_FRAMEBUFFER, frameBuffers[lod])
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture_depth, lod-1)
        #glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_depth, 0)

        ##glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer)
        ##glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, WIDTH, HEIGHT)
        ##glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer)

        #glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_depth, 0) #GL_COLOR_ATTACHMENT0
        ##glDrawElementsIndirect(GL_POINTS, GL_UNSIGNED_INT, C_NULL)
        ##glDrawBuffers(1, GLenum[GL_COLOR_ATTACHMENT0])
        ##if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return false;

        status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
        if status != GL_FRAMEBUFFER_COMPLETE error("Framebuffer is incomplete!") end
      end
      glBindFramebuffer(GL_FRAMEBUFFER, 0)
    end

    glBindTexture(GL_TEXTURE_2D, 0)
  end

  reloadShaderPrograms()

  if !render_init global render_init = true end
  if !render_ready global render_ready = true end
  global uploaded=:NOTHING
end

function reloadShaderProgram(program::Symbol, shaders::AbstractArray; transformfeedback=false)
  result=false
  try
    p = createShaderProgram(program, shaders; transformfeedback=transformfeedback)
    if p >= 0
      @GLCHECK glUseProgram(0)
      @GLCHECK glDeleteProgram(PROGRAMS[program])
      PROGRAMS[program]=p
      result=true
    end
  catch e
  end
  result
end

function bindBuffers()
  #if GPU_CHUNKS
    #use_program(PROGRAMS[:COMPUTE_CHUNKS]s, () -> begin
      #glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , indirectData.arrays[:indirect_dispatch].refID)
      #glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, indirectData.arrays[:counter].refID)
      #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, indirectData.arrays[:chunks_default].refID)
      #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, indirectData.arrays[:points_default].refID)
    #end)
  #end

  #use_program(PROGRAMS[:INSTANCES], () -> begin
    #glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , indirectData.arrays[:indirect_dispatch_instances].refID)
    #glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, indirectData.arrays[:indirect].refID)
    #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, indirectData.arrays[:points_default].refID)
    #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, indirectData.arrays[:points].refID)
  #end)
end

function reloadShaderPrograms()
  method = RENDER_METHOD
  global_vars[:CHUNK_SIZE] = CHUNK_SIZE
  global_vars[:CHUNK1D_SIZE] = CHUNK1D_SIZE
  global_vars[:CHUNK2D_SIZE] = CHUNK2D_SIZE
  global_vars[:CHUNK3D_SIZE] = CHUNK3D_SIZE

  println("Load shader files...")
  shaders = loadShaders(global_vars)

  INST_VSH = shaders[:INST_VSH]
  INST_VSH_GSH = shaders[:INST_VSH_GSH]
  INST_GSH = shaders[:INST_GSH]
  INST_FSH = shaders[:INST_FSH]
  VSH_TEXTURE = shaders[:VSH_TEXTURE]
  VSH = shaders[:VSH]
  FSH = shaders[:FSH]
  GSH = shaders[:GSH]
  CSH = shaders[:CSH]
  SCREEN_VSH = shaders[:SCREEN_VSH]
  SCREEN_FSH = shaders[:SCREEN_FSH]
  INST_CSH = shaders[:INST_CSH]
  CHUNKS_CSH = shaders[:CHUNKS_CSH]
  CHANGE_CHUNKS_CSH = shaders[:CHANGE_CHUNKS_CSH]
  INST_VSH_CSH_GSH = shaders[:INST_VSH_CSH_GSH]
  BG_FSH = shaders[:BG_FSH]
  FG_FSH = shaders[:FG_FSH]
  DEPTH_VSH = shaders[:DEPTH_VSH]
  DEPTH_FSH = shaders[:DEPTH_FSH]
  DEPTH_GSH = shaders[:DEPTH_GSH]
  DEPTH_MIP_FSH = shaders[:DEPTH_MIP_FSH]
  RASTER_VSH = shaders[:RASTER_VSH]
  RASTER_FSH = shaders[:RASTER_FSH]
  RASTER_GSH = shaders[:RASTER_GSH]

  println("Create & Compile shader programs...")
  program_data = [VSH, FSH]

  if method < 7
    if method == 1 program_data = [INST_VSH_GSH, INST_FSH, INST_GSH]
    elseif method > 1 && method <= 3 program_data = [INST_VSH, INST_FSH]
    elseif method == 4 program_data = [VSH_TEXTURE, INST_FSH]
    elseif method == 5 program_data = [VSH_TEXTURE, INST_FSH]
    elseif method == 6 program_data = [VSH, FSH]
    end
  end

  reloadShaderProgram(:CHUNKS, program_data)
  reloadShaderProgram(:NORMAL, [VSH, FSH])
  reloadShaderProgram(:SCREEN, [SCREEN_VSH, SCREEN_FSH])
  reloadShaderProgram(:COMPUTE, [CSH])
  #reloadShaderProgram(:COMPUTE_CHUNKS, [CHUNKS_CSH])
  reloadShaderProgram(:CHANGE_CHUNKS, [CHANGE_CHUNKS_CSH])
  #reloadShaderProgram(:INSTANCES, [INST_CSH])
  reloadShaderProgram(:INDIRECT, [INST_VSH_CSH_GSH, INST_FSH, INST_GSH])
  reloadShaderProgram(:BG, [VSH, BG_FSH])
  reloadShaderProgram(:FG, [VSH, FG_FSH])
  reloadShaderProgram(:DEPTH, [DEPTH_VSH, DEPTH_FSH, DEPTH_GSH])
  reloadShaderProgram(:DEPTH_MIP, [SCREEN_VSH, DEPTH_MIP_FSH])
  reloadShaderProgram(:RASTER, [RASTER_VSH, RASTER_FSH, RASTER_GSH];transformfeedback=false)

  println("bind Buffers...")
  bindBuffers()

  println("set Attributes ...")
  glBindVertexArray(CHUNK_OBJECTS[1])
  setAttributes(CHUNK_DATA, PROGRAMS[:INDIRECT], [("iInstancePos",Float32,3,0),("iInstanceFlags",Float32,3,0)])
  glBindVertexArray(0)
  #setAttributes(CHUNK_OBJECTS[1:1], CHUNK_BUFFERS[1:1], PROGRAMS[:INDIRECT])
  #setAttributes(indirectData, PROGRAMS[:INDIRECT])
  setAttributes(planeData, PROGRAMS[:NORMAL])
  setAttributes(boxData, PROGRAMS[:NORMAL])
  setAttributes(screenData, PROGRAMS[:SCREEN])
  setAttributes(chunkData, PROGRAMS[:CHUNKS])
  #setAttributes(screenData, PROGRAMS[:BG])
  #setAttributes(screenData, PROGRAMS[:FG])
  #setAttributes(screenData, PROGRAMS[:DEPTH])

  println("set Uniforms...")

  #= global =# location_position = glGetUniformLocation(PROGRAMS[:CHUNKS], "iPosition")
  #= global =# location_texindex = glGetUniformLocation(PROGRAMS[:CHUNKS], "iTexIndex")

  #setMVP(PROGRAMS[:INDIRECT], MVP)
  #setMVP(PROGRAMS[:CHUNKS], MVP)
  #setMode(PROGRAMS[:INDIRECT], "iMVP", MVP)
  #setMode(PROGRAMS[:CHUNKS], "iMVP", MVP)

  setMode(PROGRAMS[:CHUNKS], "iUseLight", LIGHTMODE)
  setMode(PROGRAMS[:CHUNKS], "iUseTexture", TEXTUREMODE)
end

""" TODO """
function checkForUpdate()
  uploadData()

  #= global =# keyValue, #= global =# keyPressed = getKey()

  if keyPressed

    if keyValue == 80 #p
      setPosition(CAMERA,[0f0,0,0])

    elseif keyValue == 81 #q
      global WIREFRAME=!WIREFRAME
      info("WIREFRAME: $WIREFRAME")

    elseif keyValue == 84 && render_ready #t
      global TEXTUREMODE=!TEXTUREMODE
      info("TEXTUREMODE: $TEXTUREMODE")
      setMode(PROGRAMS[:CHUNKS], "iUseTexture", TEXTUREMODE, "TEXTURE")

    elseif keyValue == 85 #u
      global UPDATE_FRUSTUM = !UPDATE_FRUSTUM
      info("UPDATE_FRUSTUM: $UPDATE_FRUSTUM")

    elseif keyValue == 76 && render_ready #l
      global LIGHTMODE=!LIGHTMODE
      info("LIGHTMODE: $LIGHTMODE")
      setMode(PROGRAMS[:CHUNKS], "iUseLight", LIGHTMODE, "LIGHT")

    elseif keyValue == 82 #r
      #chooseRenderMethod()
      #reloadShaderPrograms()
      #global GPU_CHUNKS_INIT = true
      println("YES")

    elseif (keyValue >= 290 && keyValue <= 301) # F1 - F12
      chooseRenderMethod(keyValue - 289)

    elseif keyValue >= 49 && keyValue <= 57 #1-9
      global CHUNK_SIZE = 2^(keyValue-49)
      info("CHUNK_SIZE: $CHUNK_SIZE")
      reloadShaderPrograms()
      global GPU_CHUNKS_INIT = true

    elseif keyValue == 48 #0
      global CHUNK_SIZE = 2^9
      info("CHUNK_SIZE: $CHUNK_SIZE")
      reloadShaderPrograms()
      global GPU_CHUNKS_INIT = true

    elseif keyValue == 45 #ß
      global CHUNK_SIZE = 2^10
      info("CHUNK_SIZE: $CHUNK_SIZE")
      reloadShaderPrograms()
      global GPU_CHUNKS_INIT = true

    elseif keyValue == 66 #b
      global SCENE = 0
      chooseRenderMethod()
    elseif keyValue == 78 #n
      global SCENE = 1
      chooseRenderMethod()
    elseif keyValue == 77 #m
      #global SCENE = 2
      #chooseRenderMethod()
      MSAA = !MSAA

    elseif keyValue == 70 #f
      global CAM_LOCK = !CAM_LOCK
      info("CAM LOCK: $CAM_LOCK")
    elseif keyValue == 86 #v
      global FRUSTUM_CULLING = !FRUSTUM_CULLING
    elseif keyValue == 79 #o
      global HIDE_UNSEEN_CUBES = !HIDE_UNSEEN_CUBES

    end
  end

  #if keyPressed resetKeys() end
end

function setFrustumProgram(program)
  use_program(program, () -> begin
    #center = FRUSTUM.pos[:CENTER]
    #center = [center.x,center.y,center.z,FRUSTUM.radius]
    dirs=[x.mNormal for (_,x) in FRUSTUM.planes] #getDirections(FRUSTUM)
    dists=[x.d for (_,x) in FRUSTUM.planes]  #getDistances(FRUSTUM)
    @GLCHECK glUniform1i(glGetUniformLocation(program, "frustum"), true)
    if !CAM_LOCK
      #@GLCHECK glUniform4fv(glGetUniformLocation(program, "frustum_center"), 1, center)
      @GLCHECK glUniform3fv(glGetUniformLocation(program, "frustum_dirs"), length(dirs), dirs)
      @GLCHECK glUniform1fv(glGetUniformLocation(program, "frustum_dists"), length(dists), dists)
    end
  end)
end

function checkCamera()
  if GPU_CHUNKS_INIT || CameraManager.OnUpdate(CAMERA)
    #println(CAMERA.position)
    #if !CAM_LOCK
      if RENDER_METHOD < 7
        #setMVP(PROGRAMS[:CHUNKS], CAMERA.MVP)
        #setMVP(PROGRAMS[:NORMAL], CAMERA.MVP)
        #setFrustumProgram(PROGRAMS[:CHUNKS])
      end
    #end
    setFrustumMode()
    global CAMERA_UPDATED=UPDATE_FRUSTUM
  end
end

function gpu_updateChunks()
  global RENDER_CHUNKS_COUNT, VISIBLE_CHUNKS_COUNT

  if GPU_CHUNKS_INIT || CAMERA_UPDATED
    #SetCamera(FRUSTUM, Vec3f(CAMERA.position), Vec3f(CAMERA.position+CameraManager.forward(CAMERA)), Vec3f(0,1,0))

    useProgram(PROGRAMS[:CHANGE_CHUNKS])

    setMode("iTime", ITIME)
    setMode("iCamPos", CAMERA.position)
    setMode("iCamAng", CAMERA.rotation)
    setMode("iProj", CAMERA.projectionMat)
    setMode("iView", CAMERA.viewMat)
    setMode("iMVP", CAMERA.MVP)

    setFrustumProgram(PROGRAMS[:CHANGE_CHUNKS])

    CHUNK_COUNTER=CHUNK_COUNTERS[1]
    #DISPATCH_BUFFER=DISPATCH_BUFFERS[1]
    CHUNK_INDIRECT_DRAW_BUFFER = CHUNK_INDIRECT_DRAW_BUFFERS[1]

    #glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, DISPATCH_BUFFER.refID) #DISPATH
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 3, CHUNK_COUNTER.refID) #counter

    #buf = glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT)
    VISIBLE_CHUNKS_COUNT = 0
    RENDER_CHUNKS_COUNT = 0

    setMode("iCulling", 0)

    for chunk in CHUNKS
      #visible=inFrustum(chunk,FRUSTUM)
      #if !GPU_CHUNKS_INIT && !visible continue end

      #if visible
        #if VISIBLE_CHUNKS_COUNT > 0 break end
        VISIBLE_CHUNKS_COUNT += 1
        VISIBLE_CHUNKS[VISIBLE_CHUNKS_COUNT] = chunk
      #end

      RENDER_CHUNKS_COUNT += 1
      I_RENDER_CHUNKS_COUNT = (RENDER_CHUNKS_COUNT-1)

      setMode("iCenter", chunk.pos)

      DISPATCH_BUFFER=DISPATCH_BUFFERS[chunk.id]
      DISPATCH_COUNTER=DISPATCH_COUNTERS[chunk.id]

      glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, DISPATCH_COUNTER.refID) #dispatchCount (used after GPU_CHUNKS_INIT)

      if single_indirect
        glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 2, CHUNK_INDIRECT_DRAW_BUFFERS[1].refID, I_RENDER_CHUNKS_COUNT*INDIRECT_DRAW_BUFFER_SIZE, sizeof(GLuint))
      else
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, CHUNK_INDIRECT_DRAW_BUFFERS[chunk.id].refID) #instanceCount
      end

      if single_storage
        #glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[1].refID, I_RENDER_CHUNKS_COUNT*CHUNK_BUFFERS_SIZE, CHUNK_BUFFERS_SIZE)
        #glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[1].refID, I_RENDER_CHUNKS_COUNT*CHUNK_BUFFERS_SIZE, CHUNK_BUFFERS_SIZE)
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[1].refID)
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[1].refID)
      else
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[chunk.id].refID)
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[chunk.id].refID)
      end

      if GPU_CHUNKS_INIT
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, DISPATCH_BUFFER.refID) #DISPATH
        glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_RESETER.refID)
        setMode("STATE", -1) #RESET / SET DISPATCH VALUE
        glDispatchComputeIndirect(C_NULL)
        glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, 0)
      end

      glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_BUFFER.refID)
      setMode("STATE", GPU_CHUNKS_INIT ? 0 : 1)
      glDispatchComputeIndirect(C_NULL)
      glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
    end

    #glDrawBuffer(GL_NONE)
    #glReadBuffer(GL_NONE)
    #glReadBuffer(GL_BACK)
    #glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, WIDTH, HEIGHT, 0)

    glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , 0)
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0)
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 0, 0, 0)

    ######################################################################
    use_depth = true
    if use_depth
    # depth buffer
    useProgram(PROGRAMS[:DEPTH])

    # Render occlusion geometry to miplevel 0
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffers[1])
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, texture_depth)

    #glClearDepth(1.0)
    glDepthMask(GL_TRUE) #default: GL_TRUE
    glDepthFunc(GL_LESS) #default: GL_LESS
    glDepthRange(0.0, 1.0)

    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
    glViewport(0, 0, WIDTH, HEIGHT) #DEPTH_SIZE
    setMode("iDepth", false)
    drawChunk(Float32[0,0,0])
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read

    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, texture_depth)
    setMode("iDepth", true)
    drawChunk(Float32[0,0,0])
    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read

    ############################################################################
#=
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, texture_depth)

    # Render occlusion geometry to miplevel > 0
    useProgram(PROGRAMS[:DEPTH_MIP])
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, texture_depth)
    glDepthFunc(GL_ALWAYS)

    glBindVertexArray(screenData.vao)

    currentWidth = WIDTH
    currentHeight = HEIGHT

    for lod=2:LOD_LEVELS
      currentWidth = round(Integer, currentWidth / 2.0)
      currentHeight = round(Integer, currentHeight / 2.0)
      if currentWidth <= 0 currentWidth = 1 end
      if currentHeight <= 0 currentHeight = 1 end

      glBindFramebuffer(GL_FRAMEBUFFER, frameBuffers[lod])
      glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
      glViewport(0,0, currentWidth, currentHeight) #DEPTH_SIZE >> lod

      # Need to do this to ensure that we cannot possibly read from the miplevel we are rendering to.
      # Otherwise, we have undefined behavior.
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, lod - 2)
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, lod - 2)

      glDrawArrays(GL_TRIANGLE_STRIP, 0, screenData.draw.count)
    end

    glBindVertexArray(0)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    glDepthFunc(GL_LEQUAL)

    # Restore miplevels. MAX_LEVEL will be clamped accordingly.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, LOD_LEVELS-1)

    glViewport(0, 0, WIDTH, HEIGHT)
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read

  =#
    ######################################################################

     # Bind Hi-Z depth map
    glActiveTexture(GL_TEXTURE0)
    glBindTexture(GL_TEXTURE_2D, texture_depth)
    #glBindSampler(0, shadow_sampler)

    ############################################################################
    end

    # rasterize_occluders
    rasterize=true

    if rasterize
      #glEnable( GL_POLYGON_OFFSET_FILL )
      #glPolygonOffset(-1,-1)
      #glEnable(GL_RASTERIZER_DISCARD)

      glBindFramebuffer(GL_FRAMEBUFFER, frameBuffers[1])
      #glDepthMask(GL_FALSE)
      glDepthFunc(GL_LESS)
      #glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE)

      useProgram(PROGRAMS[:RASTER])
      setMode("iTime", ITIME)
      setMode("iResolution", Float32[WIDTH,HEIGHT])
      setMode("iCamPos", CAMERA.position)
      setMode("iCamAng", CAMERA.rotation)
      setMode("iProj", CAMERA.projectionMat)
      setMode("iView", CAMERA.viewMat)
      setMode("iMVP", CAMERA.MVP)

      glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_BUFFERS[1].refID)
      glBindBuffer(GL_DRAW_INDIRECT_BUFFER , CHUNK_INDIRECT_DRAW_BUFFERS[1].refID)
      glMultiDrawArraysIndirect(GL_POINTS,C_NULL,VISIBLE_CHUNKS_COUNT,INDIRECT_DRAW_BUFFER_SIZE)

      #glDepthMask(GL_TRUE)
      #glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE)

      #glPolygonOffset(0,0)
      #glDisable(GL_POLYGON_OFFSET_FILL)

      glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0)
      glBindBuffer(GL_DRAW_INDIRECT_BUFFER , 0)
      glBindFramebuffer(GL_FRAMEBUFFER, 0)
      glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)
      glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)
      #glDisable(GL_RASTERIZER_DISCARD)
    end

    ############################################################################

    useProgram(PROGRAMS[:CHANGE_CHUNKS])

    setMode("iRasterrize", rasterize)

    DISPATCH_BUFFER=DISPATCH_BUFFERS[1]

    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, DISPATCH_BUFFER.refID)
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, DISPATCH_COUNTERS[1].refID)

    #=
    glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_RESETER.refID)
    setMode("STATE", -3) #RESET / SET DISPATCH VALUE
    glDispatchComputeIndirect(C_NULL)
    glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
    glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_BUFFER.refID)
    =#

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[1].refID)
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[1].refID)

    for i=1:VISIBLE_CHUNKS_COUNT
      chunk=VISIBLE_CHUNKS[i]

      setMode("iCenter", chunk.pos)

      #DISPATCH_BUFFER=DISPATCH_BUFFERS[chunk.id]
      #glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, DISPATCH_BUFFER.refID) #DISPATH
      #glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, DISPATCH_COUNTERS[chunk.id].refID) #dispatchCount (used after GPU_CHUNKS_INIT)

      if single_indirect
        glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 2, CHUNK_INDIRECT_DRAW_BUFFERS[1].refID, (i-1)*INDIRECT_DRAW_BUFFER_SIZE, sizeof(GLuint))
      else
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, CHUNK_INDIRECT_DRAW_BUFFERS[chunk.id].refID) #instanceCount
      end

      if single_storage
        #glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[1].refID, (i-1)*CHUNK_BUFFERS_SIZE, CHUNK_BUFFERS_SIZE)
        #glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[1].refID, (i-1)*CHUNK_BUFFERS_SIZE, CHUNK_BUFFERS_SIZE)

        #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[1].refID)
        #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[1].refID)
      else
        #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFERS[chunk.id].refID)
        #glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFERS[chunk.id].refID)
      end

      glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_RESETER.refID)
      setMode("STATE", -2) # SET DISPATCH VALUE
      glDispatchComputeIndirect(C_NULL)
      glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
      glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_BUFFER.refID)

      setMode("STATE", 2)
      glDispatchComputeIndirect(C_NULL)
      glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
    end

    #=
    useProgram(PROGRAMS[:CHANGE_CHUNKS])
    setMode("iCulling", 1) # Dispatch occlusion culling job

    setMode("STATE", -3) # SET DISPATCH VALUE
    glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_RESETER.refID)
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, CHUNK_INDIRECT_DRAW_BUFFER.refID)
    glDispatchComputeIndirect(C_NULL)
    glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)

    CHUNK_BUFFER=CHUNK_BUFFERS[1]
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFER.refID)
    =#

    #=
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, DISPATCH_BUFFER.refID) #DISPATH
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 3, CHUNK_COUNTER.refID) #counter

    for chunk=1:CHUNK_COUNT
      setMode("iCenter", CHUNKS[chunk].pos)

      CHUNK_ALL_BUFFER=CHUNK_ALL_BUFFERS[chunk]
      CHUNK_BUFFER=CHUNK_BUFFERS[chunk]
      CHUNK_INDIRECT_DRAW_BUFFER = CHUNK_INDIRECT_DRAW_BUFFERS[chunk]
      DISPATCH_COUNTER=DISPATCH_COUNTERS[chunk]

      #glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, DISPATCH_BUFFER.refID) #DISPATH
      glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, DISPATCH_COUNTER.refID) #dispatchCount (used after GPU_CHUNKS_INIT)
      glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 2, CHUNK_INDIRECT_DRAW_BUFFER.refID) #instanceCount
      #glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 2 , CHUNK_INDIRECT_DRAW_BUFFER.refID, 0, INDIRECT_DRAW_BUFFER_SIZE)

      glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_ALL_BUFFER.refID)
      glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, CHUNK_BUFFER.refID)

      #setMode("STATE", -2) # SET DISPATCH VALUE
      #glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_RESETER.refID)
      #glDispatchComputeIndirect(C_NULL)
      #glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
      #glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , DISPATCH_BUFFER.refID)

      setMode("STATE", 2)
      glDispatchComputeIndirect(C_NULL)
      glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
    end
    =#

    glBindSampler(0, 0)
    #setFrustumProgram(PROGRAMS[:INSTANCES])
    #setMVP(PROGRAMS[:INSTANCES], CAMERA.MVP)
    #setMode(PROGRAMS[:INSTANCES], "iCamPos", CAMERA.position)
    #setMode(PROGRAMS[:INSTANCES], "iCamAng", CAMERA.rotation)
    glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , 0)
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0)
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 0, 0, 0)
  end
  #=
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, indirectData.arrays[:points_default].refID)
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, indirectData.arrays[:points].refID)
  setMode("STATE", 2)
  glDispatchComputeIndirect(C_NULL)
  glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
  =#
  #=
  useProgram(PROGRAMS[:INSTANCES])
  setMode("iTime", ITIME)

  glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER , indirectData.arrays[:indirect_dispatch_instances].refID)
  glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, indirectData.arrays[:counter2].refID) #LIMIT
  glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 1, indirectData.arrays[:indirect].refID) #instanceCount
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, indirectData.arrays[:points_default].refID)
  glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, indirectData.arrays[:points].refID)

  glDispatchComputeIndirect(C_NULL)
  glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT) # GL_ATOMIC_COUNTER_BARRIER_BIT GL_SHADER_IMAGE_ACCESS_BARRIER_BIT
  #GL_BUFFER_UPDATE_BARRIER_BIT
  =#
end

function drawChunk(center;single=false)
  setMode("iTime", ITIME)
  setMode("iResolution", Float32[WIDTH,HEIGHT])
  setMode("iCamPos", CAMERA.position)
  setMode("iCamAng", CAMERA.rotation)
  setMode("iCenter", center)
  setMode("iProj", CAMERA.projectionMat)
  setMode("iView", CAMERA.viewMat)
  setMode("iMVP", CAMERA.MVP)
  setMode("iDepth", 0)

  #glBindVertexArray(indirectData.vao)

  glBindVertexArray(CHUNK_OBJECTS[1])
  iprogram=PROGRAMS[:INDIRECT]

  #for i=1:(single ? 1 : VISIBLE_CHUNKS_COUNT)
  #  chunk=VISIBLE_CHUNKS[i]
  #  buffer=CHUNK_BUFFERS[chunk.id]
  #  ibuffer=CHUNK_INDIRECT_DRAW_BUFFERS[chunk.id]
  #  glBindBufferRange(GL_SHADER_STORAGE_BUFFER, i, buffer.refID, i*buffsize, buffsize)
  #  glBindBufferRange(GL_DRAW_INDIRECT_BUFFER, i, ibuffer.refID, i*INDIRECT_DRAW_BUFFER_SIZE, INDIRECT_DRAW_BUFFER_SIZE)
  #  setAttributes(buffer, iprogram, atrb; bindbuffer=false)
  #end

  #=
  for i=1:(single ? 1 : VISIBLE_CHUNKS_COUNT)
    chunk = VISIBLE_CHUNKS[i]
    buffer=CHUNK_BUFFERS[chunk.id]
    ibuffer=CHUNK_INDIRECT_DRAW_BUFFERS[1]
    #glBindBuffer(GL_ARRAY_BUFFER, CHUNK_DATA.refID) #buffer.refID #for vao
    #setAttributes(buffer, iprogram, atrb; bindbuffer=false)
    #glBindBuffer(GL_ARRAY_BUFFER, 0)
    #glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.refID) #for vao
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer.refID)
    glBindBuffer(GL_DRAW_INDIRECT_BUFFER , ibuffer.refID)
    glDrawArraysIndirect(GL_POINTS, C_NULL)
    #glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT)
  end
  =#
  #CHUNK_BUFFER=CHUNK_OCCLUDED_BUFFERS[1]
  CHUNK_BUFFER=CHUNK_BUFFERS[1]
  if single_indirect
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_BUFFER.refID)
    glBindBuffer(GL_DRAW_INDIRECT_BUFFER , CHUNK_INDIRECT_DRAW_BUFFERS[1].refID)
    glMultiDrawArraysIndirect(GL_POINTS,C_NULL,VISIBLE_CHUNKS_COUNT,INDIRECT_DRAW_BUFFER_SIZE)
  elseif single_storage
    for i=1:(single ? 1 : VISIBLE_CHUNKS_COUNT)
      chunk = VISIBLE_CHUNKS[i]
      glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_BUFFER.refID, (i-1)*CHUNK_BUFFERS_SIZE, CHUNK_BUFFERS_SIZE)
      glBindBuffer(GL_DRAW_INDIRECT_BUFFER , CHUNK_INDIRECT_DRAW_BUFFERS[chunk.id].refID)
      glDrawArraysIndirect(GL_POINTS, C_NULL)
    end
  else
    for i=1:(single ? 1 : VISIBLE_CHUNKS_COUNT)
      chunk = VISIBLE_CHUNKS[i]
      glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, CHUNK_BUFFERS[chunk.id].refID)
      glBindBuffer(GL_DRAW_INDIRECT_BUFFER , CHUNK_INDIRECT_DRAW_BUFFERS[chunk.id].refID)
      glDrawArraysIndirect(GL_POINTS, C_NULL)
    end
  end

  glBindVertexArray(0)

end

""" TODO """
function game_start()
  #------------------------------------------------------------------------------------

  #const mmvp = SMatrix{4,4,Float32}(CAMERA.MVP)

  #------------------------------------------------------------------------------------

  glEnable(GL_DEPTH_TEST)
  glEnable(GL_BLEND)
  glEnable(GL_CULL_FACE)
  #glEnable(GL_MULTISAMPLE)

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  #glBlendEquation(GL_FUNC_ADD)
  #glFrontFace(GL_CCW)

  glCullFace(GL_BACK) #default: GL_BACK
  glDepthMask(GL_TRUE) #default: GL_TRUE
  glDepthFunc(GL_LEQUAL) #default: GL_LESS

  #glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)#GL_FILL,GL_LINE
  glClearColor(0.0, 0.0, 0.0, 0.0)

  # Loop until the user closes the window
  #render = function(x)
    #mvp = mmvp*MMatrix{4,4,Float32}(unsafe_wrap(Array, mvp, (4,4)))
    #setMVP(CAMERA.MVP*translation([c.x,c.y,c.z]))

    #glUniformMatrix4fv(location_mvp, 1, false, x.mvp)
    #glDrawElements(GL_TRIANGLES, chunkData.draw.count, GL_UNSIGNED_INT, C_NULL )
    #glDrawArrays(GL_TRIANGLES, 0, chunkData.draw.count)
  #  nothing
  #end

  #=
  if use_geometry_shader
    loopBlocks() = render(mychunk.childs[1])
  else
    if compileAndLink
      objptr = createLoop(1,refblocks,render) #compileAndLink
      loopBlocks() = loopByObject(objptr) #compileAndLink
    else
      loopBlocks() = for b in mychunk.childs; render(b); end
    end
  end
  =#

  global SLEEP=0 #1f0/200
  global RENDER_CHUNKS_COUNT=0
  global ITIME = 0
  global VISIBLE_CHUNKS=Array{Chunk,1}(undef, length(CHUNKS))
  global VISIBLE_CHUNKS_COUNT = 0
end

""" TODO """
function game_loop()
  #if !WINDOW_FOCUS
    sleep(0.1)
  #else
    checkForUpdate()

   # Pulse the background
    #c=0.5 * (1 + sin(i * 0.01)); i+=1
    #glClearColor(c, c, c, 1.0)
    #glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    #print("loopBlocks "); @time
    #loopBlocks()

    if render_ready
      checkCamera()

      if RENDER_METHOD < 7
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)

        glPolygonMode(GL_FRONT_AND_BACK, WIREFRAME ? GL_LINE : GL_FILL)

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, texture_blocks)

        if fileredCount > 0 #isValid(MYCHUNK)
          useProgram(PROGRAMS[:CHUNKS])
          #glPolygonMode(GL_FRONT_AND_BACK, WIREFRAME ? GL_LINE : GL_FILL)
          glBindVertexArray(chunkData.vao)

          if RENDER_METHOD == 1 glDrawArrays(GL_POINTS, 0, fileredCount) #GL_TRIANGLE_STRIP
          elseif RENDER_METHOD == 2 glDrawArraysInstanced(GL_TRIANGLES, 0, chunkData.draw.count, fileredCount)
          elseif RENDER_METHOD == 3 glDrawElementsInstanced(GL_TRIANGLES, chunkData.draw.count, GL_UNSIGNED_INT, C_NULL, fileredCount)
          #glDrawElementsInstancedBaseVertex(GL_TRIANGLES, chunkData.draw.count / 6, GL_UNSIGNED_INT, C_NULL, mychunk.count, 0)
          elseif RENDER_METHOD == 4
            #* thats slow! (glDrawElements ~60 fps, glDrawElementsInstanced ~ 200 fps !!!)
            for b in getFilteredChilds(MYCHUNK)
              if location_texindex > -1 glUniform1f(location_texindex, b.typ) end
              if location_position > -1 glUniform3fv(location_position, 1, b.pos) end
              glDrawElements(GL_TRIANGLES, chunkData.draw.count, GL_UNSIGNED_INT, C_NULL)
            end
          elseif RENDER_METHOD == 5
            #glDrawArrays(GL_TRIANGLES, 0, chunkData.draw.count)
          end
          glBindVertexArray(0)
        end
      end

      if RENDER_METHOD == 7
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)

        useProgram(PROGRAMS[:COMPUTE])

        setMode("destTex", 0)
        setMode("roll", FRAMES*0.01f0)

        #@GLCHECK glUniform1i(glGetUniformLocation(PROGRAMS[:COMPUTE], "destTex"), 0)
        #@GLCHECK glUniform1f(glGetUniformLocation(PROGRAMS[:COMPUTE], "roll"), FRAMES*0.01f0)

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, texture_screen)

        glDispatchCompute(512/1, 512/1, 1)

        useProgram(PROGRAMS[:SCREEN])
        setMode("srcTex", 0)
        #@GLCHECK glUniform1i(glGetUniformLocation(PROGRAMS[:SCREEN], "srcTex"), 0)

        glBindVertexArray(screenData.vao)
        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read
        glDrawArrays(GL_TRIANGLE_STRIP, 0, screenData.draw.count)

        glBindVertexArray(0)
      end

      if RENDER_METHOD == 8
        #frameBufferCounter+=1
        #if frameBufferCounter > frameBufferMax frameBufferCounter=1 end

        global ITIME = programTime()

        #glBlendFunc(GL_ONE, GL_ZERO)
        #glDisable( GL_BLEND )

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)

        #glActiveTexture(GL_TEXTURE0)
        #glBindTexture(GL_TEXTURE_2D, texture_screen)

        glActiveTexture(GL_TEXTURE1)
        glBindTexture(GL_TEXTURE_2D, texture_depth)

        glActiveTexture(GL_TEXTURE2)
        glBindTexture(GL_TEXTURE_2D, texture_blocks)

        glActiveTexture(GL_TEXTURE3)
        glBindTexture(GL_TEXTURE_2D, texture_screen)

        glActiveTexture(GL_TEXTURE4)
        glBindTexture(GL_TEXTURE_2D, texture_heightMap)

        # calculate landscape
        if GPU_CHUNKS_INIT || CAMERA_UPDATED
          useProgram(PROGRAMS[:COMPUTE])
          setMode("iTime", ITIME)
          glDispatchCompute(512/1, 512/1, 1)
        end

        ###################################

        glDepthMask(GL_FALSE)

        useProgram(PROGRAMS[:BG])

        setMode("iTime", ITIME)
        setMode("iResolution", Float32[WIDTH,HEIGHT])
        setMode("iCamPos", CAMERA.position)
        setMode("iCamAng", CAMERA.rotation)
        setMode("iProj", CAMERA.projectionMat)
        setMode("iView", CAMERA.viewMat)
        #setMode("iMVP", eyeMat4x4f)

        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read

        glBindVertexArray(screenData.vao)
        glDrawArrays(GL_TRIANGLE_STRIP, 0, screenData.draw.count)

        glBindVertexArray(0)
        glDepthMask(GL_TRUE)
        #################################

        gpu_updateChunks()

        if MSAA
          glBindFramebuffer(GL_FRAMEBUFFER, fbo_msaa)
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
        end
        glPolygonMode(GL_FRONT_AND_BACK, WIREFRAME ? GL_LINE : GL_FILL)
        useProgram(PROGRAMS[:INDIRECT])
        drawChunk(Float32[0,0,0])
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

        if MSAA
          # 2. now blit multisampled buffer(s) to normal colorbuffer of intermediate FBO. Image is stored in screenTexture
          glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_msaa);
          glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_intermediate);
          glBlitFramebuffer(0, 0, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);

          # 3. now render quad with scene's visuals as its texture image
          glBindFramebuffer(GL_FRAMEBUFFER, 0);

          useProgram(PROGRAMS[:SCREEN])
          setMode("srcTex", 0)

          glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read
          glBindVertexArray(screenData.vao)
          glActiveTexture(GL_TEXTURE0)
          glBindTexture(GL_TEXTURE_2D, texture_screen)
          glDrawArrays(GL_TRIANGLE_STRIP, 0, screenData.draw.count)
        end
        #################################
        #=
        useProgram(PROGRAMS[:NORMAL])
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

        if GPU_CHUNKS_INIT #|| CAMERA_UPDATED
          SetCamera(FRUSTUM, Vec3f(CAMERA.position), Vec3f(CAMERA.position+CameraManager.forward(CAMERA)), Vec3f(0,1,0); far=100f0)
          linkData(planeData, :vertices=>(getVertices(FRUSTUM),3))
          linkData(boxData, :vertices=>(DATA_CUBE,3)) #getBox(FRUSTUM)
          setAttributes(planeData, PROGRAMS[:NORMAL])
          setAttributes(boxData, PROGRAMS[:NORMAL])
        end
        setMode("iMVP", CAMERA.MVP)

        setMode("color", Vec4f(1,0,0,1))
        glBindVertexArray(planeData.vao)
        glDrawArrays(GL_TRIANGLES, 0, planeData.draw.count)

        glBindVertexArray(0)
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

        setMode("color", Vec4f(0,0,1,1))
        glBindVertexArray(boxData.vao)

        for i=1:VISIBLE_CHUNKS_COUNT
          setMode("iPosition", VISIBLE_CHUNKS[i].pos)
          glDrawArrays(GL_TRIANGLES, 0, boxData.draw.count)
        end
        =#

        #################################

        useProgram(PROGRAMS[:FG])
        setMode("iTime", ITIME)
        setMode("iResolution", Float32[WIDTH,HEIGHT])
        setMode("iCamPos", CAMERA.position)
        setMode("iCamAng", CAMERA.rotation)
        setMode("iProj", CAMERA.projectionMat)
        setMode("iView", CAMERA.viewMat)

        glActiveTexture(GL_TEXTURE1)
        glBindTexture(GL_TEXTURE_2D, texture_depth)

        glBindVertexArray(screenData.vao)
        glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) # make sure writing to image has finished before read
        glDrawArrays(GL_TRIANGLE_STRIP, 0, screenData.draw.count)

        #glDrawBuffer(GL_BACK)
        #glReadBuffer(GL_FRONT)
      end

      ##############################################

      #ptr = Ptr{GLuint}(glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0,1*sizeof(GLuint),  GL_MAP_READ_BIT|GL_MAP_WRITE_BIT))
      #counter = convert(GLuint, unsafe_load(ptr))
      #println(counter)

    end
    if CAMERA_UPDATED global CAMERA_UPDATED=false end
    if GPU_CHUNKS_INIT global GPU_CHUNKS_INIT=false end

    #if SLEEP>0 Libc.systemsleep(SLEEP) end
    #sleep(SLEEP)
  #end
end

end # SCRIPT

max_stars_repo_name: Gilga/JLGLAssets

max_issues_repo_name: Gilga/JLGLAssets

max_forks_repo_name: Gilga/JLGLAssets